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