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.
4
5#include "content/browser/renderer_host/input/touch_event_queue.h"
6
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"
14
15using blink::WebInputEvent;
16using blink::WebTouchEvent;
17using blink::WebTouchPoint;
18
19namespace content {
20namespace {
21
22const InputEventAckState kDefaultNotForwardedAck =
23    INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS;
24
25typedef std::vector<TouchEventWithLatencyInfo> WebTouchEventWithLatencyList;
26
27TouchEventWithLatencyInfo 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;
33  return event;
34}
35
36bool IsNewTouchGesture(const WebTouchEvent& event) {
37  if (event.type != WebInputEvent::TouchStart)
38    return false;
39  if (!event.touchesLength)
40    return false;
41  for (size_t i = 0; i < event.touchesLength; i++) {
42    if (event.touches[i].state != WebTouchPoint::StatePressed)
43      return false;
44  }
45  return true;
46}
47
48bool ShouldTouchTypeTriggerTimeout(WebInputEvent::Type type) {
49  return type == WebInputEvent::TouchStart ||
50         type == WebInputEvent::TouchMove;
51}
52
53}  // namespace
54
55class TouchEventQueue::TouchTimeoutHandler {
56 public:
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))) {}
63
64  ~TouchTimeoutHandler() {}
65
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_);
71  }
72
73  bool ConfirmTouchEvent(InputEventAckState ack_result) {
74    switch (pending_ack_state_) {
75      case PENDING_ACK_NONE:
76        timeout_monitor_.Stop();
77        return false;
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);
86        } else {
87          SetPendingAckState(PENDING_ACK_NONE);
88          touch_queue_->UpdateTouchAckStates(timeout_event_.event, ack_result);
89        }
90        return true;
91      case PENDING_ACK_CANCEL_EVENT:
92        SetPendingAckState(PENDING_ACK_NONE);
93        return true;
94    }
95    return false;
96  }
97
98  bool HasTimeoutEvent() const {
99    return pending_ack_state_ != PENDING_ACK_NONE;
100  }
101
102  bool IsTimeoutTimerRunning() const {
103    return timeout_monitor_.IsRunning();
104  }
105
106 private:
107  enum PendingAckState {
108    PENDING_ACK_NONE,
109    PENDING_ACK_ORIGINAL_EVENT,
110    PENDING_ACK_CANCEL_EVENT,
111  };
112
113  void OnTimeOut() {
114    SetPendingAckState(PENDING_ACK_ORIGINAL_EVENT);
115    touch_queue_->FlushQueue();
116  }
117
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)
123      return true;
124    return !IsNewTouchGesture(timeout_event_.event);
125  }
126
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);
133        break;
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");
140        break;
141      case PENDING_ACK_NONE:
142        DCHECK(!timeout_monitor_.IsRunning());
143        DCHECK(touch_queue_->empty());
144        TRACE_EVENT_ASYNC_END0("input", "TouchEventTimeout", this);
145        break;
146    }
147    pending_ack_state_ = new_pending_ack_state;
148  }
149
150
151  TouchEventQueue* touch_queue_;
152
153  // How long to wait on a touch ack before cancelling the touch sequence.
154  base::TimeDelta timeout_delay_;
155
156  // The touch event source for which we expect the next ack.
157  PendingAckState pending_ack_state_;
158
159  // The event for which the ack timeout is triggered.
160  TouchEventWithLatencyInfo timeout_event_;
161
162  // Provides timeout-based callback behavior.
163  TimeoutMonitor timeout_monitor_;
164};
165
166
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.
172class CoalescedWebTouchEvent {
173 public:
174  CoalescedWebTouchEvent(const TouchEventWithLatencyInfo& event,
175                         bool ignore_ack)
176      : coalesced_event_(event),
177        ignore_ack_(ignore_ack) {
178    events_.push_back(event);
179    TRACE_EVENT_ASYNC_BEGIN0(
180        "input", "TouchEventQueue::QueueEvent", this);
181  }
182
183  ~CoalescedWebTouchEvent() {
184    TRACE_EVENT_ASYNC_END0(
185        "input", "TouchEventQueue::QueueEvent", this);
186  }
187
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) {
192    if (ignore_ack_)
193      return false;
194
195    if (!coalesced_event_.CanCoalesceWith(event_with_latency))
196      return false;
197
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);
202    return true;
203  }
204
205  const TouchEventWithLatencyInfo& coalesced_event() const {
206    return coalesced_event_;
207  }
208
209  WebTouchEventWithLatencyList::iterator begin() {
210    return events_.begin();
211  }
212
213  WebTouchEventWithLatencyList::iterator end() {
214    return events_.end();
215  }
216
217  size_t size() const { return events_.size(); }
218
219  bool ignore_ack() const { return ignore_ack_; }
220
221 private:
222  // This is the event that is forwarded to the renderer.
223  TouchEventWithLatencyInfo coalesced_event_;
224
225  // This is the list of the original events that were coalesced.
226  WebTouchEventWithLatencyList events_;
227
228  // If |ignore_ack_| is true, don't send this touch event to client
229  // when the event is acked.
230  bool ignore_ack_;
231
232  DISALLOW_COPY_AND_ASSIGN(CoalescedWebTouchEvent);
233};
234
235TouchEventQueue::TouchEventQueue(TouchEventQueueClient* client)
236    : client_(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) {
242  DCHECK(client);
243}
244
245TouchEventQueue::~TouchEventQueue() {
246  if (!touch_queue_.empty())
247    STLDeleteElements(&touch_queue_);
248}
249
250void 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
255    // immediately.
256    touch_queue_.push_back(new CoalescedWebTouchEvent(event, false));
257    TryForwardNextEventToRenderer();
258    return;
259  }
260
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))
266      return;
267  }
268  touch_queue_.push_back(new CoalescedWebTouchEvent(event, false));
269}
270
271void TouchEventQueue::ProcessTouchAck(InputEventAckState ack_result,
272                                      const ui::LatencyInfo& latency_info) {
273  DCHECK(!dispatching_touch_ack_);
274  dispatching_touch_ = false;
275
276  if (timeout_handler_ && timeout_handler_->ConfirmTouchEvent(ack_result))
277    return;
278
279  if (touch_queue_.empty())
280    return;
281
282  if (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED)
283    renderer_is_consuming_touch_gesture_ = true;
284
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();
290}
291
292void 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);
303      break;
304    }
305    PopTouchEventToClient(kDefaultNotForwardedAck, ui::LatencyInfo());
306  }
307}
308
309void 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);
322  }
323}
324
325void 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_)
334      return;
335    no_touch_to_renderer_ = true;
336
337    // If we have a timeout event, a cancel has already been dispatched
338    // for the current touch stream.
339    if (HasTimeoutEvent())
340      return;
341
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;
354  }
355}
356
357void TouchEventQueue::FlushQueue() {
358  DCHECK(!dispatching_touch_ack_);
359  DCHECK(!dispatching_touch_);
360  while (!touch_queue_.empty())
361    PopTouchEventToClient(kDefaultNotForwardedAck, ui::LatencyInfo());
362}
363
364bool TouchEventQueue::IsPendingAckTouchStart() const {
365  DCHECK(!dispatching_touch_ack_);
366  if (touch_queue_.empty())
367    return false;
368
369  const blink::WebTouchEvent& event =
370      touch_queue_.front()->coalesced_event().event;
371  return (event.type == WebInputEvent::TouchStart);
372}
373
374void TouchEventQueue::SetAckTimeoutEnabled(bool enabled,
375                                           size_t ack_timeout_delay_ms) {
376  if (!enabled) {
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;
380    return;
381  }
382
383  ack_timeout_enabled_ = true;
384  if (!timeout_handler_)
385    timeout_handler_.reset(new TouchTimeoutHandler(this, ack_timeout_delay_ms));
386}
387
388bool TouchEventQueue::HasTimeoutEvent() const {
389  return timeout_handler_ && timeout_handler_->HasTimeoutEvent();
390}
391
392bool TouchEventQueue::IsTimeoutRunningForTesting() const {
393  return timeout_handler_ && timeout_handler_->IsTimeoutTimerRunning();
394}
395
396const TouchEventWithLatencyInfo&
397TouchEventQueue::GetLatestEventForTesting() const {
398  return touch_queue_.back()->coalesced_event();
399}
400
401void TouchEventQueue::PopTouchEventToClient(
402    InputEventAckState ack_result,
403    const ui::LatencyInfo& renderer_latency_info) {
404  DCHECK(!dispatching_touch_ack_);
405  if (touch_queue_.empty())
406    return;
407  scoped_ptr<CoalescedWebTouchEvent> acked_event(touch_queue_.front());
408  touch_queue_.pop_front();
409
410  if (acked_event->ignore_ack())
411    return;
412
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());
417
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);
423  }
424}
425
426bool TouchEventQueue::ShouldForwardToRenderer(
427    const WebTouchEvent& event) const {
428  if (HasTimeoutEvent())
429    return false;
430
431  if (no_touch_to_renderer_ &&
432      event.type != blink::WebInputEvent::TouchCancel)
433    return false;
434
435  // Touch press events should always be forwarded to the renderer.
436  if (event.type == WebInputEvent::TouchStart)
437    return true;
438
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)
443      continue;
444
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)
448        return true;
449    } else {
450      // If the ACK status of a point is unknown, then the event should be
451      // forwarded to the renderer.
452      return true;
453    }
454  }
455
456  return false;
457}
458
459void 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);
470    }
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;
476    }
477  }
478}
479
480}  // namespace content
481