Revert 268405 "Make sure that ScratchBuffer::Allocate() always r..."
[chromium-blink-merge.git] / content / browser / renderer_host / input / touch_event_queue.cc
blob187728134120ad9edc378fe6fb60347a9a123535
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/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"
14 using blink::WebInputEvent;
15 using blink::WebTouchEvent;
16 using blink::WebTouchPoint;
17 using ui::LatencyInfo;
19 namespace content {
20 namespace {
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;
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.;
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;
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;
47 bool ShouldTouchTriggerTimeout(const WebTouchEvent& event) {
48 return (event.type == WebInputEvent::TouchStart ||
49 event.type == WebInputEvent::TouchMove) &&
50 !WebInputEventTraits::IgnoresAckDisposition(event);
53 bool OutsideApplicationSlopRegion(const WebTouchEvent& event,
54 const gfx::PointF& anchor) {
55 return (gfx::PointF(event.touches[0].position) - anchor).LengthSquared() >
56 kApplicationSlopRegionLengthDipsSqared;
59 } // namespace
62 // Cancels a touch sequence if a touchstart or touchmove ack response is
63 // sufficiently delayed.
64 class TouchEventQueue::TouchTimeoutHandler {
65 public:
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());
76 ~TouchTimeoutHandler() {}
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_);
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);
100 return true;
101 case PENDING_ACK_CANCEL_EVENT:
102 SetPendingAckState(PENDING_ACK_NONE);
103 return true;
105 return false;
108 bool FilterEvent(const WebTouchEvent& event) {
109 return HasTimeoutEvent();
112 bool IsTimeoutTimerRunning() const {
113 return timeout_monitor_.IsRunning();
116 void Reset() {
117 pending_ack_state_ = PENDING_ACK_NONE;
118 timeout_monitor_.Stop();
121 void set_timeout_delay(base::TimeDelta timeout_delay) {
122 timeout_delay_ = timeout_delay;
125 private:
126 enum PendingAckState {
127 PENDING_ACK_NONE,
128 PENDING_ACK_ORIGINAL_EVENT,
129 PENDING_ACK_CANCEL_EVENT,
132 void OnTimeOut() {
133 SetPendingAckState(PENDING_ACK_ORIGINAL_EVENT);
134 touch_queue_->FlushQueue();
137 // Skip a cancel event if the timed-out event had no consumer and was the
138 // initial event in the gesture.
139 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);
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;
166 pending_ack_state_ = new_pending_ack_state;
169 bool HasTimeoutEvent() const {
170 return pending_ack_state_ != PENDING_ACK_NONE;
174 TouchEventQueue* touch_queue_;
176 // How long to wait on a touch ack before cancelling the touch sequence.
177 base::TimeDelta timeout_delay_;
179 // The touch event source for which we expect the next ack.
180 PendingAckState pending_ack_state_;
182 // The event for which the ack timeout is triggered.
183 TouchEventWithLatencyInfo timeout_event_;
185 // Provides timeout-based callback behavior.
186 TimeoutMonitor timeout_monitor_;
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:
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) {}
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;
207 if (event.type != WebInputEvent::TouchMove)
208 return false;
210 if (suppressing_touchmoves_) {
211 // Movement with a secondary pointer should terminate suppression.
212 if (event.touchesLength > 1) {
213 suppressing_touchmoves_ = false;
214 } else if (event.touchesLength == 1) {
215 // Movement outside of the slop region should terminate suppression.
216 gfx::PointF position(event.touches[0].position);
217 if ((position - touch_sequence_start_position_).LengthSquared() >
218 slop_suppression_length_dips_squared_)
219 suppressing_touchmoves_ = false;
222 return suppressing_touchmoves_;
225 void ConfirmTouchEvent(InputEventAckState ack_result) {
226 if (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED)
227 suppressing_touchmoves_ = false;
230 private:
231 double slop_suppression_length_dips_squared_;
232 gfx::PointF touch_sequence_start_position_;
233 bool suppressing_touchmoves_;
235 DISALLOW_COPY_AND_ASSIGN(TouchMoveSlopSuppressor);
238 // This class represents a single coalesced touch event. However, it also keeps
239 // track of all the original touch-events that were coalesced into a single
240 // event. The coalesced event is forwarded to the renderer, while the original
241 // touch-events are sent to the Client (on ACK for the coalesced event) so that
242 // the Client receives the event with their original timestamp.
243 class CoalescedWebTouchEvent {
244 public:
245 // Events for which |async| is true will not be ack'ed to the client after the
246 // corresponding ack is received following dispatch.
247 CoalescedWebTouchEvent(const TouchEventWithLatencyInfo& event, bool async)
248 : coalesced_event_(event) {
249 if (async)
250 coalesced_event_.event.cancelable = false;
251 else
252 events_to_ack_.push_back(event);
254 TRACE_EVENT_ASYNC_BEGIN0("input", "TouchEventQueue::QueueEvent", this);
257 ~CoalescedWebTouchEvent() {
258 TRACE_EVENT_ASYNC_END0("input", "TouchEventQueue::QueueEvent", this);
261 // Coalesces the event with the existing event if possible. Returns whether
262 // the event was coalesced.
263 bool CoalesceEventIfPossible(
264 const TouchEventWithLatencyInfo& event_with_latency) {
265 if (!WillDispatchAckToClient())
266 return false;
268 if (!coalesced_event_.CanCoalesceWith(event_with_latency))
269 return false;
271 TRACE_EVENT_INSTANT0(
272 "input", "TouchEventQueue::MoveCoalesced", TRACE_EVENT_SCOPE_THREAD);
273 coalesced_event_.CoalesceWith(event_with_latency);
274 events_to_ack_.push_back(event_with_latency);
275 return true;
278 void UpdateLatencyInfoForAck(const ui::LatencyInfo& renderer_latency_info) {
279 if (!WillDispatchAckToClient())
280 return;
282 for (WebTouchEventWithLatencyList::iterator iter = events_to_ack_.begin(),
283 end = events_to_ack_.end();
284 iter != end;
285 ++iter) {
286 iter->latency.AddNewLatencyFrom(renderer_latency_info);
290 void DispatchAckToClient(InputEventAckState ack_result,
291 TouchEventQueueClient* client) {
292 DCHECK(client);
293 if (!WillDispatchAckToClient())
294 return;
296 for (WebTouchEventWithLatencyList::const_iterator
297 iter = events_to_ack_.begin(),
298 end = events_to_ack_.end();
299 iter != end;
300 ++iter) {
301 client->OnTouchEventAck(*iter, ack_result);
305 const TouchEventWithLatencyInfo& coalesced_event() const {
306 return coalesced_event_;
309 private:
310 bool WillDispatchAckToClient() const { return !events_to_ack_.empty(); }
312 // This is the event that is forwarded to the renderer.
313 TouchEventWithLatencyInfo coalesced_event_;
315 // This is the list of the original events that were coalesced, each requiring
316 // future ack dispatch to the client.
317 typedef std::vector<TouchEventWithLatencyInfo> WebTouchEventWithLatencyList;
318 WebTouchEventWithLatencyList events_to_ack_;
320 DISALLOW_COPY_AND_ASSIGN(CoalescedWebTouchEvent);
323 TouchEventQueue::Config::Config()
324 : touchmove_slop_suppression_length_dips(0),
325 touch_scrolling_mode(TOUCH_SCROLLING_MODE_DEFAULT),
326 touch_ack_timeout_delay(base::TimeDelta::FromMilliseconds(200)),
327 touch_ack_timeout_supported(false) {
330 TouchEventQueue::TouchEventQueue(TouchEventQueueClient* client,
331 const Config& config)
332 : client_(client),
333 dispatching_touch_ack_(NULL),
334 dispatching_touch_(false),
335 touch_filtering_state_(TOUCH_FILTERING_STATE_DEFAULT),
336 ack_timeout_enabled_(config.touch_ack_timeout_supported),
337 touchmove_slop_suppressor_(new TouchMoveSlopSuppressor(
338 config.touchmove_slop_suppression_length_dips + kSlopEpsilon)),
339 send_touch_events_async_(false),
340 needs_async_touchmove_for_outer_slop_region_(false),
341 last_sent_touch_timestamp_sec_(0),
342 touch_scrolling_mode_(config.touch_scrolling_mode) {
343 DCHECK(client);
344 if (ack_timeout_enabled_) {
345 timeout_handler_.reset(
346 new TouchTimeoutHandler(this, config.touch_ack_timeout_delay));
350 TouchEventQueue::~TouchEventQueue() {
351 if (!touch_queue_.empty())
352 STLDeleteElements(&touch_queue_);
355 void TouchEventQueue::QueueEvent(const TouchEventWithLatencyInfo& event) {
356 TRACE_EVENT0("input", "TouchEventQueue::QueueEvent");
358 // If the queueing of |event| was triggered by an ack dispatch, defer
359 // processing the event until the dispatch has finished.
360 if (touch_queue_.empty() && !dispatching_touch_ack_) {
361 // Optimization of the case without touch handlers. Removing this path
362 // yields identical results, but this avoids unnecessary allocations.
363 PreFilterResult filter_result = FilterBeforeForwarding(event.event);
364 if (filter_result != FORWARD_TO_RENDERER) {
365 client_->OnTouchEventAck(event,
366 filter_result == ACK_WITH_NO_CONSUMER_EXISTS
367 ? INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS
368 : INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
369 return;
372 // There is no touch event in the queue. Forward it to the renderer
373 // immediately.
374 touch_queue_.push_back(new CoalescedWebTouchEvent(event, false));
375 ForwardNextEventToRenderer();
376 return;
379 // If the last queued touch-event was a touch-move, and the current event is
380 // also a touch-move, then the events can be coalesced into a single event.
381 if (touch_queue_.size() > 1) {
382 CoalescedWebTouchEvent* last_event = touch_queue_.back();
383 if (last_event->CoalesceEventIfPossible(event))
384 return;
386 touch_queue_.push_back(new CoalescedWebTouchEvent(event, false));
389 void TouchEventQueue::ProcessTouchAck(InputEventAckState ack_result,
390 const LatencyInfo& latency_info) {
391 TRACE_EVENT0("input", "TouchEventQueue::ProcessTouchAck");
393 DCHECK(!dispatching_touch_ack_);
394 dispatching_touch_ = false;
396 if (timeout_handler_ && timeout_handler_->ConfirmTouchEvent(ack_result))
397 return;
399 touchmove_slop_suppressor_->ConfirmTouchEvent(ack_result);
401 if (touch_queue_.empty())
402 return;
404 if (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED &&
405 touch_filtering_state_ == FORWARD_TOUCHES_UNTIL_TIMEOUT) {
406 touch_filtering_state_ = FORWARD_ALL_TOUCHES;
409 PopTouchEventToClient(ack_result, latency_info);
410 TryForwardNextEventToRenderer();
413 void TouchEventQueue::TryForwardNextEventToRenderer() {
414 DCHECK(!dispatching_touch_ack_);
415 // If there are queued touch events, then try to forward them to the renderer
416 // immediately, or ACK the events back to the client if appropriate.
417 while (!touch_queue_.empty()) {
418 PreFilterResult filter_result =
419 FilterBeforeForwarding(touch_queue_.front()->coalesced_event().event);
420 switch (filter_result) {
421 case ACK_WITH_NO_CONSUMER_EXISTS:
422 PopTouchEventToClient(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
423 break;
424 case ACK_WITH_NOT_CONSUMED:
425 PopTouchEventToClient(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
426 break;
427 case FORWARD_TO_RENDERER:
428 ForwardNextEventToRenderer();
429 return;
434 void TouchEventQueue::ForwardNextEventToRenderer() {
435 TRACE_EVENT0("input", "TouchEventQueue::ForwardNextEventToRenderer");
437 DCHECK(!empty());
438 DCHECK(!dispatching_touch_);
439 DCHECK_NE(touch_filtering_state_, DROP_ALL_TOUCHES);
440 TouchEventWithLatencyInfo touch = touch_queue_.front()->coalesced_event();
442 if (WebTouchEventTraits::IsTouchSequenceStart(touch.event)) {
443 touch_filtering_state_ =
444 ack_timeout_enabled_ ? FORWARD_TOUCHES_UNTIL_TIMEOUT
445 : FORWARD_ALL_TOUCHES;
446 touch_ack_states_.clear();
447 send_touch_events_async_ = false;
448 touch_sequence_start_position_ =
449 gfx::PointF(touch.event.touches[0].position);
452 if (send_touch_events_async_ &&
453 touch.event.type == WebInputEvent::TouchMove) {
454 // Throttling touchmove's in a continuous touchmove stream while scrolling
455 // reduces the risk of jank. However, it's still important that the web
456 // application be sent touches at key points in the gesture stream,
457 // e.g., when the application slop region is exceeded or touchmove
458 // coalescing fails because of different modifiers.
459 const bool send_touchmove_now =
460 size() > 1 ||
461 (touch.event.timeStampSeconds >=
462 last_sent_touch_timestamp_sec_ + kAsyncTouchMoveIntervalSec) ||
463 (needs_async_touchmove_for_outer_slop_region_ &&
464 OutsideApplicationSlopRegion(touch.event,
465 touch_sequence_start_position_)) ||
466 (pending_async_touchmove_ &&
467 !pending_async_touchmove_->CanCoalesceWith(touch));
469 if (!send_touchmove_now) {
470 if (!pending_async_touchmove_) {
471 pending_async_touchmove_.reset(new TouchEventWithLatencyInfo(touch));
472 } else {
473 DCHECK(pending_async_touchmove_->CanCoalesceWith(touch));
474 pending_async_touchmove_->CoalesceWith(touch);
476 DCHECK_EQ(1U, size());
477 PopTouchEventToClient(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
478 // It's possible (though unlikely) that ack'ing the current touch will
479 // trigger the queueing of another touch event (e.g., a touchcancel). As
480 // forwarding of the queued event will be deferred while the ack is being
481 // dispatched (see |OnTouchEvent()|), try forwarding it now.
482 TryForwardNextEventToRenderer();
483 return;
487 last_sent_touch_timestamp_sec_ = touch.event.timeStampSeconds;
489 // Flush any pending async touch move. If it can be combined with the current
490 // (touchmove) event, great, otherwise send it immediately but separately. Its
491 // ack will trigger forwarding of the original |touch| event.
492 if (pending_async_touchmove_) {
493 if (pending_async_touchmove_->CanCoalesceWith(touch)) {
494 pending_async_touchmove_->CoalesceWith(touch);
495 pending_async_touchmove_->event.cancelable = !send_touch_events_async_;
496 touch = *pending_async_touchmove_.Pass();
497 } else {
498 scoped_ptr<TouchEventWithLatencyInfo> async_move =
499 pending_async_touchmove_.Pass();
500 async_move->event.cancelable = false;
501 touch_queue_.push_front(new CoalescedWebTouchEvent(*async_move, true));
502 SendTouchEventImmediately(*async_move);
503 return;
507 // Note: Marking touchstart events as not-cancelable prevents them from
508 // blocking subsequent gestures, but it may not be the best long term solution
509 // for tracking touch point dispatch.
510 if (send_touch_events_async_)
511 touch.event.cancelable = false;
513 // A synchronous ack will reset |dispatching_touch_|, in which case
514 // the touch timeout should not be started.
515 base::AutoReset<bool> dispatching_touch(&dispatching_touch_, true);
516 SendTouchEventImmediately(touch);
517 if (dispatching_touch_ &&
518 touch_filtering_state_ == FORWARD_TOUCHES_UNTIL_TIMEOUT &&
519 ShouldTouchTriggerTimeout(touch.event)) {
520 DCHECK(timeout_handler_);
521 timeout_handler_->Start(touch);
525 void TouchEventQueue::OnGestureScrollEvent(
526 const GestureEventWithLatencyInfo& gesture_event) {
527 if (gesture_event.event.type != blink::WebInputEvent::GestureScrollBegin)
528 return;
530 if (touch_scrolling_mode_ == TOUCH_SCROLLING_MODE_ASYNC_TOUCHMOVE) {
531 if (touch_filtering_state_ != DROP_ALL_TOUCHES &&
532 touch_filtering_state_ != DROP_TOUCHES_IN_SEQUENCE) {
533 // If no touch points have a consumer, prevent all subsequent touch events
534 // received during the scroll from reaching the renderer. This ensures
535 // that the first touchstart the renderer sees in any given sequence can
536 // always be preventDefault'ed (cancelable == true).
537 // TODO(jdduke): Revisit if touchstarts during scroll are made cancelable.
538 if (touch_ack_states_.empty() ||
539 AllTouchAckStatesHaveState(
540 INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS)) {
541 touch_filtering_state_ = DROP_TOUCHES_IN_SEQUENCE;
542 return;
546 pending_async_touchmove_.reset();
547 send_touch_events_async_ = true;
548 needs_async_touchmove_for_outer_slop_region_ = true;
549 return;
552 if (touch_scrolling_mode_ != TOUCH_SCROLLING_MODE_TOUCHCANCEL)
553 return;
555 // We assume that scroll events are generated synchronously from
556 // dispatching a touch event ack. This allows us to generate a synthetic
557 // cancel event that has the same touch ids as the touch event that
558 // is being acked. Otherwise, we don't perform the touch-cancel optimization.
559 if (!dispatching_touch_ack_)
560 return;
562 if (touch_filtering_state_ == DROP_TOUCHES_IN_SEQUENCE)
563 return;
565 touch_filtering_state_ = DROP_TOUCHES_IN_SEQUENCE;
567 // Fake a TouchCancel to cancel the touch points of the touch event
568 // that is currently being acked.
569 // Note: |dispatching_touch_ack_| is non-null when we reach here, meaning we
570 // are in the scope of PopTouchEventToClient() and that no touch event
571 // in the queue is waiting for ack from renderer. So we can just insert
572 // the touch cancel at the beginning of the queue.
573 touch_queue_.push_front(new CoalescedWebTouchEvent(
574 ObtainCancelEventForTouchEvent(
575 dispatching_touch_ack_->coalesced_event()), true));
578 void TouchEventQueue::OnGestureEventAck(
579 const GestureEventWithLatencyInfo& event,
580 InputEventAckState ack_result) {
581 if (touch_scrolling_mode_ != TOUCH_SCROLLING_MODE_ASYNC_TOUCHMOVE)
582 return;
584 if (event.event.type != blink::WebInputEvent::GestureScrollUpdate)
585 return;
587 // Throttle sending touchmove events as long as the scroll events are handled.
588 // Note that there's no guarantee that this ACK is for the most recent
589 // gesture event (or even part of the current sequence). Worst case, the
590 // delay in updating the absorption state will result in minor UI glitches.
591 // A valid |pending_async_touchmove_| will be flushed when the next event is
592 // forwarded.
593 send_touch_events_async_ = (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED);
594 if (!send_touch_events_async_)
595 needs_async_touchmove_for_outer_slop_region_ = false;
598 void TouchEventQueue::OnHasTouchEventHandlers(bool has_handlers) {
599 DCHECK(!dispatching_touch_ack_);
600 DCHECK(!dispatching_touch_);
602 if (has_handlers) {
603 if (touch_filtering_state_ == DROP_ALL_TOUCHES) {
604 // If no touch handler was previously registered, ensure that we don't
605 // send a partial touch sequence to the renderer.
606 DCHECK(touch_queue_.empty());
607 touch_filtering_state_ = DROP_TOUCHES_IN_SEQUENCE;
609 } else {
610 // TODO(jdduke): Synthesize a TouchCancel if necessary to update Blink touch
611 // state tracking (e.g., if the touch handler was removed mid-sequence).
612 touch_filtering_state_ = DROP_ALL_TOUCHES;
613 pending_async_touchmove_.reset();
614 if (timeout_handler_)
615 timeout_handler_->Reset();
616 if (!touch_queue_.empty())
617 ProcessTouchAck(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS, LatencyInfo());
618 // As there is no touch handler, ack'ing the event should flush the queue.
619 DCHECK(touch_queue_.empty());
623 bool TouchEventQueue::IsPendingAckTouchStart() const {
624 DCHECK(!dispatching_touch_ack_);
625 if (touch_queue_.empty())
626 return false;
628 const blink::WebTouchEvent& event =
629 touch_queue_.front()->coalesced_event().event;
630 return (event.type == WebInputEvent::TouchStart);
633 void TouchEventQueue::SetAckTimeoutEnabled(bool enabled) {
634 // The timeout handler is valid only if explicitly supported in the config.
635 if (!timeout_handler_)
636 return;
638 if (ack_timeout_enabled_ == enabled)
639 return;
641 ack_timeout_enabled_ = enabled;
643 if (enabled)
644 return;
646 if (touch_filtering_state_ == FORWARD_TOUCHES_UNTIL_TIMEOUT)
647 touch_filtering_state_ = FORWARD_ALL_TOUCHES;
648 // Only reset the |timeout_handler_| if the timer is running and has not yet
649 // timed out. This ensures that an already timed out sequence is properly
650 // flushed by the handler.
651 if (timeout_handler_ && timeout_handler_->IsTimeoutTimerRunning())
652 timeout_handler_->Reset();
655 bool TouchEventQueue::HasPendingAsyncTouchMoveForTesting() const {
656 return pending_async_touchmove_;
659 bool TouchEventQueue::IsTimeoutRunningForTesting() const {
660 return timeout_handler_ && timeout_handler_->IsTimeoutTimerRunning();
663 const TouchEventWithLatencyInfo&
664 TouchEventQueue::GetLatestEventForTesting() const {
665 return touch_queue_.back()->coalesced_event();
668 void TouchEventQueue::FlushQueue() {
669 DCHECK(!dispatching_touch_ack_);
670 DCHECK(!dispatching_touch_);
671 pending_async_touchmove_.reset();
672 if (touch_filtering_state_ != DROP_ALL_TOUCHES)
673 touch_filtering_state_ = DROP_TOUCHES_IN_SEQUENCE;
674 while (!touch_queue_.empty())
675 PopTouchEventToClient(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
678 void TouchEventQueue::PopTouchEventToClient(InputEventAckState ack_result) {
679 AckTouchEventToClient(ack_result, PopTouchEvent());
682 void TouchEventQueue::PopTouchEventToClient(
683 InputEventAckState ack_result,
684 const LatencyInfo& renderer_latency_info) {
685 scoped_ptr<CoalescedWebTouchEvent> acked_event = PopTouchEvent();
686 acked_event->UpdateLatencyInfoForAck(renderer_latency_info);
687 AckTouchEventToClient(ack_result, acked_event.Pass());
690 void TouchEventQueue::AckTouchEventToClient(
691 InputEventAckState ack_result,
692 scoped_ptr<CoalescedWebTouchEvent> acked_event) {
693 DCHECK(acked_event);
694 DCHECK(!dispatching_touch_ack_);
695 UpdateTouchAckStates(acked_event->coalesced_event().event, ack_result);
697 // Note that acking the touch-event may result in multiple gestures being sent
698 // to the renderer, or touch-events being queued.
699 base::AutoReset<const CoalescedWebTouchEvent*> dispatching_touch_ack(
700 &dispatching_touch_ack_, acked_event.get());
701 acked_event->DispatchAckToClient(ack_result, client_);
704 scoped_ptr<CoalescedWebTouchEvent> TouchEventQueue::PopTouchEvent() {
705 DCHECK(!touch_queue_.empty());
706 scoped_ptr<CoalescedWebTouchEvent> event(touch_queue_.front());
707 touch_queue_.pop_front();
708 return event.Pass();
711 void TouchEventQueue::SendTouchEventImmediately(
712 const TouchEventWithLatencyInfo& touch) {
713 if (needs_async_touchmove_for_outer_slop_region_) {
714 // Any event other than a touchmove (e.g., touchcancel or secondary
715 // touchstart) after a scroll has started will interrupt the need to send a
716 // an outer slop-region exceeding touchmove.
717 if (touch.event.type != WebInputEvent::TouchMove ||
718 OutsideApplicationSlopRegion(touch.event,
719 touch_sequence_start_position_))
720 needs_async_touchmove_for_outer_slop_region_ = false;
723 client_->SendTouchEventImmediately(touch);
726 TouchEventQueue::PreFilterResult
727 TouchEventQueue::FilterBeforeForwarding(const WebTouchEvent& event) {
728 if (timeout_handler_ && timeout_handler_->FilterEvent(event))
729 return ACK_WITH_NO_CONSUMER_EXISTS;
731 if (touchmove_slop_suppressor_->FilterEvent(event))
732 return ACK_WITH_NOT_CONSUMED;
734 if (touch_filtering_state_ == DROP_ALL_TOUCHES)
735 return ACK_WITH_NO_CONSUMER_EXISTS;
737 if (touch_filtering_state_ == DROP_TOUCHES_IN_SEQUENCE &&
738 event.type != WebInputEvent::TouchCancel) {
739 if (WebTouchEventTraits::IsTouchSequenceStart(event))
740 return FORWARD_TO_RENDERER;
741 return ACK_WITH_NO_CONSUMER_EXISTS;
744 // Touch press events should always be forwarded to the renderer.
745 if (event.type == WebInputEvent::TouchStart)
746 return FORWARD_TO_RENDERER;
748 for (unsigned int i = 0; i < event.touchesLength; ++i) {
749 const WebTouchPoint& point = event.touches[i];
750 // If a point has been stationary, then don't take it into account.
751 if (point.state == WebTouchPoint::StateStationary)
752 continue;
754 if (touch_ack_states_.count(point.id) > 0) {
755 if (touch_ack_states_.find(point.id)->second !=
756 INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS)
757 return FORWARD_TO_RENDERER;
758 } else {
759 // If the ACK status of a point is unknown, then the event should be
760 // forwarded to the renderer.
761 return FORWARD_TO_RENDERER;
765 return ACK_WITH_NO_CONSUMER_EXISTS;
768 void TouchEventQueue::UpdateTouchAckStates(const WebTouchEvent& event,
769 InputEventAckState ack_result) {
770 // Update the ACK status for each touch point in the ACKed event.
771 if (event.type == WebInputEvent::TouchEnd ||
772 event.type == WebInputEvent::TouchCancel) {
773 // The points have been released. Erase the ACK states.
774 for (unsigned i = 0; i < event.touchesLength; ++i) {
775 const WebTouchPoint& point = event.touches[i];
776 if (point.state == WebTouchPoint::StateReleased ||
777 point.state == WebTouchPoint::StateCancelled)
778 touch_ack_states_.erase(point.id);
780 } else if (event.type == WebInputEvent::TouchStart) {
781 for (unsigned i = 0; i < event.touchesLength; ++i) {
782 const WebTouchPoint& point = event.touches[i];
783 if (point.state == WebTouchPoint::StatePressed)
784 touch_ack_states_[point.id] = ack_result;
789 bool TouchEventQueue::AllTouchAckStatesHaveState(
790 InputEventAckState ack_state) const {
791 if (touch_ack_states_.empty())
792 return false;
794 for (TouchPointAckStates::const_iterator iter = touch_ack_states_.begin(),
795 end = touch_ack_states_.end();
796 iter != end;
797 ++iter) {
798 if (iter->second != ack_state)
799 return false;
802 return true;
805 } // namespace content