touch_event_queue.cc revision 010d83a9304c5a91596085d917d248abff47903a
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/debug/trace_event.h"
9#include "base/stl_util.h"
10#include "content/browser/renderer_host/input/timeout_monitor.h"
11#include "content/common/input/web_touch_event_traits.h"
12#include "ui/gfx/geometry/point_f.h"
13
14using blink::WebInputEvent;
15using blink::WebTouchEvent;
16using blink::WebTouchPoint;
17using ui::LatencyInfo;
18
19namespace content {
20namespace {
21
22// Time interval at which touchmove events will be forwarded to the client while
23// scrolling is active and possible.
24const double kAsyncTouchMoveIntervalSec = .2;
25
26// A slop region just larger than that used by many web applications. When
27// touchmove's are being sent asynchronously, movement outside this region will
28// trigger an immediate async touchmove to cancel potential tap-related logic.
29const double kApplicationSlopRegionLengthDipsSqared = 15. * 15.;
30
31// Using a small epsilon when comparing slop distances allows pixel perfect
32// slop determination when using fractional DIP coordinates (assuming the slop
33// region and DPI scale are reasonably proportioned).
34const float kSlopEpsilon = .05f;
35
36TouchEventWithLatencyInfo ObtainCancelEventForTouchEvent(
37    const TouchEventWithLatencyInfo& event_to_cancel) {
38  TouchEventWithLatencyInfo event = event_to_cancel;
39  WebTouchEventTraits::ResetTypeAndTouchStates(
40      WebInputEvent::TouchCancel,
41      // TODO(rbyers): Shouldn't we use a fresh timestamp?
42      event.event.timeStampSeconds,
43      &event.event);
44  return event;
45}
46
47bool ShouldTouchTriggerTimeout(const WebTouchEvent& event) {
48  return (event.type == WebInputEvent::TouchStart ||
49          event.type == WebInputEvent::TouchMove) &&
50         !WebInputEventTraits::IgnoresAckDisposition(event);
51}
52
53bool OutsideApplicationSlopRegion(const WebTouchEvent& event,
54                                  const gfx::PointF& anchor) {
55  return (gfx::PointF(event.touches[0].position) - anchor).LengthSquared() >
56         kApplicationSlopRegionLengthDipsSqared;
57}
58
59}  // namespace
60
61
62// Cancels a touch sequence if a touchstart or touchmove ack response is
63// sufficiently delayed.
64class TouchEventQueue::TouchTimeoutHandler {
65 public:
66  TouchTimeoutHandler(TouchEventQueue* touch_queue,
67                      base::TimeDelta timeout_delay)
68      : touch_queue_(touch_queue),
69        timeout_delay_(timeout_delay),
70        pending_ack_state_(PENDING_ACK_NONE),
71        timeout_monitor_(base::Bind(&TouchTimeoutHandler::OnTimeOut,
72                                    base::Unretained(this))) {
73    DCHECK(timeout_delay != base::TimeDelta());
74  }
75
76  ~TouchTimeoutHandler() {}
77
78  void Start(const TouchEventWithLatencyInfo& event) {
79    DCHECK_EQ(pending_ack_state_, PENDING_ACK_NONE);
80    DCHECK(ShouldTouchTriggerTimeout(event.event));
81    timeout_event_ = event;
82    timeout_monitor_.Restart(timeout_delay_);
83  }
84
85  bool ConfirmTouchEvent(InputEventAckState ack_result) {
86    switch (pending_ack_state_) {
87      case PENDING_ACK_NONE:
88        timeout_monitor_.Stop();
89        return false;
90      case PENDING_ACK_ORIGINAL_EVENT:
91        if (AckedTimeoutEventRequiresCancel(ack_result)) {
92          SetPendingAckState(PENDING_ACK_CANCEL_EVENT);
93          TouchEventWithLatencyInfo cancel_event =
94              ObtainCancelEventForTouchEvent(timeout_event_);
95          touch_queue_->SendTouchEventImmediately(cancel_event);
96        } else {
97          SetPendingAckState(PENDING_ACK_NONE);
98          touch_queue_->UpdateTouchAckStates(timeout_event_.event, ack_result);
99        }
100        return true;
101      case PENDING_ACK_CANCEL_EVENT:
102        SetPendingAckState(PENDING_ACK_NONE);
103        return true;
104    }
105    return false;
106  }
107
108  bool FilterEvent(const WebTouchEvent& event) {
109    return HasTimeoutEvent();
110  }
111
112  bool IsTimeoutTimerRunning() const {
113    return timeout_monitor_.IsRunning();
114  }
115
116  void Reset() {
117    pending_ack_state_ = PENDING_ACK_NONE;
118    timeout_monitor_.Stop();
119  }
120
121  void set_timeout_delay(base::TimeDelta timeout_delay) {
122    timeout_delay_ = timeout_delay;
123  }
124
125 private:
126  enum PendingAckState {
127    PENDING_ACK_NONE,
128    PENDING_ACK_ORIGINAL_EVENT,
129    PENDING_ACK_CANCEL_EVENT,
130  };
131
132  void OnTimeOut() {
133    SetPendingAckState(PENDING_ACK_ORIGINAL_EVENT);
134    touch_queue_->FlushQueue();
135  }
136
137  // Skip a cancel event if the timed-out event had no consumer and was the
138  // initial event in the gesture.
139  bool AckedTimeoutEventRequiresCancel(InputEventAckState ack_result) const {
140    DCHECK(HasTimeoutEvent());
141    if (ack_result != INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS)
142      return true;
143    return !WebTouchEventTraits::IsTouchSequenceStart(timeout_event_.event);
144  }
145
146  void SetPendingAckState(PendingAckState new_pending_ack_state) {
147    DCHECK_NE(pending_ack_state_, new_pending_ack_state);
148    switch (new_pending_ack_state) {
149      case PENDING_ACK_ORIGINAL_EVENT:
150        DCHECK_EQ(pending_ack_state_, PENDING_ACK_NONE);
151        TRACE_EVENT_ASYNC_BEGIN0("input", "TouchEventTimeout", this);
152        break;
153      case PENDING_ACK_CANCEL_EVENT:
154        DCHECK_EQ(pending_ack_state_, PENDING_ACK_ORIGINAL_EVENT);
155        DCHECK(!timeout_monitor_.IsRunning());
156        DCHECK(touch_queue_->empty());
157        TRACE_EVENT_ASYNC_STEP_INTO0(
158            "input", "TouchEventTimeout", this, "CancelEvent");
159        break;
160      case PENDING_ACK_NONE:
161        DCHECK(!timeout_monitor_.IsRunning());
162        DCHECK(touch_queue_->empty());
163        TRACE_EVENT_ASYNC_END0("input", "TouchEventTimeout", this);
164        break;
165    }
166    pending_ack_state_ = new_pending_ack_state;
167  }
168
169  bool HasTimeoutEvent() const {
170    return pending_ack_state_ != PENDING_ACK_NONE;
171  }
172
173
174  TouchEventQueue* touch_queue_;
175
176  // How long to wait on a touch ack before cancelling the touch sequence.
177  base::TimeDelta timeout_delay_;
178
179  // The touch event source for which we expect the next ack.
180  PendingAckState pending_ack_state_;
181
182  // The event for which the ack timeout is triggered.
183  TouchEventWithLatencyInfo timeout_event_;
184
185  // Provides timeout-based callback behavior.
186  TimeoutMonitor timeout_monitor_;
187};
188
189// Provides touchmove slop suppression for a single touch that remains within
190// a given slop region, unless the touchstart is preventDefault'ed.
191// TODO(jdduke): Use a flag bundled with each TouchEvent declaring whether it
192// has exceeded the slop region, removing duplicated slop determination logic.
193class TouchEventQueue::TouchMoveSlopSuppressor {
194 public:
195  TouchMoveSlopSuppressor(double slop_suppression_length_dips)
196      : slop_suppression_length_dips_squared_(slop_suppression_length_dips *
197                                              slop_suppression_length_dips),
198        suppressing_touchmoves_(false) {}
199
200  bool FilterEvent(const WebTouchEvent& event) {
201    if (WebTouchEventTraits::IsTouchSequenceStart(event)) {
202      touch_sequence_start_position_ =
203          gfx::PointF(event.touches[0].position);
204      suppressing_touchmoves_ = slop_suppression_length_dips_squared_ != 0;
205    }
206
207    if (event.type != WebInputEvent::TouchMove)
208      return false;
209
210    if (suppressing_touchmoves_) {
211      // Movement with a secondary pointer should terminate suppression.
212      if (event.touchesLength > 1) {
213        suppressing_touchmoves_ = false;
214      } else if (event.touchesLength == 1) {
215        // Movement outside of the slop region should terminate suppression.
216        gfx::PointF position(event.touches[0].position);
217        if ((position - touch_sequence_start_position_).LengthSquared() >
218            slop_suppression_length_dips_squared_)
219          suppressing_touchmoves_ = false;
220      }
221    }
222    return suppressing_touchmoves_;
223  }
224
225  void ConfirmTouchEvent(InputEventAckState ack_result) {
226    if (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED)
227      suppressing_touchmoves_ = false;
228  }
229
230 private:
231  double slop_suppression_length_dips_squared_;
232  gfx::PointF touch_sequence_start_position_;
233  bool suppressing_touchmoves_;
234
235  DISALLOW_COPY_AND_ASSIGN(TouchMoveSlopSuppressor);
236};
237
238// This class represents a single coalesced touch event. However, it also keeps
239// track of all the original touch-events that were coalesced into a single
240// event. The coalesced event is forwarded to the renderer, while the original
241// touch-events are sent to the Client (on ACK for the coalesced event) so that
242// the Client receives the event with their original timestamp.
243class CoalescedWebTouchEvent {
244 public:
245  // Events for which |async| is true will not be ack'ed to the client after the
246  // corresponding ack is received following dispatch.
247  CoalescedWebTouchEvent(const TouchEventWithLatencyInfo& event, bool async)
248      : coalesced_event_(event) {
249    if (async)
250      coalesced_event_.event.cancelable = false;
251    else
252      events_to_ack_.push_back(event);
253
254    TRACE_EVENT_ASYNC_BEGIN0("input", "TouchEventQueue::QueueEvent", this);
255  }
256
257  ~CoalescedWebTouchEvent() {
258    TRACE_EVENT_ASYNC_END0("input", "TouchEventQueue::QueueEvent", this);
259  }
260
261  // Coalesces the event with the existing event if possible. Returns whether
262  // the event was coalesced.
263  bool CoalesceEventIfPossible(
264      const TouchEventWithLatencyInfo& event_with_latency) {
265    if (!WillDispatchAckToClient())
266      return false;
267
268    if (!coalesced_event_.CanCoalesceWith(event_with_latency))
269      return false;
270
271    TRACE_EVENT_INSTANT0(
272        "input", "TouchEventQueue::MoveCoalesced", TRACE_EVENT_SCOPE_THREAD);
273    coalesced_event_.CoalesceWith(event_with_latency);
274    events_to_ack_.push_back(event_with_latency);
275    return true;
276  }
277
278  void UpdateLatencyInfoForAck(const ui::LatencyInfo& renderer_latency_info) {
279    if (!WillDispatchAckToClient())
280      return;
281
282    for (WebTouchEventWithLatencyList::iterator iter = events_to_ack_.begin(),
283                                                end = events_to_ack_.end();
284         iter != end;
285         ++iter) {
286      iter->latency.AddNewLatencyFrom(renderer_latency_info);
287    }
288  }
289
290  void DispatchAckToClient(InputEventAckState ack_result,
291                           TouchEventQueueClient* client) {
292    DCHECK(client);
293    if (!WillDispatchAckToClient())
294      return;
295
296    for (WebTouchEventWithLatencyList::const_iterator
297             iter = events_to_ack_.begin(),
298             end = events_to_ack_.end();
299         iter != end;
300         ++iter) {
301      client->OnTouchEventAck(*iter, ack_result);
302    }
303  }
304
305  const TouchEventWithLatencyInfo& coalesced_event() const {
306    return coalesced_event_;
307  }
308
309 private:
310  bool WillDispatchAckToClient() const { return !events_to_ack_.empty(); }
311
312  // This is the event that is forwarded to the renderer.
313  TouchEventWithLatencyInfo coalesced_event_;
314
315  // This is the list of the original events that were coalesced, each requiring
316  // future ack dispatch to the client.
317  typedef std::vector<TouchEventWithLatencyInfo> WebTouchEventWithLatencyList;
318  WebTouchEventWithLatencyList events_to_ack_;
319
320  DISALLOW_COPY_AND_ASSIGN(CoalescedWebTouchEvent);
321};
322
323TouchEventQueue::Config::Config()
324    : touchmove_slop_suppression_length_dips(0),
325      touch_scrolling_mode(TOUCH_SCROLLING_MODE_DEFAULT),
326      touch_ack_timeout_delay(base::TimeDelta::FromMilliseconds(200)),
327      touch_ack_timeout_supported(false) {
328}
329
330TouchEventQueue::TouchEventQueue(TouchEventQueueClient* client,
331                                 const Config& config)
332    : client_(client),
333      dispatching_touch_ack_(NULL),
334      dispatching_touch_(false),
335      touch_filtering_state_(TOUCH_FILTERING_STATE_DEFAULT),
336      ack_timeout_enabled_(config.touch_ack_timeout_supported),
337      touchmove_slop_suppressor_(new TouchMoveSlopSuppressor(
338          config.touchmove_slop_suppression_length_dips + kSlopEpsilon)),
339      send_touch_events_async_(false),
340      needs_async_touchmove_for_outer_slop_region_(false),
341      last_sent_touch_timestamp_sec_(0),
342      touch_scrolling_mode_(config.touch_scrolling_mode) {
343  DCHECK(client);
344  if (ack_timeout_enabled_) {
345    timeout_handler_.reset(
346        new TouchTimeoutHandler(this, config.touch_ack_timeout_delay));
347  }
348}
349
350TouchEventQueue::~TouchEventQueue() {
351  if (!touch_queue_.empty())
352    STLDeleteElements(&touch_queue_);
353}
354
355void TouchEventQueue::QueueEvent(const TouchEventWithLatencyInfo& event) {
356  TRACE_EVENT0("input", "TouchEventQueue::QueueEvent");
357
358  // If the queueing of |event| was triggered by an ack dispatch, defer
359  // processing the event until the dispatch has finished.
360  if (touch_queue_.empty() && !dispatching_touch_ack_) {
361    // Optimization of the case without touch handlers.  Removing this path
362    // yields identical results, but this avoids unnecessary allocations.
363    PreFilterResult filter_result = FilterBeforeForwarding(event.event);
364    if (filter_result != FORWARD_TO_RENDERER) {
365      client_->OnTouchEventAck(event,
366                               filter_result == ACK_WITH_NO_CONSUMER_EXISTS
367                                   ? INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS
368                                   : INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
369      return;
370    }
371
372    // There is no touch event in the queue. Forward it to the renderer
373    // immediately.
374    touch_queue_.push_back(new CoalescedWebTouchEvent(event, false));
375    ForwardNextEventToRenderer();
376    return;
377  }
378
379  // If the last queued touch-event was a touch-move, and the current event is
380  // also a touch-move, then the events can be coalesced into a single event.
381  if (touch_queue_.size() > 1) {
382    CoalescedWebTouchEvent* last_event = touch_queue_.back();
383    if (last_event->CoalesceEventIfPossible(event))
384      return;
385  }
386  touch_queue_.push_back(new CoalescedWebTouchEvent(event, false));
387}
388
389void TouchEventQueue::ProcessTouchAck(InputEventAckState ack_result,
390                                      const LatencyInfo& latency_info) {
391  TRACE_EVENT0("input", "TouchEventQueue::ProcessTouchAck");
392
393  DCHECK(!dispatching_touch_ack_);
394  dispatching_touch_ = false;
395
396  if (timeout_handler_ && timeout_handler_->ConfirmTouchEvent(ack_result))
397    return;
398
399  touchmove_slop_suppressor_->ConfirmTouchEvent(ack_result);
400
401  if (touch_queue_.empty())
402    return;
403
404  if (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED &&
405      touch_filtering_state_ == FORWARD_TOUCHES_UNTIL_TIMEOUT) {
406    touch_filtering_state_ = FORWARD_ALL_TOUCHES;
407  }
408
409  PopTouchEventToClient(ack_result, latency_info);
410  TryForwardNextEventToRenderer();
411}
412
413void TouchEventQueue::TryForwardNextEventToRenderer() {
414  DCHECK(!dispatching_touch_ack_);
415  // If there are queued touch events, then try to forward them to the renderer
416  // immediately, or ACK the events back to the client if appropriate.
417  while (!touch_queue_.empty()) {
418    PreFilterResult filter_result =
419        FilterBeforeForwarding(touch_queue_.front()->coalesced_event().event);
420    switch (filter_result) {
421      case ACK_WITH_NO_CONSUMER_EXISTS:
422        PopTouchEventToClient(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
423        break;
424      case ACK_WITH_NOT_CONSUMED:
425        PopTouchEventToClient(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
426        break;
427      case FORWARD_TO_RENDERER:
428        ForwardNextEventToRenderer();
429        return;
430    }
431  }
432}
433
434void TouchEventQueue::ForwardNextEventToRenderer() {
435  TRACE_EVENT0("input", "TouchEventQueue::ForwardNextEventToRenderer");
436
437  DCHECK(!empty());
438  DCHECK(!dispatching_touch_);
439  DCHECK_NE(touch_filtering_state_, DROP_ALL_TOUCHES);
440  TouchEventWithLatencyInfo touch = touch_queue_.front()->coalesced_event();
441
442  if (WebTouchEventTraits::IsTouchSequenceStart(touch.event)) {
443    touch_filtering_state_ =
444        ack_timeout_enabled_ ? FORWARD_TOUCHES_UNTIL_TIMEOUT
445                             : FORWARD_ALL_TOUCHES;
446    touch_ack_states_.clear();
447    send_touch_events_async_ = false;
448    touch_sequence_start_position_ =
449        gfx::PointF(touch.event.touches[0].position);
450  }
451
452  if (send_touch_events_async_ &&
453      touch.event.type == WebInputEvent::TouchMove) {
454    // Throttling touchmove's in a continuous touchmove stream while scrolling
455    // reduces the risk of jank. However, it's still important that the web
456    // application be sent touches at key points in the gesture stream,
457    // e.g., when the application slop region is exceeded or touchmove
458    // coalescing fails because of different modifiers.
459    const bool send_touchmove_now =
460        size() > 1 ||
461        (touch.event.timeStampSeconds >=
462         last_sent_touch_timestamp_sec_ + kAsyncTouchMoveIntervalSec) ||
463        (needs_async_touchmove_for_outer_slop_region_ &&
464         OutsideApplicationSlopRegion(touch.event,
465                                      touch_sequence_start_position_)) ||
466        (pending_async_touchmove_ &&
467         !pending_async_touchmove_->CanCoalesceWith(touch));
468
469    if (!send_touchmove_now) {
470      if (!pending_async_touchmove_) {
471        pending_async_touchmove_.reset(new TouchEventWithLatencyInfo(touch));
472      } else {
473        DCHECK(pending_async_touchmove_->CanCoalesceWith(touch));
474        pending_async_touchmove_->CoalesceWith(touch);
475      }
476      DCHECK_EQ(1U, size());
477      PopTouchEventToClient(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
478      // It's possible (though unlikely) that ack'ing the current touch will
479      // trigger the queueing of another touch event (e.g., a touchcancel). As
480      // forwarding of the queued event will be deferred while the ack is being
481      // dispatched (see |OnTouchEvent()|), try forwarding it now.
482      TryForwardNextEventToRenderer();
483      return;
484    }
485  }
486
487  last_sent_touch_timestamp_sec_ = touch.event.timeStampSeconds;
488
489  // Flush any pending async touch move. If it can be combined with the current
490  // (touchmove) event, great, otherwise send it immediately but separately. Its
491  // ack will trigger forwarding of the original |touch| event.
492  if (pending_async_touchmove_) {
493    if (pending_async_touchmove_->CanCoalesceWith(touch)) {
494      pending_async_touchmove_->CoalesceWith(touch);
495      pending_async_touchmove_->event.cancelable = !send_touch_events_async_;
496      touch = *pending_async_touchmove_.Pass();
497    } else {
498      scoped_ptr<TouchEventWithLatencyInfo> async_move =
499          pending_async_touchmove_.Pass();
500      async_move->event.cancelable = false;
501      touch_queue_.push_front(new CoalescedWebTouchEvent(*async_move, true));
502      SendTouchEventImmediately(*async_move);
503      return;
504    }
505  }
506
507  // Note: Marking touchstart events as not-cancelable prevents them from
508  // blocking subsequent gestures, but it may not be the best long term solution
509  // for tracking touch point dispatch.
510  if (send_touch_events_async_)
511    touch.event.cancelable = false;
512
513  // A synchronous ack will reset |dispatching_touch_|, in which case
514  // the touch timeout should not be started.
515  base::AutoReset<bool> dispatching_touch(&dispatching_touch_, true);
516  SendTouchEventImmediately(touch);
517  if (dispatching_touch_ &&
518      touch_filtering_state_ == FORWARD_TOUCHES_UNTIL_TIMEOUT &&
519      ShouldTouchTriggerTimeout(touch.event)) {
520    DCHECK(timeout_handler_);
521    timeout_handler_->Start(touch);
522  }
523}
524
525void TouchEventQueue::OnGestureScrollEvent(
526    const GestureEventWithLatencyInfo& gesture_event) {
527  if (gesture_event.event.type != blink::WebInputEvent::GestureScrollBegin)
528    return;
529
530  if (touch_scrolling_mode_ == TOUCH_SCROLLING_MODE_ASYNC_TOUCHMOVE) {
531    if (touch_filtering_state_ != DROP_ALL_TOUCHES &&
532        touch_filtering_state_ != DROP_TOUCHES_IN_SEQUENCE) {
533      // If no touch points have a consumer, prevent all subsequent touch events
534      // received during the scroll from reaching the renderer. This ensures
535      // that the first touchstart the renderer sees in any given sequence can
536      // always be preventDefault'ed (cancelable == true).
537      // TODO(jdduke): Revisit if touchstarts during scroll are made cancelable.
538      if (touch_ack_states_.empty() ||
539          AllTouchAckStatesHaveState(
540              INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS)) {
541        touch_filtering_state_ = DROP_TOUCHES_IN_SEQUENCE;
542        return;
543      }
544    }
545
546    pending_async_touchmove_.reset();
547    send_touch_events_async_ = true;
548    needs_async_touchmove_for_outer_slop_region_ = true;
549    return;
550  }
551
552  if (touch_scrolling_mode_ != TOUCH_SCROLLING_MODE_TOUCHCANCEL)
553    return;
554
555  // We assume that scroll events are generated synchronously from
556  // dispatching a touch event ack. This allows us to generate a synthetic
557  // cancel event that has the same touch ids as the touch event that
558  // is being acked. Otherwise, we don't perform the touch-cancel optimization.
559  if (!dispatching_touch_ack_)
560    return;
561
562  if (touch_filtering_state_ == DROP_TOUCHES_IN_SEQUENCE)
563    return;
564
565  touch_filtering_state_ = DROP_TOUCHES_IN_SEQUENCE;
566
567  // Fake a TouchCancel to cancel the touch points of the touch event
568  // that is currently being acked.
569  // Note: |dispatching_touch_ack_| is non-null when we reach here, meaning we
570  // are in the scope of PopTouchEventToClient() and that no touch event
571  // in the queue is waiting for ack from renderer. So we can just insert
572  // the touch cancel at the beginning of the queue.
573  touch_queue_.push_front(new CoalescedWebTouchEvent(
574      ObtainCancelEventForTouchEvent(
575          dispatching_touch_ack_->coalesced_event()), true));
576}
577
578void TouchEventQueue::OnGestureEventAck(
579    const GestureEventWithLatencyInfo& event,
580    InputEventAckState ack_result) {
581  if (touch_scrolling_mode_ != TOUCH_SCROLLING_MODE_ASYNC_TOUCHMOVE)
582    return;
583
584  if (event.event.type != blink::WebInputEvent::GestureScrollUpdate)
585    return;
586
587  // Throttle sending touchmove events as long as the scroll events are handled.
588  // Note that there's no guarantee that this ACK is for the most recent
589  // gesture event (or even part of the current sequence).  Worst case, the
590  // delay in updating the absorption state will result in minor UI glitches.
591  // A valid |pending_async_touchmove_| will be flushed when the next event is
592  // forwarded.
593  send_touch_events_async_ = (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED);
594  if (!send_touch_events_async_)
595    needs_async_touchmove_for_outer_slop_region_ = false;
596}
597
598void TouchEventQueue::OnHasTouchEventHandlers(bool has_handlers) {
599  DCHECK(!dispatching_touch_ack_);
600  DCHECK(!dispatching_touch_);
601
602  if (has_handlers) {
603    if (touch_filtering_state_ == DROP_ALL_TOUCHES) {
604      // If no touch handler was previously registered, ensure that we don't
605      // send a partial touch sequence to the renderer.
606      DCHECK(touch_queue_.empty());
607      touch_filtering_state_ = DROP_TOUCHES_IN_SEQUENCE;
608    }
609  } else {
610    // TODO(jdduke): Synthesize a TouchCancel if necessary to update Blink touch
611    // state tracking (e.g., if the touch handler was removed mid-sequence).
612    touch_filtering_state_ = DROP_ALL_TOUCHES;
613    pending_async_touchmove_.reset();
614    if (timeout_handler_)
615      timeout_handler_->Reset();
616    if (!touch_queue_.empty())
617      ProcessTouchAck(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS, LatencyInfo());
618    // As there is no touch handler, ack'ing the event should flush the queue.
619    DCHECK(touch_queue_.empty());
620  }
621}
622
623bool TouchEventQueue::IsPendingAckTouchStart() const {
624  DCHECK(!dispatching_touch_ack_);
625  if (touch_queue_.empty())
626    return false;
627
628  const blink::WebTouchEvent& event =
629      touch_queue_.front()->coalesced_event().event;
630  return (event.type == WebInputEvent::TouchStart);
631}
632
633void TouchEventQueue::SetAckTimeoutEnabled(bool enabled) {
634  // The timeout handler is valid only if explicitly supported in the config.
635  if (!timeout_handler_)
636    return;
637
638  if (ack_timeout_enabled_ == enabled)
639    return;
640
641  ack_timeout_enabled_ = enabled;
642
643  if (enabled)
644    return;
645
646  if (touch_filtering_state_ == FORWARD_TOUCHES_UNTIL_TIMEOUT)
647    touch_filtering_state_ = FORWARD_ALL_TOUCHES;
648  // Only reset the |timeout_handler_| if the timer is running and has not yet
649  // timed out. This ensures that an already timed out sequence is properly
650  // flushed by the handler.
651  if (timeout_handler_ && timeout_handler_->IsTimeoutTimerRunning())
652    timeout_handler_->Reset();
653}
654
655bool TouchEventQueue::HasPendingAsyncTouchMoveForTesting() const {
656  return pending_async_touchmove_;
657}
658
659bool TouchEventQueue::IsTimeoutRunningForTesting() const {
660  return timeout_handler_ && timeout_handler_->IsTimeoutTimerRunning();
661}
662
663const TouchEventWithLatencyInfo&
664TouchEventQueue::GetLatestEventForTesting() const {
665  return touch_queue_.back()->coalesced_event();
666}
667
668void TouchEventQueue::FlushQueue() {
669  DCHECK(!dispatching_touch_ack_);
670  DCHECK(!dispatching_touch_);
671  pending_async_touchmove_.reset();
672  if (touch_filtering_state_ != DROP_ALL_TOUCHES)
673    touch_filtering_state_ = DROP_TOUCHES_IN_SEQUENCE;
674  while (!touch_queue_.empty())
675    PopTouchEventToClient(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
676}
677
678void TouchEventQueue::PopTouchEventToClient(InputEventAckState ack_result) {
679  AckTouchEventToClient(ack_result, PopTouchEvent());
680}
681
682void TouchEventQueue::PopTouchEventToClient(
683    InputEventAckState ack_result,
684    const LatencyInfo& renderer_latency_info) {
685  scoped_ptr<CoalescedWebTouchEvent> acked_event = PopTouchEvent();
686  acked_event->UpdateLatencyInfoForAck(renderer_latency_info);
687  AckTouchEventToClient(ack_result, acked_event.Pass());
688}
689
690void TouchEventQueue::AckTouchEventToClient(
691    InputEventAckState ack_result,
692    scoped_ptr<CoalescedWebTouchEvent> acked_event) {
693  DCHECK(acked_event);
694  DCHECK(!dispatching_touch_ack_);
695  UpdateTouchAckStates(acked_event->coalesced_event().event, ack_result);
696
697  // Note that acking the touch-event may result in multiple gestures being sent
698  // to the renderer, or touch-events being queued.
699  base::AutoReset<const CoalescedWebTouchEvent*> dispatching_touch_ack(
700      &dispatching_touch_ack_, acked_event.get());
701  acked_event->DispatchAckToClient(ack_result, client_);
702}
703
704scoped_ptr<CoalescedWebTouchEvent> TouchEventQueue::PopTouchEvent() {
705  DCHECK(!touch_queue_.empty());
706  scoped_ptr<CoalescedWebTouchEvent> event(touch_queue_.front());
707  touch_queue_.pop_front();
708  return event.Pass();
709}
710
711void TouchEventQueue::SendTouchEventImmediately(
712    const TouchEventWithLatencyInfo& touch) {
713  if (needs_async_touchmove_for_outer_slop_region_) {
714    // Any event other than a touchmove (e.g., touchcancel or secondary
715    // touchstart) after a scroll has started will interrupt the need to send a
716    // an outer slop-region exceeding touchmove.
717    if (touch.event.type != WebInputEvent::TouchMove ||
718        OutsideApplicationSlopRegion(touch.event,
719                                     touch_sequence_start_position_))
720      needs_async_touchmove_for_outer_slop_region_ = false;
721  }
722
723  client_->SendTouchEventImmediately(touch);
724}
725
726TouchEventQueue::PreFilterResult
727TouchEventQueue::FilterBeforeForwarding(const WebTouchEvent& event) {
728  if (timeout_handler_ && timeout_handler_->FilterEvent(event))
729    return ACK_WITH_NO_CONSUMER_EXISTS;
730
731  if (touchmove_slop_suppressor_->FilterEvent(event))
732    return ACK_WITH_NOT_CONSUMED;
733
734  if (touch_filtering_state_ == DROP_ALL_TOUCHES)
735    return ACK_WITH_NO_CONSUMER_EXISTS;
736
737  if (touch_filtering_state_ == DROP_TOUCHES_IN_SEQUENCE &&
738      event.type != WebInputEvent::TouchCancel) {
739    if (WebTouchEventTraits::IsTouchSequenceStart(event))
740      return FORWARD_TO_RENDERER;
741    return ACK_WITH_NO_CONSUMER_EXISTS;
742  }
743
744  // Touch press events should always be forwarded to the renderer.
745  if (event.type == WebInputEvent::TouchStart)
746    return FORWARD_TO_RENDERER;
747
748  for (unsigned int i = 0; i < event.touchesLength; ++i) {
749    const WebTouchPoint& point = event.touches[i];
750    // If a point has been stationary, then don't take it into account.
751    if (point.state == WebTouchPoint::StateStationary)
752      continue;
753
754    if (touch_ack_states_.count(point.id) > 0) {
755      if (touch_ack_states_.find(point.id)->second !=
756          INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS)
757        return FORWARD_TO_RENDERER;
758    } else {
759      // If the ACK status of a point is unknown, then the event should be
760      // forwarded to the renderer.
761      return FORWARD_TO_RENDERER;
762    }
763  }
764
765  return ACK_WITH_NO_CONSUMER_EXISTS;
766}
767
768void TouchEventQueue::UpdateTouchAckStates(const WebTouchEvent& event,
769                                           InputEventAckState ack_result) {
770  // Update the ACK status for each touch point in the ACKed event.
771  if (event.type == WebInputEvent::TouchEnd ||
772      event.type == WebInputEvent::TouchCancel) {
773    // The points have been released. Erase the ACK states.
774    for (unsigned i = 0; i < event.touchesLength; ++i) {
775      const WebTouchPoint& point = event.touches[i];
776      if (point.state == WebTouchPoint::StateReleased ||
777          point.state == WebTouchPoint::StateCancelled)
778        touch_ack_states_.erase(point.id);
779    }
780  } else if (event.type == WebInputEvent::TouchStart) {
781    for (unsigned i = 0; i < event.touchesLength; ++i) {
782      const WebTouchPoint& point = event.touches[i];
783      if (point.state == WebTouchPoint::StatePressed)
784        touch_ack_states_[point.id] = ack_result;
785    }
786  }
787}
788
789bool TouchEventQueue::AllTouchAckStatesHaveState(
790    InputEventAckState ack_state) const {
791  if (touch_ack_states_.empty())
792    return false;
793
794  for (TouchPointAckStates::const_iterator iter = touch_ack_states_.begin(),
795                                           end = touch_ack_states_.end();
796       iter != end;
797       ++iter) {
798    if (iter->second != ack_state)
799      return false;
800  }
801
802  return true;
803}
804
805}  // namespace content
806