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/command_line.h"
9 #include "base/debug/trace_event.h"
10 #include "base/stl_util.h"
11 #include "content/browser/renderer_host/input/timeout_monitor.h"
12 #include "content/browser/renderer_host/input/web_touch_event_traits.h"
13 #include "content/public/common/content_switches.h"
14 #include "ui/gfx/geometry/point_f.h"
16 using blink::WebInputEvent
;
17 using blink::WebTouchEvent
;
18 using blink::WebTouchPoint
;
19 using ui::LatencyInfo
;
24 typedef std::vector
<TouchEventWithLatencyInfo
> WebTouchEventWithLatencyList
;
26 TouchEventWithLatencyInfo
ObtainCancelEventForTouchEvent(
27 const TouchEventWithLatencyInfo
& event_to_cancel
) {
28 TouchEventWithLatencyInfo event
= event_to_cancel
;
29 event
.event
.type
= WebInputEvent::TouchCancel
;
30 for (size_t i
= 0; i
< event
.event
.touchesLength
; i
++)
31 event
.event
.touches
[i
].state
= WebTouchPoint::StateCancelled
;
35 bool ShouldTouchTypeTriggerTimeout(WebInputEvent::Type type
) {
36 return type
== WebInputEvent::TouchStart
||
37 type
== WebInputEvent::TouchMove
;
43 // Cancels a touch sequence if a touchstart or touchmove ack response is
44 // sufficiently delayed.
45 class TouchEventQueue::TouchTimeoutHandler
{
47 TouchTimeoutHandler(TouchEventQueue
* touch_queue
, size_t timeout_delay_ms
)
48 : touch_queue_(touch_queue
),
49 timeout_delay_(base::TimeDelta::FromMilliseconds(timeout_delay_ms
)),
50 pending_ack_state_(PENDING_ACK_NONE
),
51 timeout_monitor_(base::Bind(&TouchTimeoutHandler::OnTimeOut
,
52 base::Unretained(this))) {}
54 ~TouchTimeoutHandler() {}
56 void Start(const TouchEventWithLatencyInfo
& event
) {
57 DCHECK_EQ(pending_ack_state_
, PENDING_ACK_NONE
);
58 DCHECK(ShouldTouchTypeTriggerTimeout(event
.event
.type
));
59 timeout_event_
= event
;
60 timeout_monitor_
.Restart(timeout_delay_
);
63 bool ConfirmTouchEvent(InputEventAckState ack_result
) {
64 switch (pending_ack_state_
) {
65 case PENDING_ACK_NONE
:
66 timeout_monitor_
.Stop();
68 case PENDING_ACK_ORIGINAL_EVENT
:
69 if (AckedTimeoutEventRequiresCancel(ack_result
)) {
70 SetPendingAckState(PENDING_ACK_CANCEL_EVENT
);
71 TouchEventWithLatencyInfo cancel_event
=
72 ObtainCancelEventForTouchEvent(timeout_event_
);
73 touch_queue_
->client_
->SendTouchEventImmediately(cancel_event
);
75 SetPendingAckState(PENDING_ACK_NONE
);
76 touch_queue_
->UpdateTouchAckStates(timeout_event_
.event
, ack_result
);
79 case PENDING_ACK_CANCEL_EVENT
:
80 SetPendingAckState(PENDING_ACK_NONE
);
86 bool FilterEvent(const WebTouchEvent
& event
) {
87 return HasTimeoutEvent();
90 bool IsTimeoutTimerRunning() const {
91 return timeout_monitor_
.IsRunning();
95 pending_ack_state_
= PENDING_ACK_NONE
;
96 timeout_monitor_
.Stop();
100 enum PendingAckState
{
102 PENDING_ACK_ORIGINAL_EVENT
,
103 PENDING_ACK_CANCEL_EVENT
,
107 SetPendingAckState(PENDING_ACK_ORIGINAL_EVENT
);
108 touch_queue_
->FlushQueue();
111 // Skip a cancel event if the timed-out event had no consumer and was the
112 // initial event in the gesture.
113 bool AckedTimeoutEventRequiresCancel(InputEventAckState ack_result
) const {
114 DCHECK(HasTimeoutEvent());
115 if (ack_result
!= INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS
)
117 return !WebTouchEventTraits::IsTouchSequenceStart(timeout_event_
.event
);
120 void SetPendingAckState(PendingAckState new_pending_ack_state
) {
121 DCHECK_NE(pending_ack_state_
, new_pending_ack_state
);
122 switch (new_pending_ack_state
) {
123 case PENDING_ACK_ORIGINAL_EVENT
:
124 DCHECK_EQ(pending_ack_state_
, PENDING_ACK_NONE
);
125 TRACE_EVENT_ASYNC_BEGIN0("input", "TouchEventTimeout", this);
127 case PENDING_ACK_CANCEL_EVENT
:
128 DCHECK_EQ(pending_ack_state_
, PENDING_ACK_ORIGINAL_EVENT
);
129 DCHECK(!timeout_monitor_
.IsRunning());
130 DCHECK(touch_queue_
->empty());
131 TRACE_EVENT_ASYNC_STEP_INTO0(
132 "input", "TouchEventTimeout", this, "CancelEvent");
134 case PENDING_ACK_NONE
:
135 DCHECK(!timeout_monitor_
.IsRunning());
136 DCHECK(touch_queue_
->empty());
137 TRACE_EVENT_ASYNC_END0("input", "TouchEventTimeout", this);
140 pending_ack_state_
= new_pending_ack_state
;
143 bool HasTimeoutEvent() const {
144 return pending_ack_state_
!= PENDING_ACK_NONE
;
148 TouchEventQueue
* touch_queue_
;
150 // How long to wait on a touch ack before cancelling the touch sequence.
151 base::TimeDelta timeout_delay_
;
153 // The touch event source for which we expect the next ack.
154 PendingAckState pending_ack_state_
;
156 // The event for which the ack timeout is triggered.
157 TouchEventWithLatencyInfo timeout_event_
;
159 // Provides timeout-based callback behavior.
160 TimeoutMonitor timeout_monitor_
;
163 // Provides touchmove slop suppression for a single touch that remains within
164 // a given slop region, unless the touchstart is preventDefault'ed.
165 class TouchEventQueue::TouchMoveSlopSuppressor
{
167 // TODO(jdduke): Remove int cast on suppression length, crbug.com/336807.
168 TouchMoveSlopSuppressor(double slop_suppression_length_dips
)
169 : slop_suppression_length_dips_squared_(
170 static_cast<int>(slop_suppression_length_dips
) *
171 static_cast<int>(slop_suppression_length_dips
)),
172 suppressing_touch_moves_(false) {}
174 bool FilterEvent(const WebTouchEvent
& event
) {
175 if (WebTouchEventTraits::IsTouchSequenceStart(event
)) {
176 touch_sequence_start_position_
=
177 gfx::PointF(event
.touches
[0].position
.x
, event
.touches
[0].position
.y
);
178 suppressing_touch_moves_
= slop_suppression_length_dips_squared_
!= 0;
181 if (event
.type
!= WebInputEvent::TouchMove
)
184 if (suppressing_touch_moves_
) {
185 // Movement with a secondary pointer should terminate suppression.
186 if (event
.touchesLength
> 1) {
187 suppressing_touch_moves_
= false;
188 } else if (event
.touchesLength
== 1) {
189 // Movement outside of the slop region should terminate suppression.
190 // TODO(jdduke): Use strict inequality, crbug.com/336807.
191 gfx::PointF
position(event
.touches
[0].position
.x
,
192 event
.touches
[0].position
.y
);
193 if ((position
- touch_sequence_start_position_
).LengthSquared() >=
194 slop_suppression_length_dips_squared_
)
195 suppressing_touch_moves_
= false;
198 return suppressing_touch_moves_
;
201 void ConfirmTouchEvent(InputEventAckState ack_result
) {
202 if (ack_result
== INPUT_EVENT_ACK_STATE_CONSUMED
)
203 suppressing_touch_moves_
= false;
207 double slop_suppression_length_dips_squared_
;
208 gfx::PointF touch_sequence_start_position_
;
209 bool suppressing_touch_moves_
;
211 DISALLOW_COPY_AND_ASSIGN(TouchMoveSlopSuppressor
);
214 // This class represents a single coalesced touch event. However, it also keeps
215 // track of all the original touch-events that were coalesced into a single
216 // event. The coalesced event is forwarded to the renderer, while the original
217 // touch-events are sent to the Client (on ACK for the coalesced event) so that
218 // the Client receives the event with their original timestamp.
219 class CoalescedWebTouchEvent
{
221 CoalescedWebTouchEvent(const TouchEventWithLatencyInfo
& event
,
223 : coalesced_event_(event
),
224 ignore_ack_(ignore_ack
) {
225 events_
.push_back(event
);
226 TRACE_EVENT_ASYNC_BEGIN0(
227 "input", "TouchEventQueue::QueueEvent", this);
230 ~CoalescedWebTouchEvent() {
231 TRACE_EVENT_ASYNC_END0(
232 "input", "TouchEventQueue::QueueEvent", this);
235 // Coalesces the event with the existing event if possible. Returns whether
236 // the event was coalesced.
237 bool CoalesceEventIfPossible(
238 const TouchEventWithLatencyInfo
& event_with_latency
) {
242 if (!coalesced_event_
.CanCoalesceWith(event_with_latency
))
245 TRACE_EVENT_INSTANT0(
246 "input", "TouchEventQueue::MoveCoalesced", TRACE_EVENT_SCOPE_THREAD
);
247 coalesced_event_
.CoalesceWith(event_with_latency
);
248 events_
.push_back(event_with_latency
);
252 const TouchEventWithLatencyInfo
& coalesced_event() const {
253 return coalesced_event_
;
256 WebTouchEventWithLatencyList::iterator
begin() {
257 return events_
.begin();
260 WebTouchEventWithLatencyList::iterator
end() {
261 return events_
.end();
264 size_t size() const { return events_
.size(); }
266 bool ignore_ack() const { return ignore_ack_
; }
269 // This is the event that is forwarded to the renderer.
270 TouchEventWithLatencyInfo coalesced_event_
;
272 // This is the list of the original events that were coalesced.
273 WebTouchEventWithLatencyList events_
;
275 // If |ignore_ack_| is true, don't send this touch event to client
276 // when the event is acked.
279 DISALLOW_COPY_AND_ASSIGN(CoalescedWebTouchEvent
);
282 TouchEventQueue::TouchEventQueue(TouchEventQueueClient
* client
,
283 TouchScrollingMode mode
,
284 double touchmove_suppression_length_dips
)
286 dispatching_touch_ack_(NULL
),
287 dispatching_touch_(false),
288 touch_filtering_state_(TOUCH_FILTERING_STATE_DEFAULT
),
289 ack_timeout_enabled_(false),
290 touchmove_slop_suppressor_(
291 new TouchMoveSlopSuppressor(touchmove_suppression_length_dips
)),
292 absorbing_touch_moves_(false),
293 touch_scrolling_mode_(mode
) {
297 TouchEventQueue::~TouchEventQueue() {
298 if (!touch_queue_
.empty())
299 STLDeleteElements(&touch_queue_
);
302 void TouchEventQueue::QueueEvent(const TouchEventWithLatencyInfo
& event
) {
303 TRACE_EVENT0("input", "TouchEventQueue::QueueEvent");
305 // If the queueing of |event| was triggered by an ack dispatch, defer
306 // processing the event until the dispatch has finished.
307 if (touch_queue_
.empty() && !dispatching_touch_ack_
) {
308 // Optimization of the case without touch handlers. Removing this path
309 // yields identical results, but this avoids unnecessary allocations.
310 if (touch_filtering_state_
== DROP_ALL_TOUCHES
||
311 (touch_filtering_state_
== DROP_TOUCHES_IN_SEQUENCE
&&
312 !WebTouchEventTraits::IsTouchSequenceStart(event
.event
))) {
313 client_
->OnTouchEventAck(event
, INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS
);
317 // There is no touch event in the queue. Forward it to the renderer
319 touch_queue_
.push_back(new CoalescedWebTouchEvent(event
, false));
320 TryForwardNextEventToRenderer();
324 // If the last queued touch-event was a touch-move, and the current event is
325 // also a touch-move, then the events can be coalesced into a single event.
326 if (touch_queue_
.size() > 1) {
327 CoalescedWebTouchEvent
* last_event
= touch_queue_
.back();
328 if (last_event
->CoalesceEventIfPossible(event
))
331 touch_queue_
.push_back(new CoalescedWebTouchEvent(event
, false));
334 void TouchEventQueue::ProcessTouchAck(InputEventAckState ack_result
,
335 const LatencyInfo
& latency_info
) {
336 TRACE_EVENT0("input", "TouchEventQueue::ProcessTouchAck");
338 DCHECK(!dispatching_touch_ack_
);
339 dispatching_touch_
= false;
341 if (timeout_handler_
&& timeout_handler_
->ConfirmTouchEvent(ack_result
))
344 touchmove_slop_suppressor_
->ConfirmTouchEvent(ack_result
);
346 if (touch_queue_
.empty())
349 const WebTouchEvent
& acked_event
=
350 touch_queue_
.front()->coalesced_event().event
;
352 if (ack_result
== INPUT_EVENT_ACK_STATE_CONSUMED
&&
353 touch_filtering_state_
== FORWARD_TOUCHES_UNTIL_TIMEOUT
) {
354 touch_filtering_state_
= FORWARD_ALL_TOUCHES
;
357 if (ack_result
== INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS
&&
358 touch_filtering_state_
!= DROP_ALL_TOUCHES
&&
359 WebTouchEventTraits::IsTouchSequenceStart(acked_event
)) {
360 touch_filtering_state_
= DROP_TOUCHES_IN_SEQUENCE
;
363 UpdateTouchAckStates(acked_event
, ack_result
);
364 PopTouchEventToClient(ack_result
, latency_info
);
365 TryForwardNextEventToRenderer();
368 void TouchEventQueue::TryForwardNextEventToRenderer() {
369 DCHECK(!dispatching_touch_ack_
);
370 // If there are queued touch events, then try to forward them to the renderer
371 // immediately, or ACK the events back to the client if appropriate.
372 while (!touch_queue_
.empty()) {
373 const TouchEventWithLatencyInfo
& touch
=
374 touch_queue_
.front()->coalesced_event();
375 PreFilterResult result
= FilterBeforeForwarding(touch
.event
);
377 case ACK_WITH_NO_CONSUMER_EXISTS
:
378 PopTouchEventToClient(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS
,
381 case ACK_WITH_NOT_CONSUMED
:
382 PopTouchEventToClient(INPUT_EVENT_ACK_STATE_NOT_CONSUMED
,
385 case FORWARD_TO_RENDERER
:
386 ForwardToRenderer(touch
);
392 void TouchEventQueue::ForwardToRenderer(
393 const TouchEventWithLatencyInfo
& touch
) {
394 TRACE_EVENT0("input", "TouchEventQueue::ForwardToRenderer");
396 DCHECK(!dispatching_touch_
);
397 DCHECK_NE(touch_filtering_state_
, DROP_ALL_TOUCHES
);
399 if (WebTouchEventTraits::IsTouchSequenceStart(touch
.event
)) {
400 touch_filtering_state_
=
401 ack_timeout_enabled_
? FORWARD_TOUCHES_UNTIL_TIMEOUT
402 : FORWARD_ALL_TOUCHES
;
403 touch_ack_states_
.clear();
404 absorbing_touch_moves_
= false;
407 // A synchronous ack will reset |dispatching_touch_|, in which case
408 // the touch timeout should not be started.
409 base::AutoReset
<bool> dispatching_touch(&dispatching_touch_
, true);
410 client_
->SendTouchEventImmediately(touch
);
411 if (dispatching_touch_
&&
412 touch_filtering_state_
== FORWARD_TOUCHES_UNTIL_TIMEOUT
&&
413 ShouldTouchTypeTriggerTimeout(touch
.event
.type
)) {
414 DCHECK(timeout_handler_
);
415 timeout_handler_
->Start(touch
);
419 void TouchEventQueue::OnGestureScrollEvent(
420 const GestureEventWithLatencyInfo
& gesture_event
) {
421 if (gesture_event
.event
.type
!= blink::WebInputEvent::GestureScrollBegin
)
424 if (touch_scrolling_mode_
!= TOUCH_SCROLLING_MODE_TOUCHCANCEL
)
427 // We assume that scroll events are generated synchronously from
428 // dispatching a touch event ack. This allows us to generate a synthetic
429 // cancel event that has the same touch ids as the touch event that
430 // is being acked. Otherwise, we don't perform the touch-cancel optimization.
431 if (!dispatching_touch_ack_
)
434 if (touch_filtering_state_
== DROP_TOUCHES_IN_SEQUENCE
)
437 touch_filtering_state_
= DROP_TOUCHES_IN_SEQUENCE
;
439 // Fake a TouchCancel to cancel the touch points of the touch event
440 // that is currently being acked.
441 // Note: |dispatching_touch_ack_| is non-null when we reach here, meaning we
442 // are in the scope of PopTouchEventToClient() and that no touch event
443 // in the queue is waiting for ack from renderer. So we can just insert
444 // the touch cancel at the beginning of the queue.
445 touch_queue_
.push_front(new CoalescedWebTouchEvent(
446 ObtainCancelEventForTouchEvent(
447 dispatching_touch_ack_
->coalesced_event()), true));
450 void TouchEventQueue::OnGestureEventAck(
451 const GestureEventWithLatencyInfo
& event
,
452 InputEventAckState ack_result
) {
453 if (touch_scrolling_mode_
!= TOUCH_SCROLLING_MODE_ABSORB_TOUCHMOVE
)
456 if (event
.event
.type
!= blink::WebInputEvent::GestureScrollUpdate
)
459 // Suspend sending touchmove events as long as the scroll events are handled.
460 // Note that there's no guarantee that this ACK is for the most recent
461 // gesture event (or even part of the current sequence). Worst case, the
462 // delay in updating the absorption state should only result in minor UI
464 // TODO(rbyers): Define precise timing requirements and potentially implement
465 // mitigations for races.
466 absorbing_touch_moves_
= (ack_result
== INPUT_EVENT_ACK_STATE_CONSUMED
);
469 void TouchEventQueue::OnHasTouchEventHandlers(bool has_handlers
) {
470 DCHECK(!dispatching_touch_ack_
);
471 DCHECK(!dispatching_touch_
);
474 if (touch_filtering_state_
== DROP_ALL_TOUCHES
) {
475 // If no touch handler was previously registered, ensure that we don't
476 // send a partial touch sequence to the renderer.
477 DCHECK(touch_queue_
.empty());
478 touch_filtering_state_
= DROP_TOUCHES_IN_SEQUENCE
;
481 // TODO(jdduke): Synthesize a TouchCancel if necessary to update Blink touch
482 // state tracking (e.g., if the touch handler was removed mid-sequence).
483 touch_filtering_state_
= DROP_ALL_TOUCHES
;
484 if (timeout_handler_
)
485 timeout_handler_
->Reset();
486 if (!touch_queue_
.empty())
487 ProcessTouchAck(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS
, LatencyInfo());
488 // As there is no touch handler, ack'ing the event should flush the queue.
489 DCHECK(touch_queue_
.empty());
493 bool TouchEventQueue::IsPendingAckTouchStart() const {
494 DCHECK(!dispatching_touch_ack_
);
495 if (touch_queue_
.empty())
498 const blink::WebTouchEvent
& event
=
499 touch_queue_
.front()->coalesced_event().event
;
500 return (event
.type
== WebInputEvent::TouchStart
);
503 void TouchEventQueue::SetAckTimeoutEnabled(bool enabled
,
504 size_t ack_timeout_delay_ms
) {
506 // Avoid resetting |timeout_handler_|, as an outstanding timeout may
507 // be active and must be completed for ack handling consistency.
508 ack_timeout_enabled_
= false;
512 ack_timeout_enabled_
= true;
513 if (!timeout_handler_
)
514 timeout_handler_
.reset(new TouchTimeoutHandler(this, ack_timeout_delay_ms
));
517 bool TouchEventQueue::IsTimeoutRunningForTesting() const {
518 return timeout_handler_
&& timeout_handler_
->IsTimeoutTimerRunning();
521 const TouchEventWithLatencyInfo
&
522 TouchEventQueue::GetLatestEventForTesting() const {
523 return touch_queue_
.back()->coalesced_event();
526 void TouchEventQueue::FlushQueue() {
527 DCHECK(!dispatching_touch_ack_
);
528 DCHECK(!dispatching_touch_
);
529 if (touch_filtering_state_
!= DROP_ALL_TOUCHES
)
530 touch_filtering_state_
= DROP_TOUCHES_IN_SEQUENCE
;
531 while (!touch_queue_
.empty())
532 PopTouchEventToClient(INPUT_EVENT_ACK_STATE_NOT_CONSUMED
, LatencyInfo());
535 void TouchEventQueue::PopTouchEventToClient(
536 InputEventAckState ack_result
,
537 const LatencyInfo
& renderer_latency_info
) {
538 DCHECK(!dispatching_touch_ack_
);
539 if (touch_queue_
.empty())
541 scoped_ptr
<CoalescedWebTouchEvent
> acked_event(touch_queue_
.front());
542 touch_queue_
.pop_front();
544 if (acked_event
->ignore_ack())
547 // Note that acking the touch-event may result in multiple gestures being sent
548 // to the renderer, or touch-events being queued.
549 base::AutoReset
<CoalescedWebTouchEvent
*>
550 dispatching_touch_ack(&dispatching_touch_ack_
, acked_event
.get());
552 for (WebTouchEventWithLatencyList::iterator iter
= acked_event
->begin(),
553 end
= acked_event
->end();
554 iter
!= end
; ++iter
) {
555 iter
->latency
.AddNewLatencyFrom(renderer_latency_info
);
556 client_
->OnTouchEventAck((*iter
), ack_result
);
560 TouchEventQueue::PreFilterResult
561 TouchEventQueue::FilterBeforeForwarding(const WebTouchEvent
& event
) {
562 if (timeout_handler_
&& timeout_handler_
->FilterEvent(event
))
563 return ACK_WITH_NO_CONSUMER_EXISTS
;
565 if (touchmove_slop_suppressor_
->FilterEvent(event
))
566 return ACK_WITH_NOT_CONSUMED
;
568 if (touch_filtering_state_
== DROP_ALL_TOUCHES
)
569 return ACK_WITH_NO_CONSUMER_EXISTS
;
571 if (touch_filtering_state_
== DROP_TOUCHES_IN_SEQUENCE
&&
572 event
.type
!= WebInputEvent::TouchCancel
) {
573 if (WebTouchEventTraits::IsTouchSequenceStart(event
))
574 return FORWARD_TO_RENDERER
;
575 return ACK_WITH_NOT_CONSUMED
;
578 if (absorbing_touch_moves_
&& event
.type
== WebInputEvent::TouchMove
)
579 return ACK_WITH_NOT_CONSUMED
;
581 // Touch press events should always be forwarded to the renderer.
582 if (event
.type
== WebInputEvent::TouchStart
)
583 return FORWARD_TO_RENDERER
;
585 for (unsigned int i
= 0; i
< event
.touchesLength
; ++i
) {
586 const WebTouchPoint
& point
= event
.touches
[i
];
587 // If a point has been stationary, then don't take it into account.
588 if (point
.state
== WebTouchPoint::StateStationary
)
591 if (touch_ack_states_
.count(point
.id
) > 0) {
592 if (touch_ack_states_
.find(point
.id
)->second
!=
593 INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS
)
594 return FORWARD_TO_RENDERER
;
596 // If the ACK status of a point is unknown, then the event should be
597 // forwarded to the renderer.
598 return FORWARD_TO_RENDERER
;
602 return ACK_WITH_NO_CONSUMER_EXISTS
;
605 void TouchEventQueue::UpdateTouchAckStates(const WebTouchEvent
& event
,
606 InputEventAckState ack_result
) {
607 // Update the ACK status for each touch point in the ACKed event.
608 if (event
.type
== WebInputEvent::TouchEnd
||
609 event
.type
== WebInputEvent::TouchCancel
) {
610 // The points have been released. Erase the ACK states.
611 for (unsigned i
= 0; i
< event
.touchesLength
; ++i
) {
612 const WebTouchPoint
& point
= event
.touches
[i
];
613 if (point
.state
== WebTouchPoint::StateReleased
||
614 point
.state
== WebTouchPoint::StateCancelled
)
615 touch_ack_states_
.erase(point
.id
);
617 } else if (event
.type
== WebInputEvent::TouchStart
) {
618 for (unsigned i
= 0; i
< event
.touchesLength
; ++i
) {
619 const WebTouchPoint
& point
= event
.touches
[i
];
620 if (point
.state
== WebTouchPoint::StatePressed
)
621 touch_ack_states_
[point
.id
] = ack_result
;
626 } // namespace content