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
;
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
,
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
;
62 // Cancels a touch sequence if a touchstart or touchmove ack response is
63 // sufficiently delayed.
64 class TouchEventQueue::TouchTimeoutHandler
{
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();
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
);
97 SetPendingAckState(PENDING_ACK_NONE
);
98 touch_queue_
->UpdateTouchAckStates(timeout_event_
.event
, ack_result
);
101 case PENDING_ACK_CANCEL_EVENT
:
102 SetPendingAckState(PENDING_ACK_NONE
);
108 bool FilterEvent(const WebTouchEvent
& event
) {
109 return HasTimeoutEvent();
112 bool IsTimeoutTimerRunning() const {
113 return timeout_monitor_
.IsRunning();
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
;
126 enum PendingAckState
{
128 PENDING_ACK_ORIGINAL_EVENT
,
129 PENDING_ACK_CANCEL_EVENT
,
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
)
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);
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");
160 case PENDING_ACK_NONE
:
161 DCHECK(!timeout_monitor_
.IsRunning());
162 DCHECK(touch_queue_
->empty());
163 TRACE_EVENT_ASYNC_END0("input", "TouchEventTimeout", this);
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
{
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
)
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;
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
{
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
) {
250 coalesced_event_
.event
.cancelable
= false;
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())
268 if (!coalesced_event_
.CanCoalesceWith(event_with_latency
))
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
);
278 void UpdateLatencyInfoForAck(const ui::LatencyInfo
& renderer_latency_info
) {
279 if (!WillDispatchAckToClient())
282 for (WebTouchEventWithLatencyList::iterator iter
= events_to_ack_
.begin(),
283 end
= events_to_ack_
.end();
286 iter
->latency
.AddNewLatencyFrom(renderer_latency_info
);
290 void DispatchAckToClient(InputEventAckState ack_result
,
291 TouchEventQueueClient
* client
) {
293 if (!WillDispatchAckToClient())
296 for (WebTouchEventWithLatencyList::const_iterator
297 iter
= events_to_ack_
.begin(),
298 end
= events_to_ack_
.end();
301 client
->OnTouchEventAck(*iter
, ack_result
);
305 const TouchEventWithLatencyInfo
& coalesced_event() const {
306 return coalesced_event_
;
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
)
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
) {
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
);
372 // There is no touch event in the queue. Forward it to the renderer
374 touch_queue_
.push_back(new CoalescedWebTouchEvent(event
, false));
375 ForwardNextEventToRenderer();
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
))
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
))
399 touchmove_slop_suppressor_
->ConfirmTouchEvent(ack_result
);
401 if (touch_queue_
.empty())
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
);
424 case ACK_WITH_NOT_CONSUMED
:
425 PopTouchEventToClient(INPUT_EVENT_ACK_STATE_NOT_CONSUMED
);
427 case FORWARD_TO_RENDERER
:
428 ForwardNextEventToRenderer();
434 void TouchEventQueue::ForwardNextEventToRenderer() {
435 TRACE_EVENT0("input", "TouchEventQueue::ForwardNextEventToRenderer");
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
=
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
));
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();
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();
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
);
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
)
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
;
546 pending_async_touchmove_
.reset();
547 send_touch_events_async_
= true;
548 needs_async_touchmove_for_outer_slop_region_
= true;
552 if (touch_scrolling_mode_
!= TOUCH_SCROLLING_MODE_TOUCHCANCEL
)
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_
)
562 if (touch_filtering_state_
== DROP_TOUCHES_IN_SEQUENCE
)
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
)
584 if (event
.event
.type
!= blink::WebInputEvent::GestureScrollUpdate
)
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
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_
);
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
;
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())
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_
)
638 if (ack_timeout_enabled_
== enabled
)
641 ack_timeout_enabled_
= enabled
;
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
) {
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();
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
)
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
;
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())
794 for (TouchPointAckStates::const_iterator iter
= touch_ack_states_
.begin(),
795 end
= touch_ack_states_
.end();
798 if (iter
->second
!= ack_state
)
805 } // namespace content