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