Add ICU message format support
[chromium-blink-merge.git] / content / browser / renderer_host / input / touch_event_queue.cc
blob35b1657048ba5635cf1ffbba8c353f8687b94359
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.
5 #include "content/browser/renderer_host/input/touch_event_queue.h"
7 #include "base/auto_reset.h"
8 #include "base/metrics/histogram_macros.h"
9 #include "base/stl_util.h"
10 #include "base/trace_event/trace_event.h"
11 #include "content/browser/renderer_host/input/timeout_monitor.h"
12 #include "content/common/input/web_touch_event_traits.h"
13 #include "ui/gfx/geometry/point_f.h"
15 using blink::WebInputEvent;
16 using blink::WebTouchEvent;
17 using blink::WebTouchPoint;
18 using ui::LatencyInfo;
20 namespace content {
21 namespace {
23 // Time interval at which touchmove events will be forwarded to the client while
24 // scrolling is active and possible.
25 const double kAsyncTouchMoveIntervalSec = .2;
27 // A sanity check on touches received to ensure that touch movement outside
28 // the platform slop region will cause scrolling, as indicated by the event's
29 // |causesScrollingIfUncanceled| bit.
30 const double kMaxConceivablePlatformSlopRegionLengthDipsSquared = 60. * 60.;
32 TouchEventWithLatencyInfo ObtainCancelEventForTouchEvent(
33 const TouchEventWithLatencyInfo& event_to_cancel) {
34 TouchEventWithLatencyInfo event = event_to_cancel;
35 WebTouchEventTraits::ResetTypeAndTouchStates(
36 WebInputEvent::TouchCancel,
37 // TODO(rbyers): Shouldn't we use a fresh timestamp?
38 event.event.timeStampSeconds,
39 &event.event);
40 return event;
43 bool ShouldTouchTriggerTimeout(const WebTouchEvent& event) {
44 return (event.type == WebInputEvent::TouchStart ||
45 event.type == WebInputEvent::TouchMove) &&
46 WebInputEventTraits::WillReceiveAckFromRenderer(event) &&
47 event.cancelable;
50 // Compare all properties of touch points to determine the state.
51 bool HasPointChanged(const WebTouchPoint& point_1,
52 const WebTouchPoint& point_2) {
53 DCHECK_EQ(point_1.id, point_2.id);
54 if (point_1.screenPosition != point_2.screenPosition ||
55 point_1.position != point_2.position ||
56 point_1.radiusX != point_2.radiusX ||
57 point_1.radiusY != point_2.radiusY ||
58 point_1.rotationAngle != point_2.rotationAngle ||
59 point_1.force != point_2.force) {
60 return true;
62 return false;
65 } // namespace
68 // Cancels a touch sequence if a touchstart or touchmove ack response is
69 // sufficiently delayed.
70 class TouchEventQueue::TouchTimeoutHandler {
71 public:
72 TouchTimeoutHandler(TouchEventQueue* touch_queue,
73 base::TimeDelta desktop_timeout_delay,
74 base::TimeDelta mobile_timeout_delay)
75 : touch_queue_(touch_queue),
76 desktop_timeout_delay_(desktop_timeout_delay),
77 mobile_timeout_delay_(mobile_timeout_delay),
78 use_mobile_timeout_(false),
79 pending_ack_state_(PENDING_ACK_NONE),
80 timeout_monitor_(base::Bind(&TouchTimeoutHandler::OnTimeOut,
81 base::Unretained(this))),
82 enabled_(true),
83 enabled_for_current_sequence_(false),
84 sequence_awaiting_uma_update_(false),
85 sequence_using_mobile_timeout_(false) {
86 SetUseMobileTimeout(false);
89 ~TouchTimeoutHandler() {
90 LogSequenceEndForUMAIfNecessary(false);
93 void StartIfNecessary(const TouchEventWithLatencyInfo& event) {
94 if (pending_ack_state_ != PENDING_ACK_NONE)
95 return;
97 if (!enabled_)
98 return;
100 const base::TimeDelta timeout_delay = GetTimeoutDelay();
101 if (timeout_delay.is_zero())
102 return;
104 if (!ShouldTouchTriggerTimeout(event.event))
105 return;
107 if (WebTouchEventTraits::IsTouchSequenceStart(event.event)) {
108 LogSequenceStartForUMA();
109 enabled_for_current_sequence_ = true;
112 if (!enabled_for_current_sequence_)
113 return;
115 timeout_event_ = event;
116 timeout_monitor_.Restart(timeout_delay);
119 bool ConfirmTouchEvent(InputEventAckState ack_result) {
120 switch (pending_ack_state_) {
121 case PENDING_ACK_NONE:
122 if (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED)
123 enabled_for_current_sequence_ = false;
124 timeout_monitor_.Stop();
125 return false;
126 case PENDING_ACK_ORIGINAL_EVENT:
127 if (AckedTimeoutEventRequiresCancel(ack_result)) {
128 SetPendingAckState(PENDING_ACK_CANCEL_EVENT);
129 TouchEventWithLatencyInfo cancel_event =
130 ObtainCancelEventForTouchEvent(timeout_event_);
131 touch_queue_->SendTouchEventImmediately(&cancel_event);
132 } else {
133 SetPendingAckState(PENDING_ACK_NONE);
134 touch_queue_->UpdateTouchConsumerStates(timeout_event_.event,
135 ack_result);
137 return true;
138 case PENDING_ACK_CANCEL_EVENT:
139 SetPendingAckState(PENDING_ACK_NONE);
140 return true;
142 return false;
145 bool FilterEvent(const WebTouchEvent& event) {
146 if (!HasTimeoutEvent())
147 return false;
149 if (WebTouchEventTraits::IsTouchSequenceStart(event)) {
150 // If a new sequence is observed while we're still waiting on the
151 // timed-out sequence response, also count the new sequence as timed-out.
152 LogSequenceStartForUMA();
153 LogSequenceEndForUMAIfNecessary(true);
156 return true;
159 void SetEnabled(bool enabled) {
160 if (enabled_ == enabled)
161 return;
163 enabled_ = enabled;
165 if (enabled_)
166 return;
168 enabled_for_current_sequence_ = false;
169 // Only reset the |timeout_handler_| if the timer is running and has not
170 // yet timed out. This ensures that an already timed out sequence is
171 // properly flushed by the handler.
172 if (IsTimeoutTimerRunning()) {
173 pending_ack_state_ = PENDING_ACK_NONE;
174 timeout_monitor_.Stop();
178 void SetUseMobileTimeout(bool use_mobile_timeout) {
179 use_mobile_timeout_ = use_mobile_timeout;
182 bool IsTimeoutTimerRunning() const { return timeout_monitor_.IsRunning(); }
184 bool IsEnabled() const {
185 return enabled_ && !GetTimeoutDelay().is_zero();
188 private:
189 enum PendingAckState {
190 PENDING_ACK_NONE,
191 PENDING_ACK_ORIGINAL_EVENT,
192 PENDING_ACK_CANCEL_EVENT,
195 void OnTimeOut() {
196 LogSequenceEndForUMAIfNecessary(true);
197 SetPendingAckState(PENDING_ACK_ORIGINAL_EVENT);
198 touch_queue_->FlushQueue();
201 // Skip a cancel event if the timed-out event had no consumer and was the
202 // initial event in the gesture.
203 bool AckedTimeoutEventRequiresCancel(InputEventAckState ack_result) const {
204 DCHECK(HasTimeoutEvent());
205 if (ack_result != INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS)
206 return true;
207 return !WebTouchEventTraits::IsTouchSequenceStart(timeout_event_.event);
210 void SetPendingAckState(PendingAckState new_pending_ack_state) {
211 DCHECK_NE(pending_ack_state_, new_pending_ack_state);
212 switch (new_pending_ack_state) {
213 case PENDING_ACK_ORIGINAL_EVENT:
214 DCHECK_EQ(pending_ack_state_, PENDING_ACK_NONE);
215 TRACE_EVENT_ASYNC_BEGIN0("input", "TouchEventTimeout", this);
216 break;
217 case PENDING_ACK_CANCEL_EVENT:
218 DCHECK_EQ(pending_ack_state_, PENDING_ACK_ORIGINAL_EVENT);
219 DCHECK(!timeout_monitor_.IsRunning());
220 DCHECK(touch_queue_->empty());
221 TRACE_EVENT_ASYNC_STEP_INTO0(
222 "input", "TouchEventTimeout", this, "CancelEvent");
223 break;
224 case PENDING_ACK_NONE:
225 DCHECK(!timeout_monitor_.IsRunning());
226 DCHECK(touch_queue_->empty());
227 TRACE_EVENT_ASYNC_END0("input", "TouchEventTimeout", this);
228 break;
230 pending_ack_state_ = new_pending_ack_state;
233 void LogSequenceStartForUMA() {
234 // Always flush any unlogged entries before starting a new one.
235 LogSequenceEndForUMAIfNecessary(false);
236 sequence_awaiting_uma_update_ = true;
237 sequence_using_mobile_timeout_ = use_mobile_timeout_;
240 void LogSequenceEndForUMAIfNecessary(bool timed_out) {
241 if (!sequence_awaiting_uma_update_)
242 return;
244 sequence_awaiting_uma_update_ = false;
246 if (sequence_using_mobile_timeout_) {
247 UMA_HISTOGRAM_BOOLEAN("Event.Touch.TimedOutOnMobileSite", timed_out);
248 } else {
249 UMA_HISTOGRAM_BOOLEAN("Event.Touch.TimedOutOnDesktopSite", timed_out);
253 base::TimeDelta GetTimeoutDelay() const {
254 return use_mobile_timeout_ ? mobile_timeout_delay_ : desktop_timeout_delay_;
257 bool HasTimeoutEvent() const {
258 return pending_ack_state_ != PENDING_ACK_NONE;
262 TouchEventQueue* touch_queue_;
264 // How long to wait on a touch ack before cancelling the touch sequence.
265 const base::TimeDelta desktop_timeout_delay_;
266 const base::TimeDelta mobile_timeout_delay_;
267 bool use_mobile_timeout_;
269 // The touch event source for which we expect the next ack.
270 PendingAckState pending_ack_state_;
272 // The event for which the ack timeout is triggered.
273 TouchEventWithLatencyInfo timeout_event_;
275 // Provides timeout-based callback behavior.
276 TimeoutMonitor timeout_monitor_;
278 bool enabled_;
279 bool enabled_for_current_sequence_;
281 // Bookkeeping to classify and log whether a touch sequence times out.
282 bool sequence_awaiting_uma_update_;
283 bool sequence_using_mobile_timeout_;
286 // Provides touchmove slop suppression for a touch sequence until a
287 // (unprevented) touch will trigger immediate scrolling.
288 class TouchEventQueue::TouchMoveSlopSuppressor {
289 public:
290 TouchMoveSlopSuppressor() : suppressing_touchmoves_(false) {}
292 bool FilterEvent(const WebTouchEvent& event) {
293 if (WebTouchEventTraits::IsTouchSequenceStart(event)) {
294 suppressing_touchmoves_ = true;
295 touch_start_location_ = gfx::PointF(event.touches[0].position);
298 if (event.type == WebInputEvent::TouchEnd ||
299 event.type == WebInputEvent::TouchCancel)
300 suppressing_touchmoves_ = false;
302 if (event.type != WebInputEvent::TouchMove)
303 return false;
305 if (suppressing_touchmoves_) {
306 if (event.touchesLength > 1) {
307 suppressing_touchmoves_ = false;
308 } else if (event.causesScrollingIfUncanceled) {
309 suppressing_touchmoves_ = false;
310 } else {
311 // No sane slop region should be larger than 60 DIPs.
312 DCHECK_LT((gfx::PointF(event.touches[0].position) -
313 touch_start_location_).LengthSquared(),
314 kMaxConceivablePlatformSlopRegionLengthDipsSquared);
318 return suppressing_touchmoves_;
321 void ConfirmTouchEvent(InputEventAckState ack_result) {
322 if (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED)
323 suppressing_touchmoves_ = false;
326 bool suppressing_touchmoves() const { return suppressing_touchmoves_; }
328 private:
329 bool suppressing_touchmoves_;
331 // Sanity check that the upstream touch provider is properly reporting whether
332 // the touch sequence will cause scrolling.
333 gfx::PointF touch_start_location_;
335 DISALLOW_COPY_AND_ASSIGN(TouchMoveSlopSuppressor);
338 // This class represents a single coalesced touch event. However, it also keeps
339 // track of all the original touch-events that were coalesced into a single
340 // event. The coalesced event is forwarded to the renderer, while the original
341 // touch-events are sent to the Client (on ACK for the coalesced event) so that
342 // the Client receives the event with their original timestamp.
343 class CoalescedWebTouchEvent {
344 public:
345 CoalescedWebTouchEvent(const TouchEventWithLatencyInfo& event,
346 bool suppress_client_ack)
347 : coalesced_event_(event), suppress_client_ack_(suppress_client_ack) {
348 TRACE_EVENT_ASYNC_BEGIN0("input", "TouchEventQueue::QueueEvent", this);
351 ~CoalescedWebTouchEvent() {
352 TRACE_EVENT_ASYNC_END0("input", "TouchEventQueue::QueueEvent", this);
355 // Coalesces the event with the existing event if possible. Returns whether
356 // the event was coalesced.
357 bool CoalesceEventIfPossible(
358 const TouchEventWithLatencyInfo& event_with_latency) {
359 if (suppress_client_ack_)
360 return false;
362 if (!coalesced_event_.CanCoalesceWith(event_with_latency))
363 return false;
365 // Addition of the first event to |uncoaleseced_events_to_ack_| is deferred
366 // until the first coalesced event, optimizing the (common) case where the
367 // event is not coalesced at all.
368 if (uncoaleseced_events_to_ack_.empty())
369 uncoaleseced_events_to_ack_.push_back(coalesced_event_);
371 TRACE_EVENT_INSTANT0(
372 "input", "TouchEventQueue::MoveCoalesced", TRACE_EVENT_SCOPE_THREAD);
373 coalesced_event_.CoalesceWith(event_with_latency);
374 uncoaleseced_events_to_ack_.push_back(event_with_latency);
375 DCHECK_GE(uncoaleseced_events_to_ack_.size(), 2U);
376 return true;
379 void DispatchAckToClient(InputEventAckState ack_result,
380 const ui::LatencyInfo* optional_latency_info,
381 TouchEventQueueClient* client) {
382 DCHECK(client);
383 if (suppress_client_ack_)
384 return;
386 if (uncoaleseced_events_to_ack_.empty()) {
387 if (optional_latency_info)
388 coalesced_event_.latency.AddNewLatencyFrom(*optional_latency_info);
389 client->OnTouchEventAck(coalesced_event_, ack_result);
390 return;
393 DCHECK_GE(uncoaleseced_events_to_ack_.size(), 2U);
394 for (WebTouchEventWithLatencyList::iterator
395 iter = uncoaleseced_events_to_ack_.begin(),
396 end = uncoaleseced_events_to_ack_.end();
397 iter != end;
398 ++iter) {
399 if (optional_latency_info)
400 iter->latency.AddNewLatencyFrom(*optional_latency_info);
401 client->OnTouchEventAck(*iter, ack_result);
405 const TouchEventWithLatencyInfo& coalesced_event() const {
406 return coalesced_event_;
409 private:
410 // This is the event that is forwarded to the renderer.
411 TouchEventWithLatencyInfo coalesced_event_;
413 // This is the list of the original events that were coalesced, each requiring
414 // future ack dispatch to the client.
415 // Note that this will be empty if no coalescing has occurred.
416 typedef std::vector<TouchEventWithLatencyInfo> WebTouchEventWithLatencyList;
417 WebTouchEventWithLatencyList uncoaleseced_events_to_ack_;
419 bool suppress_client_ack_;
421 DISALLOW_COPY_AND_ASSIGN(CoalescedWebTouchEvent);
424 TouchEventQueue::Config::Config()
425 : desktop_touch_ack_timeout_delay(base::TimeDelta::FromMilliseconds(200)),
426 mobile_touch_ack_timeout_delay(base::TimeDelta::FromMilliseconds(1000)),
427 touch_ack_timeout_supported(false) {
430 TouchEventQueue::TouchEventQueue(TouchEventQueueClient* client,
431 const Config& config)
432 : client_(client),
433 dispatching_touch_ack_(false),
434 dispatching_touch_(false),
435 has_handlers_(true),
436 has_handler_for_current_sequence_(false),
437 drop_remaining_touches_in_sequence_(false),
438 touchmove_slop_suppressor_(new TouchMoveSlopSuppressor),
439 send_touch_events_async_(false),
440 last_sent_touch_timestamp_sec_(0) {
441 DCHECK(client);
442 if (config.touch_ack_timeout_supported) {
443 timeout_handler_.reset(
444 new TouchTimeoutHandler(this,
445 config.desktop_touch_ack_timeout_delay,
446 config.mobile_touch_ack_timeout_delay));
450 TouchEventQueue::~TouchEventQueue() {
451 if (!touch_queue_.empty())
452 STLDeleteElements(&touch_queue_);
455 void TouchEventQueue::QueueEvent(const TouchEventWithLatencyInfo& event) {
456 TRACE_EVENT0("input", "TouchEventQueue::QueueEvent");
458 // If the queueing of |event| was triggered by an ack dispatch, defer
459 // processing the event until the dispatch has finished.
460 if (touch_queue_.empty() && !dispatching_touch_ack_) {
461 // Optimization of the case without touch handlers. Removing this path
462 // yields identical results, but this avoids unnecessary allocations.
463 PreFilterResult filter_result = FilterBeforeForwarding(event.event);
464 if (filter_result != FORWARD_TO_RENDERER) {
465 client_->OnTouchEventAck(event,
466 filter_result == ACK_WITH_NO_CONSUMER_EXISTS
467 ? INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS
468 : INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
469 return;
472 // There is no touch event in the queue. Forward it to the renderer
473 // immediately.
474 touch_queue_.push_back(new CoalescedWebTouchEvent(event, false));
475 ForwardNextEventToRenderer();
476 return;
479 // If the last queued touch-event was a touch-move, and the current event is
480 // also a touch-move, then the events can be coalesced into a single event.
481 if (touch_queue_.size() > 1) {
482 CoalescedWebTouchEvent* last_event = touch_queue_.back();
483 if (last_event->CoalesceEventIfPossible(event))
484 return;
486 touch_queue_.push_back(new CoalescedWebTouchEvent(event, false));
489 void TouchEventQueue::ProcessTouchAck(InputEventAckState ack_result,
490 const LatencyInfo& latency_info,
491 const uint32 unique_touch_event_id) {
492 TRACE_EVENT0("input", "TouchEventQueue::ProcessTouchAck");
494 // We receive an ack for async touchmove from render.
495 if (!ack_pending_async_touchmove_ids_.empty() &&
496 ack_pending_async_touchmove_ids_.front() == unique_touch_event_id) {
497 // Remove the first touchmove from the ack_pending_async_touchmove queue.
498 ack_pending_async_touchmove_ids_.pop_front();
499 // Send the next pending async touch move once we receive all acks back.
500 if (pending_async_touchmove_ && ack_pending_async_touchmove_ids_.empty()) {
501 DCHECK(touch_queue_.empty());
503 // Dispatch the next pending async touch move when time expires.
504 if (pending_async_touchmove_->event.timeStampSeconds >=
505 last_sent_touch_timestamp_sec_ + kAsyncTouchMoveIntervalSec) {
506 FlushPendingAsyncTouchmove();
509 return;
512 DCHECK(!dispatching_touch_ack_);
513 dispatching_touch_ = false;
515 if (timeout_handler_ && timeout_handler_->ConfirmTouchEvent(ack_result))
516 return;
518 touchmove_slop_suppressor_->ConfirmTouchEvent(ack_result);
520 if (touch_queue_.empty())
521 return;
523 DCHECK_EQ(touch_queue_.front()->coalesced_event().event.uniqueTouchEventId,
524 unique_touch_event_id);
526 PopTouchEventToClient(ack_result, latency_info);
527 TryForwardNextEventToRenderer();
530 void TouchEventQueue::TryForwardNextEventToRenderer() {
531 DCHECK(!dispatching_touch_ack_);
532 // If there are queued touch events, then try to forward them to the renderer
533 // immediately, or ACK the events back to the client if appropriate.
534 while (!touch_queue_.empty()) {
535 PreFilterResult filter_result =
536 FilterBeforeForwarding(touch_queue_.front()->coalesced_event().event);
537 switch (filter_result) {
538 case ACK_WITH_NO_CONSUMER_EXISTS:
539 PopTouchEventToClient(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
540 break;
541 case ACK_WITH_NOT_CONSUMED:
542 PopTouchEventToClient(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
543 break;
544 case FORWARD_TO_RENDERER:
545 ForwardNextEventToRenderer();
546 return;
551 void TouchEventQueue::ForwardNextEventToRenderer() {
552 TRACE_EVENT0("input", "TouchEventQueue::ForwardNextEventToRenderer");
554 DCHECK(!empty());
555 DCHECK(!dispatching_touch_);
556 TouchEventWithLatencyInfo touch = touch_queue_.front()->coalesced_event();
558 if (send_touch_events_async_ &&
559 touch.event.type == WebInputEvent::TouchMove) {
560 // Throttling touchmove's in a continuous touchmove stream while scrolling
561 // reduces the risk of jank. However, it's still important that the web
562 // application be sent touches at key points in the gesture stream,
563 // e.g., when the application slop region is exceeded or touchmove
564 // coalescing fails because of different modifiers.
565 bool send_touchmove_now = size() > 1;
566 send_touchmove_now |= pending_async_touchmove_ &&
567 !pending_async_touchmove_->CanCoalesceWith(touch);
568 send_touchmove_now |=
569 ack_pending_async_touchmove_ids_.empty() &&
570 (touch.event.timeStampSeconds >=
571 last_sent_touch_timestamp_sec_ + kAsyncTouchMoveIntervalSec);
573 if (!send_touchmove_now) {
574 if (!pending_async_touchmove_) {
575 pending_async_touchmove_.reset(new TouchEventWithLatencyInfo(touch));
576 } else {
577 DCHECK(pending_async_touchmove_->CanCoalesceWith(touch));
578 pending_async_touchmove_->CoalesceWith(touch);
580 DCHECK_EQ(1U, size());
581 PopTouchEventToClient(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
582 // It's possible (though unlikely) that ack'ing the current touch will
583 // trigger the queueing of another touch event (e.g., a touchcancel). As
584 // forwarding of the queued event will be deferred while the ack is being
585 // dispatched (see |OnTouchEvent()|), try forwarding it now.
586 TryForwardNextEventToRenderer();
587 return;
591 last_sent_touch_timestamp_sec_ = touch.event.timeStampSeconds;
593 // Flush any pending async touch move. If it can be combined with the current
594 // (touchmove) event, great, otherwise send it immediately but separately. Its
595 // ack will trigger forwarding of the original |touch| event.
596 if (pending_async_touchmove_) {
597 if (pending_async_touchmove_->CanCoalesceWith(touch)) {
598 pending_async_touchmove_->CoalesceWith(touch);
599 pending_async_touchmove_->event.cancelable = !send_touch_events_async_;
600 touch = *pending_async_touchmove_;
601 pending_async_touchmove_.reset();
602 } else {
603 FlushPendingAsyncTouchmove();
604 return;
608 // Note: Touchstart events are marked cancelable to allow transitions between
609 // platform scrolling and JS pinching. Touchend events, however, remain
610 // uncancelable, mitigating the risk of jank when transitioning to a fling.
611 if (send_touch_events_async_ && touch.event.type != WebInputEvent::TouchStart)
612 touch.event.cancelable = false;
614 SendTouchEventImmediately(&touch);
617 void TouchEventQueue::FlushPendingAsyncTouchmove() {
618 DCHECK(!dispatching_touch_);
619 scoped_ptr<TouchEventWithLatencyInfo> touch = pending_async_touchmove_.Pass();
620 touch->event.cancelable = false;
621 touch_queue_.push_front(new CoalescedWebTouchEvent(*touch, true));
622 SendTouchEventImmediately(touch.get());
625 void TouchEventQueue::OnGestureScrollEvent(
626 const GestureEventWithLatencyInfo& gesture_event) {
627 if (gesture_event.event.type == blink::WebInputEvent::GestureScrollBegin) {
628 if (has_handler_for_current_sequence_ &&
629 !drop_remaining_touches_in_sequence_) {
630 DCHECK(!touchmove_slop_suppressor_->suppressing_touchmoves())
631 << "A touch handler should be offered a touchmove before scrolling.";
634 pending_async_touchmove_.reset();
636 return;
639 if (gesture_event.event.type == blink::WebInputEvent::GestureScrollUpdate)
640 send_touch_events_async_ = true;
643 void TouchEventQueue::OnGestureEventAck(
644 const GestureEventWithLatencyInfo& event,
645 InputEventAckState ack_result) {
646 // Throttle sending touchmove events as long as the scroll events are handled.
647 // Note that there's no guarantee that this ACK is for the most recent
648 // gesture event (or even part of the current sequence). Worst case, the
649 // delay in updating the absorption state will result in minor UI glitches.
650 // A valid |pending_async_touchmove_| will be flushed when the next event is
651 // forwarded.
652 if (event.event.type == blink::WebInputEvent::GestureScrollUpdate)
653 send_touch_events_async_ = (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED);
656 void TouchEventQueue::OnHasTouchEventHandlers(bool has_handlers) {
657 DCHECK(!dispatching_touch_ack_);
658 DCHECK(!dispatching_touch_);
659 has_handlers_ = has_handlers;
662 bool TouchEventQueue::IsPendingAckTouchStart() const {
663 DCHECK(!dispatching_touch_ack_);
664 if (touch_queue_.empty())
665 return false;
667 const blink::WebTouchEvent& event =
668 touch_queue_.front()->coalesced_event().event;
669 return (event.type == WebInputEvent::TouchStart);
672 void TouchEventQueue::SetAckTimeoutEnabled(bool enabled) {
673 if (timeout_handler_)
674 timeout_handler_->SetEnabled(enabled);
677 void TouchEventQueue::SetIsMobileOptimizedSite(bool mobile_optimized_site) {
678 if (timeout_handler_)
679 timeout_handler_->SetUseMobileTimeout(mobile_optimized_site);
682 bool TouchEventQueue::IsAckTimeoutEnabled() const {
683 return timeout_handler_ && timeout_handler_->IsEnabled();
686 bool TouchEventQueue::HasPendingAsyncTouchMoveForTesting() const {
687 return pending_async_touchmove_;
690 bool TouchEventQueue::IsTimeoutRunningForTesting() const {
691 return timeout_handler_ && timeout_handler_->IsTimeoutTimerRunning();
694 const TouchEventWithLatencyInfo&
695 TouchEventQueue::GetLatestEventForTesting() const {
696 return touch_queue_.back()->coalesced_event();
699 void TouchEventQueue::FlushQueue() {
700 DCHECK(!dispatching_touch_ack_);
701 DCHECK(!dispatching_touch_);
702 pending_async_touchmove_.reset();
703 drop_remaining_touches_in_sequence_ = true;
704 while (!touch_queue_.empty())
705 PopTouchEventToClient(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
708 void TouchEventQueue::PopTouchEventToClient(InputEventAckState ack_result) {
709 AckTouchEventToClient(ack_result, PopTouchEvent(), nullptr);
712 void TouchEventQueue::PopTouchEventToClient(
713 InputEventAckState ack_result,
714 const LatencyInfo& renderer_latency_info) {
715 AckTouchEventToClient(ack_result, PopTouchEvent(), &renderer_latency_info);
718 void TouchEventQueue::AckTouchEventToClient(
719 InputEventAckState ack_result,
720 scoped_ptr<CoalescedWebTouchEvent> acked_event,
721 const ui::LatencyInfo* optional_latency_info) {
722 DCHECK(acked_event);
723 DCHECK(!dispatching_touch_ack_);
724 UpdateTouchConsumerStates(acked_event->coalesced_event().event, ack_result);
726 // Note that acking the touch-event may result in multiple gestures being sent
727 // to the renderer, or touch-events being queued.
728 base::AutoReset<bool> dispatching_touch_ack(&dispatching_touch_ack_, true);
729 acked_event->DispatchAckToClient(ack_result, optional_latency_info, client_);
732 scoped_ptr<CoalescedWebTouchEvent> TouchEventQueue::PopTouchEvent() {
733 DCHECK(!touch_queue_.empty());
734 scoped_ptr<CoalescedWebTouchEvent> event(touch_queue_.front());
735 touch_queue_.pop_front();
736 return event.Pass();
739 void TouchEventQueue::SendTouchEventImmediately(
740 TouchEventWithLatencyInfo* touch) {
741 // For touchmove events, compare touch points position from current event
742 // to last sent event and update touch points state.
743 if (touch->event.type == WebInputEvent::TouchMove) {
744 CHECK(last_sent_touchevent_);
745 for (unsigned int i = 0; i < last_sent_touchevent_->touchesLength; ++i) {
746 const WebTouchPoint& last_touch_point =
747 last_sent_touchevent_->touches[i];
748 // Touches with same id may not have same index in Touches array.
749 for (unsigned int j = 0; j < touch->event.touchesLength; ++j) {
750 const WebTouchPoint& current_touchmove_point = touch->event.touches[j];
751 if (current_touchmove_point.id != last_touch_point.id)
752 continue;
754 if (!HasPointChanged(last_touch_point, current_touchmove_point))
755 touch->event.touches[j].state = WebTouchPoint::StateStationary;
757 break;
762 if (last_sent_touchevent_)
763 *last_sent_touchevent_ = touch->event;
764 else
765 last_sent_touchevent_.reset(new WebTouchEvent(touch->event));
767 base::AutoReset<bool> dispatching_touch(&dispatching_touch_, true);
769 client_->SendTouchEventImmediately(*touch);
771 // A synchronous ack will reset |dispatching_touch_|, in which case the touch
772 // timeout should not be started and the count also should not be increased.
773 if (dispatching_touch_) {
774 if (touch->event.type == WebInputEvent::TouchMove &&
775 !touch->event.cancelable) {
776 // When we send out a uncancelable touch move, we increase the count and
777 // we do not process input event ack any more, we will just ack to client
778 // and wait for the ack from render. Also we will remove it from the front
779 // of the queue.
780 ack_pending_async_touchmove_ids_.push_back(
781 touch->event.uniqueTouchEventId);
782 dispatching_touch_ = false;
783 PopTouchEventToClient(INPUT_EVENT_ACK_STATE_IGNORED);
784 TryForwardNextEventToRenderer();
785 return;
788 if (timeout_handler_)
789 timeout_handler_->StartIfNecessary(*touch);
793 TouchEventQueue::PreFilterResult
794 TouchEventQueue::FilterBeforeForwarding(const WebTouchEvent& event) {
795 if (WebTouchEventTraits::IsTouchSequenceStart(event)) {
796 has_handler_for_current_sequence_ = false;
797 send_touch_events_async_ = false;
798 pending_async_touchmove_.reset();
799 last_sent_touchevent_.reset();
801 touch_sequence_start_position_ = gfx::PointF(event.touches[0].position);
802 drop_remaining_touches_in_sequence_ = false;
803 if (!has_handlers_) {
804 drop_remaining_touches_in_sequence_ = true;
805 return ACK_WITH_NO_CONSUMER_EXISTS;
809 if (timeout_handler_ && timeout_handler_->FilterEvent(event))
810 return ACK_WITH_NO_CONSUMER_EXISTS;
812 if (touchmove_slop_suppressor_->FilterEvent(event))
813 return ACK_WITH_NOT_CONSUMED;
815 if (drop_remaining_touches_in_sequence_ &&
816 event.type != WebInputEvent::TouchCancel) {
817 return ACK_WITH_NO_CONSUMER_EXISTS;
820 if (event.type == WebInputEvent::TouchStart) {
821 return (has_handlers_ || has_handler_for_current_sequence_)
822 ? FORWARD_TO_RENDERER
823 : ACK_WITH_NO_CONSUMER_EXISTS;
826 if (has_handler_for_current_sequence_) {
827 // Only forward a touch if it has a non-stationary pointer that is active
828 // in the current touch sequence.
829 for (size_t i = 0; i < event.touchesLength; ++i) {
830 const WebTouchPoint& point = event.touches[i];
831 if (point.state == WebTouchPoint::StateStationary)
832 continue;
834 // |last_sent_touchevent_| will be non-null as long as there is an
835 // active touch sequence being forwarded to the renderer.
836 if (!last_sent_touchevent_)
837 continue;
839 for (size_t j = 0; j < last_sent_touchevent_->touchesLength; ++j) {
840 if (point.id != last_sent_touchevent_->touches[j].id)
841 continue;
843 if (event.type != WebInputEvent::TouchMove)
844 return FORWARD_TO_RENDERER;
846 // All pointers in TouchMove events may have state as StateMoved,
847 // even though none of the pointers have not changed in real.
848 // Forward these events when at least one pointer has changed.
849 if (HasPointChanged(last_sent_touchevent_->touches[j], point))
850 return FORWARD_TO_RENDERER;
852 // This is a TouchMove event for which we have yet to find a
853 // non-stationary pointer. Continue checking the next pointers
854 // in the |event|.
855 break;
861 return ACK_WITH_NO_CONSUMER_EXISTS;
864 void TouchEventQueue::UpdateTouchConsumerStates(const WebTouchEvent& event,
865 InputEventAckState ack_result) {
866 if (event.type == WebInputEvent::TouchStart) {
867 if (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED)
868 send_touch_events_async_ = false;
869 has_handler_for_current_sequence_ |=
870 ack_result != INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS;
871 } else if (WebTouchEventTraits::IsTouchSequenceEnd(event)) {
872 has_handler_for_current_sequence_ = false;
876 } // namespace content