• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
14 using blink::WebInputEvent;
15 using blink::WebTouchEvent;
16 using blink::WebTouchPoint;
17 using ui::LatencyInfo;
18 
19 namespace content {
20 namespace {
21 
22 // Time interval at which touchmove events will be forwarded to the client while
23 // scrolling is active and possible.
24 const 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.
29 const 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).
34 const float kSlopEpsilon = .05f;
35 
ObtainCancelEventForTouchEvent(const TouchEventWithLatencyInfo & event_to_cancel)36 TouchEventWithLatencyInfo 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 
ShouldTouchTriggerTimeout(const WebTouchEvent & event)47 bool ShouldTouchTriggerTimeout(const WebTouchEvent& event) {
48   return (event.type == WebInputEvent::TouchStart ||
49           event.type == WebInputEvent::TouchMove) &&
50          !WebInputEventTraits::IgnoresAckDisposition(event);
51 }
52 
OutsideApplicationSlopRegion(const WebTouchEvent & event,const gfx::PointF & anchor)53 bool 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.
64 class TouchEventQueue::TouchTimeoutHandler {
65  public:
TouchTimeoutHandler(TouchEventQueue * touch_queue,base::TimeDelta timeout_delay)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 
~TouchTimeoutHandler()76   ~TouchTimeoutHandler() {}
77 
Start(const TouchEventWithLatencyInfo & event)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 
ConfirmTouchEvent(InputEventAckState ack_result)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 
FilterEvent(const WebTouchEvent & event)108   bool FilterEvent(const WebTouchEvent& event) {
109     return HasTimeoutEvent();
110   }
111 
IsTimeoutTimerRunning() const112   bool IsTimeoutTimerRunning() const {
113     return timeout_monitor_.IsRunning();
114   }
115 
Reset()116   void Reset() {
117     pending_ack_state_ = PENDING_ACK_NONE;
118     timeout_monitor_.Stop();
119   }
120 
set_timeout_delay(base::TimeDelta timeout_delay)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 
OnTimeOut()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.
AckedTimeoutEventRequiresCancel(InputEventAckState ack_result) const139   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 
SetPendingAckState(PendingAckState new_pending_ack_state)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 
HasTimeoutEvent() const169   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.
193 class TouchEventQueue::TouchMoveSlopSuppressor {
194  public:
TouchMoveSlopSuppressor(double slop_suppression_length_dips)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 
FilterEvent(const WebTouchEvent & event)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::TouchEnd ||
208         event.type == WebInputEvent::TouchCancel)
209       suppressing_touchmoves_ = false;
210 
211     if (event.type != WebInputEvent::TouchMove)
212       return false;
213 
214     if (suppressing_touchmoves_) {
215       // Movement with a secondary pointer should terminate suppression.
216       if (event.touchesLength > 1) {
217         suppressing_touchmoves_ = false;
218       } else if (event.touchesLength == 1) {
219         // Movement outside of the slop region should terminate suppression.
220         gfx::PointF position(event.touches[0].position);
221         if ((position - touch_sequence_start_position_).LengthSquared() >
222             slop_suppression_length_dips_squared_)
223           suppressing_touchmoves_ = false;
224       }
225     }
226     return suppressing_touchmoves_;
227   }
228 
ConfirmTouchEvent(InputEventAckState ack_result)229   void ConfirmTouchEvent(InputEventAckState ack_result) {
230     if (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED)
231       suppressing_touchmoves_ = false;
232   }
233 
suppressing_touchmoves() const234   bool suppressing_touchmoves() const { return suppressing_touchmoves_; }
235 
236  private:
237   double slop_suppression_length_dips_squared_;
238   gfx::PointF touch_sequence_start_position_;
239   bool suppressing_touchmoves_;
240 
241   DISALLOW_COPY_AND_ASSIGN(TouchMoveSlopSuppressor);
242 };
243 
244 // This class represents a single coalesced touch event. However, it also keeps
245 // track of all the original touch-events that were coalesced into a single
246 // event. The coalesced event is forwarded to the renderer, while the original
247 // touch-events are sent to the Client (on ACK for the coalesced event) so that
248 // the Client receives the event with their original timestamp.
249 class CoalescedWebTouchEvent {
250  public:
251   // Events for which |async| is true will not be ack'ed to the client after the
252   // corresponding ack is received following dispatch.
CoalescedWebTouchEvent(const TouchEventWithLatencyInfo & event,bool async)253   CoalescedWebTouchEvent(const TouchEventWithLatencyInfo& event, bool async)
254       : coalesced_event_(event) {
255     if (async)
256       coalesced_event_.event.cancelable = false;
257     else
258       events_to_ack_.push_back(event);
259 
260     TRACE_EVENT_ASYNC_BEGIN0("input", "TouchEventQueue::QueueEvent", this);
261   }
262 
~CoalescedWebTouchEvent()263   ~CoalescedWebTouchEvent() {
264     TRACE_EVENT_ASYNC_END0("input", "TouchEventQueue::QueueEvent", this);
265   }
266 
267   // Coalesces the event with the existing event if possible. Returns whether
268   // the event was coalesced.
CoalesceEventIfPossible(const TouchEventWithLatencyInfo & event_with_latency)269   bool CoalesceEventIfPossible(
270       const TouchEventWithLatencyInfo& event_with_latency) {
271     if (!WillDispatchAckToClient())
272       return false;
273 
274     if (!coalesced_event_.CanCoalesceWith(event_with_latency))
275       return false;
276 
277     TRACE_EVENT_INSTANT0(
278         "input", "TouchEventQueue::MoveCoalesced", TRACE_EVENT_SCOPE_THREAD);
279     coalesced_event_.CoalesceWith(event_with_latency);
280     events_to_ack_.push_back(event_with_latency);
281     return true;
282   }
283 
UpdateLatencyInfoForAck(const ui::LatencyInfo & renderer_latency_info)284   void UpdateLatencyInfoForAck(const ui::LatencyInfo& renderer_latency_info) {
285     if (!WillDispatchAckToClient())
286       return;
287 
288     for (WebTouchEventWithLatencyList::iterator iter = events_to_ack_.begin(),
289                                                 end = events_to_ack_.end();
290          iter != end;
291          ++iter) {
292       iter->latency.AddNewLatencyFrom(renderer_latency_info);
293     }
294   }
295 
DispatchAckToClient(InputEventAckState ack_result,TouchEventQueueClient * client)296   void DispatchAckToClient(InputEventAckState ack_result,
297                            TouchEventQueueClient* client) {
298     DCHECK(client);
299     if (!WillDispatchAckToClient())
300       return;
301 
302     for (WebTouchEventWithLatencyList::const_iterator
303              iter = events_to_ack_.begin(),
304              end = events_to_ack_.end();
305          iter != end;
306          ++iter) {
307       client->OnTouchEventAck(*iter, ack_result);
308     }
309   }
310 
coalesced_event() const311   const TouchEventWithLatencyInfo& coalesced_event() const {
312     return coalesced_event_;
313   }
314 
315  private:
WillDispatchAckToClient() const316   bool WillDispatchAckToClient() const { return !events_to_ack_.empty(); }
317 
318   // This is the event that is forwarded to the renderer.
319   TouchEventWithLatencyInfo coalesced_event_;
320 
321   // This is the list of the original events that were coalesced, each requiring
322   // future ack dispatch to the client.
323   typedef std::vector<TouchEventWithLatencyInfo> WebTouchEventWithLatencyList;
324   WebTouchEventWithLatencyList events_to_ack_;
325 
326   DISALLOW_COPY_AND_ASSIGN(CoalescedWebTouchEvent);
327 };
328 
Config()329 TouchEventQueue::Config::Config()
330     : touchmove_slop_suppression_length_dips(0),
331       touchmove_slop_suppression_region_includes_boundary(true),
332       touch_scrolling_mode(TOUCH_SCROLLING_MODE_DEFAULT),
333       touch_ack_timeout_delay(base::TimeDelta::FromMilliseconds(200)),
334       touch_ack_timeout_supported(false) {
335 }
336 
TouchEventQueue(TouchEventQueueClient * client,const Config & config)337 TouchEventQueue::TouchEventQueue(TouchEventQueueClient* client,
338                                  const Config& config)
339     : client_(client),
340       dispatching_touch_ack_(NULL),
341       dispatching_touch_(false),
342       touch_filtering_state_(TOUCH_FILTERING_STATE_DEFAULT),
343       ack_timeout_enabled_(config.touch_ack_timeout_supported),
344       touchmove_slop_suppressor_(new TouchMoveSlopSuppressor(
345           config.touchmove_slop_suppression_length_dips +
346           (config.touchmove_slop_suppression_region_includes_boundary
347                ? kSlopEpsilon
348                : -kSlopEpsilon))),
349       send_touch_events_async_(false),
350       needs_async_touchmove_for_outer_slop_region_(false),
351       last_sent_touch_timestamp_sec_(0),
352       touch_scrolling_mode_(config.touch_scrolling_mode) {
353   DCHECK(client);
354   if (ack_timeout_enabled_) {
355     timeout_handler_.reset(
356         new TouchTimeoutHandler(this, config.touch_ack_timeout_delay));
357   }
358 }
359 
~TouchEventQueue()360 TouchEventQueue::~TouchEventQueue() {
361   if (!touch_queue_.empty())
362     STLDeleteElements(&touch_queue_);
363 }
364 
QueueEvent(const TouchEventWithLatencyInfo & event)365 void TouchEventQueue::QueueEvent(const TouchEventWithLatencyInfo& event) {
366   TRACE_EVENT0("input", "TouchEventQueue::QueueEvent");
367 
368   // If the queueing of |event| was triggered by an ack dispatch, defer
369   // processing the event until the dispatch has finished.
370   if (touch_queue_.empty() && !dispatching_touch_ack_) {
371     // Optimization of the case without touch handlers.  Removing this path
372     // yields identical results, but this avoids unnecessary allocations.
373     PreFilterResult filter_result = FilterBeforeForwarding(event.event);
374     if (filter_result != FORWARD_TO_RENDERER) {
375       client_->OnTouchEventAck(event,
376                                filter_result == ACK_WITH_NO_CONSUMER_EXISTS
377                                    ? INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS
378                                    : INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
379       return;
380     }
381 
382     // There is no touch event in the queue. Forward it to the renderer
383     // immediately.
384     touch_queue_.push_back(new CoalescedWebTouchEvent(event, false));
385     ForwardNextEventToRenderer();
386     return;
387   }
388 
389   // If the last queued touch-event was a touch-move, and the current event is
390   // also a touch-move, then the events can be coalesced into a single event.
391   if (touch_queue_.size() > 1) {
392     CoalescedWebTouchEvent* last_event = touch_queue_.back();
393     if (last_event->CoalesceEventIfPossible(event))
394       return;
395   }
396   touch_queue_.push_back(new CoalescedWebTouchEvent(event, false));
397 }
398 
ProcessTouchAck(InputEventAckState ack_result,const LatencyInfo & latency_info)399 void TouchEventQueue::ProcessTouchAck(InputEventAckState ack_result,
400                                       const LatencyInfo& latency_info) {
401   TRACE_EVENT0("input", "TouchEventQueue::ProcessTouchAck");
402 
403   DCHECK(!dispatching_touch_ack_);
404   dispatching_touch_ = false;
405 
406   if (timeout_handler_ && timeout_handler_->ConfirmTouchEvent(ack_result))
407     return;
408 
409   touchmove_slop_suppressor_->ConfirmTouchEvent(ack_result);
410 
411   if (touch_queue_.empty())
412     return;
413 
414   if (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED &&
415       touch_filtering_state_ == FORWARD_TOUCHES_UNTIL_TIMEOUT) {
416     touch_filtering_state_ = FORWARD_ALL_TOUCHES;
417   }
418 
419   PopTouchEventToClient(ack_result, latency_info);
420   TryForwardNextEventToRenderer();
421 }
422 
TryForwardNextEventToRenderer()423 void TouchEventQueue::TryForwardNextEventToRenderer() {
424   DCHECK(!dispatching_touch_ack_);
425   // If there are queued touch events, then try to forward them to the renderer
426   // immediately, or ACK the events back to the client if appropriate.
427   while (!touch_queue_.empty()) {
428     PreFilterResult filter_result =
429         FilterBeforeForwarding(touch_queue_.front()->coalesced_event().event);
430     switch (filter_result) {
431       case ACK_WITH_NO_CONSUMER_EXISTS:
432         PopTouchEventToClient(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
433         break;
434       case ACK_WITH_NOT_CONSUMED:
435         PopTouchEventToClient(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
436         break;
437       case FORWARD_TO_RENDERER:
438         ForwardNextEventToRenderer();
439         return;
440     }
441   }
442 }
443 
ForwardNextEventToRenderer()444 void TouchEventQueue::ForwardNextEventToRenderer() {
445   TRACE_EVENT0("input", "TouchEventQueue::ForwardNextEventToRenderer");
446 
447   DCHECK(!empty());
448   DCHECK(!dispatching_touch_);
449   DCHECK_NE(touch_filtering_state_, DROP_ALL_TOUCHES);
450   TouchEventWithLatencyInfo touch = touch_queue_.front()->coalesced_event();
451 
452   if (WebTouchEventTraits::IsTouchSequenceStart(touch.event)) {
453     touch_filtering_state_ =
454         ack_timeout_enabled_ ? FORWARD_TOUCHES_UNTIL_TIMEOUT
455                              : FORWARD_ALL_TOUCHES;
456     touch_ack_states_.clear();
457     send_touch_events_async_ = false;
458     touch_sequence_start_position_ =
459         gfx::PointF(touch.event.touches[0].position);
460   }
461 
462   if (send_touch_events_async_ &&
463       touch.event.type == WebInputEvent::TouchMove) {
464     // Throttling touchmove's in a continuous touchmove stream while scrolling
465     // reduces the risk of jank. However, it's still important that the web
466     // application be sent touches at key points in the gesture stream,
467     // e.g., when the application slop region is exceeded or touchmove
468     // coalescing fails because of different modifiers.
469     const bool send_touchmove_now =
470         size() > 1 ||
471         (touch.event.timeStampSeconds >=
472          last_sent_touch_timestamp_sec_ + kAsyncTouchMoveIntervalSec) ||
473         (needs_async_touchmove_for_outer_slop_region_ &&
474          OutsideApplicationSlopRegion(touch.event,
475                                       touch_sequence_start_position_)) ||
476         (pending_async_touchmove_ &&
477          !pending_async_touchmove_->CanCoalesceWith(touch));
478 
479     if (!send_touchmove_now) {
480       if (!pending_async_touchmove_) {
481         pending_async_touchmove_.reset(new TouchEventWithLatencyInfo(touch));
482       } else {
483         DCHECK(pending_async_touchmove_->CanCoalesceWith(touch));
484         pending_async_touchmove_->CoalesceWith(touch);
485       }
486       DCHECK_EQ(1U, size());
487       PopTouchEventToClient(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
488       // It's possible (though unlikely) that ack'ing the current touch will
489       // trigger the queueing of another touch event (e.g., a touchcancel). As
490       // forwarding of the queued event will be deferred while the ack is being
491       // dispatched (see |OnTouchEvent()|), try forwarding it now.
492       TryForwardNextEventToRenderer();
493       return;
494     }
495   }
496 
497   last_sent_touch_timestamp_sec_ = touch.event.timeStampSeconds;
498 
499   // Flush any pending async touch move. If it can be combined with the current
500   // (touchmove) event, great, otherwise send it immediately but separately. Its
501   // ack will trigger forwarding of the original |touch| event.
502   if (pending_async_touchmove_) {
503     if (pending_async_touchmove_->CanCoalesceWith(touch)) {
504       pending_async_touchmove_->CoalesceWith(touch);
505       pending_async_touchmove_->event.cancelable = !send_touch_events_async_;
506       touch = *pending_async_touchmove_.Pass();
507     } else {
508       scoped_ptr<TouchEventWithLatencyInfo> async_move =
509           pending_async_touchmove_.Pass();
510       async_move->event.cancelable = false;
511       touch_queue_.push_front(new CoalescedWebTouchEvent(*async_move, true));
512       SendTouchEventImmediately(*async_move);
513       return;
514     }
515   }
516 
517   // Note: Marking touchstart events as not-cancelable prevents them from
518   // blocking subsequent gestures, but it may not be the best long term solution
519   // for tracking touch point dispatch.
520   if (send_touch_events_async_)
521     touch.event.cancelable = false;
522 
523   // A synchronous ack will reset |dispatching_touch_|, in which case
524   // the touch timeout should not be started.
525   base::AutoReset<bool> dispatching_touch(&dispatching_touch_, true);
526   SendTouchEventImmediately(touch);
527   if (dispatching_touch_ &&
528       touch_filtering_state_ == FORWARD_TOUCHES_UNTIL_TIMEOUT &&
529       ShouldTouchTriggerTimeout(touch.event)) {
530     DCHECK(timeout_handler_);
531     timeout_handler_->Start(touch);
532   }
533 }
534 
OnGestureScrollEvent(const GestureEventWithLatencyInfo & gesture_event)535 void TouchEventQueue::OnGestureScrollEvent(
536     const GestureEventWithLatencyInfo& gesture_event) {
537   if (gesture_event.event.type != blink::WebInputEvent::GestureScrollBegin)
538     return;
539 
540   if (touch_filtering_state_ != DROP_ALL_TOUCHES &&
541       touch_filtering_state_ != DROP_TOUCHES_IN_SEQUENCE) {
542     DCHECK(!touchmove_slop_suppressor_->suppressing_touchmoves())
543         << "The renderer should be offered a touchmove before scrolling begins";
544   }
545 
546   if (touch_scrolling_mode_ == TOUCH_SCROLLING_MODE_ASYNC_TOUCHMOVE) {
547     if (touch_filtering_state_ != DROP_ALL_TOUCHES &&
548         touch_filtering_state_ != DROP_TOUCHES_IN_SEQUENCE) {
549       // If no touch points have a consumer, prevent all subsequent touch events
550       // received during the scroll from reaching the renderer. This ensures
551       // that the first touchstart the renderer sees in any given sequence can
552       // always be preventDefault'ed (cancelable == true).
553       // TODO(jdduke): Revisit if touchstarts during scroll are made cancelable.
554       if (touch_ack_states_.empty() ||
555           AllTouchAckStatesHaveState(
556               INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS)) {
557         touch_filtering_state_ = DROP_TOUCHES_IN_SEQUENCE;
558         return;
559       }
560     }
561 
562     pending_async_touchmove_.reset();
563     send_touch_events_async_ = true;
564     needs_async_touchmove_for_outer_slop_region_ = true;
565     return;
566   }
567 
568   if (touch_scrolling_mode_ != TOUCH_SCROLLING_MODE_TOUCHCANCEL)
569     return;
570 
571   // We assume that scroll events are generated synchronously from
572   // dispatching a touch event ack. This allows us to generate a synthetic
573   // cancel event that has the same touch ids as the touch event that
574   // is being acked. Otherwise, we don't perform the touch-cancel optimization.
575   if (!dispatching_touch_ack_)
576     return;
577 
578   if (touch_filtering_state_ == DROP_TOUCHES_IN_SEQUENCE)
579     return;
580 
581   touch_filtering_state_ = DROP_TOUCHES_IN_SEQUENCE;
582 
583   // Fake a TouchCancel to cancel the touch points of the touch event
584   // that is currently being acked.
585   // Note: |dispatching_touch_ack_| is non-null when we reach here, meaning we
586   // are in the scope of PopTouchEventToClient() and that no touch event
587   // in the queue is waiting for ack from renderer. So we can just insert
588   // the touch cancel at the beginning of the queue.
589   touch_queue_.push_front(new CoalescedWebTouchEvent(
590       ObtainCancelEventForTouchEvent(
591           dispatching_touch_ack_->coalesced_event()), true));
592 }
593 
OnGestureEventAck(const GestureEventWithLatencyInfo & event,InputEventAckState ack_result)594 void TouchEventQueue::OnGestureEventAck(
595     const GestureEventWithLatencyInfo& event,
596     InputEventAckState ack_result) {
597   if (touch_scrolling_mode_ != TOUCH_SCROLLING_MODE_ASYNC_TOUCHMOVE)
598     return;
599 
600   if (event.event.type != blink::WebInputEvent::GestureScrollUpdate)
601     return;
602 
603   // Throttle sending touchmove events as long as the scroll events are handled.
604   // Note that there's no guarantee that this ACK is for the most recent
605   // gesture event (or even part of the current sequence).  Worst case, the
606   // delay in updating the absorption state will result in minor UI glitches.
607   // A valid |pending_async_touchmove_| will be flushed when the next event is
608   // forwarded.
609   send_touch_events_async_ = (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED);
610   if (!send_touch_events_async_)
611     needs_async_touchmove_for_outer_slop_region_ = false;
612 }
613 
OnHasTouchEventHandlers(bool has_handlers)614 void TouchEventQueue::OnHasTouchEventHandlers(bool has_handlers) {
615   DCHECK(!dispatching_touch_ack_);
616   DCHECK(!dispatching_touch_);
617 
618   if (has_handlers) {
619     if (touch_filtering_state_ == DROP_ALL_TOUCHES) {
620       // If no touch handler was previously registered, ensure that we don't
621       // send a partial touch sequence to the renderer.
622       DCHECK(touch_queue_.empty());
623       touch_filtering_state_ = DROP_TOUCHES_IN_SEQUENCE;
624     }
625   } else {
626     // TODO(jdduke): Synthesize a TouchCancel if necessary to update Blink touch
627     // state tracking and/or touch-action filtering (e.g., if the touch handler
628     // was removed mid-sequence), crbug.com/375940.
629     touch_filtering_state_ = DROP_ALL_TOUCHES;
630     pending_async_touchmove_.reset();
631     if (timeout_handler_)
632       timeout_handler_->Reset();
633     if (!touch_queue_.empty())
634       ProcessTouchAck(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS, LatencyInfo());
635     // As there is no touch handler, ack'ing the event should flush the queue.
636     DCHECK(touch_queue_.empty());
637   }
638 }
639 
IsPendingAckTouchStart() const640 bool TouchEventQueue::IsPendingAckTouchStart() const {
641   DCHECK(!dispatching_touch_ack_);
642   if (touch_queue_.empty())
643     return false;
644 
645   const blink::WebTouchEvent& event =
646       touch_queue_.front()->coalesced_event().event;
647   return (event.type == WebInputEvent::TouchStart);
648 }
649 
SetAckTimeoutEnabled(bool enabled)650 void TouchEventQueue::SetAckTimeoutEnabled(bool enabled) {
651   // The timeout handler is valid only if explicitly supported in the config.
652   if (!timeout_handler_)
653     return;
654 
655   if (ack_timeout_enabled_ == enabled)
656     return;
657 
658   ack_timeout_enabled_ = enabled;
659 
660   if (enabled)
661     return;
662 
663   if (touch_filtering_state_ == FORWARD_TOUCHES_UNTIL_TIMEOUT)
664     touch_filtering_state_ = FORWARD_ALL_TOUCHES;
665   // Only reset the |timeout_handler_| if the timer is running and has not yet
666   // timed out. This ensures that an already timed out sequence is properly
667   // flushed by the handler.
668   if (timeout_handler_ && timeout_handler_->IsTimeoutTimerRunning())
669     timeout_handler_->Reset();
670 }
671 
HasPendingAsyncTouchMoveForTesting() const672 bool TouchEventQueue::HasPendingAsyncTouchMoveForTesting() const {
673   return pending_async_touchmove_;
674 }
675 
IsTimeoutRunningForTesting() const676 bool TouchEventQueue::IsTimeoutRunningForTesting() const {
677   return timeout_handler_ && timeout_handler_->IsTimeoutTimerRunning();
678 }
679 
680 const TouchEventWithLatencyInfo&
GetLatestEventForTesting() const681 TouchEventQueue::GetLatestEventForTesting() const {
682   return touch_queue_.back()->coalesced_event();
683 }
684 
FlushQueue()685 void TouchEventQueue::FlushQueue() {
686   DCHECK(!dispatching_touch_ack_);
687   DCHECK(!dispatching_touch_);
688   pending_async_touchmove_.reset();
689   if (touch_filtering_state_ != DROP_ALL_TOUCHES)
690     touch_filtering_state_ = DROP_TOUCHES_IN_SEQUENCE;
691   while (!touch_queue_.empty())
692     PopTouchEventToClient(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
693 }
694 
PopTouchEventToClient(InputEventAckState ack_result)695 void TouchEventQueue::PopTouchEventToClient(InputEventAckState ack_result) {
696   AckTouchEventToClient(ack_result, PopTouchEvent());
697 }
698 
PopTouchEventToClient(InputEventAckState ack_result,const LatencyInfo & renderer_latency_info)699 void TouchEventQueue::PopTouchEventToClient(
700     InputEventAckState ack_result,
701     const LatencyInfo& renderer_latency_info) {
702   scoped_ptr<CoalescedWebTouchEvent> acked_event = PopTouchEvent();
703   acked_event->UpdateLatencyInfoForAck(renderer_latency_info);
704   AckTouchEventToClient(ack_result, acked_event.Pass());
705 }
706 
AckTouchEventToClient(InputEventAckState ack_result,scoped_ptr<CoalescedWebTouchEvent> acked_event)707 void TouchEventQueue::AckTouchEventToClient(
708     InputEventAckState ack_result,
709     scoped_ptr<CoalescedWebTouchEvent> acked_event) {
710   DCHECK(acked_event);
711   DCHECK(!dispatching_touch_ack_);
712   UpdateTouchAckStates(acked_event->coalesced_event().event, ack_result);
713 
714   // Note that acking the touch-event may result in multiple gestures being sent
715   // to the renderer, or touch-events being queued.
716   base::AutoReset<const CoalescedWebTouchEvent*> dispatching_touch_ack(
717       &dispatching_touch_ack_, acked_event.get());
718   acked_event->DispatchAckToClient(ack_result, client_);
719 }
720 
PopTouchEvent()721 scoped_ptr<CoalescedWebTouchEvent> TouchEventQueue::PopTouchEvent() {
722   DCHECK(!touch_queue_.empty());
723   scoped_ptr<CoalescedWebTouchEvent> event(touch_queue_.front());
724   touch_queue_.pop_front();
725   return event.Pass();
726 }
727 
SendTouchEventImmediately(const TouchEventWithLatencyInfo & touch)728 void TouchEventQueue::SendTouchEventImmediately(
729     const TouchEventWithLatencyInfo& touch) {
730   if (needs_async_touchmove_for_outer_slop_region_) {
731     // Any event other than a touchmove (e.g., touchcancel or secondary
732     // touchstart) after a scroll has started will interrupt the need to send a
733     // an outer slop-region exceeding touchmove.
734     if (touch.event.type != WebInputEvent::TouchMove ||
735         OutsideApplicationSlopRegion(touch.event,
736                                      touch_sequence_start_position_))
737       needs_async_touchmove_for_outer_slop_region_ = false;
738   }
739 
740   client_->SendTouchEventImmediately(touch);
741 }
742 
743 TouchEventQueue::PreFilterResult
FilterBeforeForwarding(const WebTouchEvent & event)744 TouchEventQueue::FilterBeforeForwarding(const WebTouchEvent& event) {
745   if (timeout_handler_ && timeout_handler_->FilterEvent(event))
746     return ACK_WITH_NO_CONSUMER_EXISTS;
747 
748   if (touchmove_slop_suppressor_->FilterEvent(event))
749     return ACK_WITH_NOT_CONSUMED;
750 
751   if (touch_filtering_state_ == DROP_ALL_TOUCHES)
752     return ACK_WITH_NO_CONSUMER_EXISTS;
753 
754   if (touch_filtering_state_ == DROP_TOUCHES_IN_SEQUENCE &&
755       event.type != WebInputEvent::TouchCancel) {
756     if (WebTouchEventTraits::IsTouchSequenceStart(event))
757       return FORWARD_TO_RENDERER;
758     return ACK_WITH_NO_CONSUMER_EXISTS;
759   }
760 
761   // Touch press events should always be forwarded to the renderer.
762   if (event.type == WebInputEvent::TouchStart)
763     return FORWARD_TO_RENDERER;
764 
765   for (unsigned int i = 0; i < event.touchesLength; ++i) {
766     const WebTouchPoint& point = event.touches[i];
767     // If a point has been stationary, then don't take it into account.
768     if (point.state == WebTouchPoint::StateStationary)
769       continue;
770 
771     if (touch_ack_states_.count(point.id) > 0) {
772       if (touch_ack_states_.find(point.id)->second !=
773           INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS)
774         return FORWARD_TO_RENDERER;
775     } else {
776       // If the ACK status of a point is unknown, then the event should be
777       // forwarded to the renderer.
778       return FORWARD_TO_RENDERER;
779     }
780   }
781 
782   return ACK_WITH_NO_CONSUMER_EXISTS;
783 }
784 
UpdateTouchAckStates(const WebTouchEvent & event,InputEventAckState ack_result)785 void TouchEventQueue::UpdateTouchAckStates(const WebTouchEvent& event,
786                                            InputEventAckState ack_result) {
787   // Update the ACK status for each touch point in the ACKed event.
788   if (event.type == WebInputEvent::TouchEnd ||
789       event.type == WebInputEvent::TouchCancel) {
790     // The points have been released. Erase the ACK states.
791     for (unsigned i = 0; i < event.touchesLength; ++i) {
792       const WebTouchPoint& point = event.touches[i];
793       if (point.state == WebTouchPoint::StateReleased ||
794           point.state == WebTouchPoint::StateCancelled)
795         touch_ack_states_.erase(point.id);
796     }
797   } else if (event.type == WebInputEvent::TouchStart) {
798     for (unsigned i = 0; i < event.touchesLength; ++i) {
799       const WebTouchPoint& point = event.touches[i];
800       if (point.state == WebTouchPoint::StatePressed)
801         touch_ack_states_[point.id] = ack_result;
802     }
803   }
804 }
805 
AllTouchAckStatesHaveState(InputEventAckState ack_state) const806 bool TouchEventQueue::AllTouchAckStatesHaveState(
807     InputEventAckState ack_state) const {
808   if (touch_ack_states_.empty())
809     return false;
810 
811   for (TouchPointAckStates::const_iterator iter = touch_ack_states_.begin(),
812                                            end = touch_ack_states_.end();
813        iter != end;
814        ++iter) {
815     if (iter->second != ack_state)
816       return false;
817   }
818 
819   return true;
820 }
821 
822 }  // namespace content
823