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