Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / content / browser / renderer_host / input / touch_event_queue.cc
1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "content/browser/renderer_host/input/touch_event_queue.h"
6
7 #include "base/auto_reset.h"
8 #include "base/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/common/input/web_input_event_traits.h"
13 #include "content/public/common/content_switches.h"
14 #include "ui/gfx/geometry/point_f.h"
15
16 using blink::WebInputEvent;
17 using blink::WebTouchEvent;
18 using blink::WebTouchPoint;
19 using ui::LatencyInfo;
20
21 namespace content {
22 namespace {
23
24 typedef std::vector<TouchEventWithLatencyInfo> WebTouchEventWithLatencyList;
25
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;
32   return event;
33 }
34
35 bool IsNewTouchSequence(const WebTouchEvent& event) {
36   if (event.type != WebInputEvent::TouchStart)
37     return false;
38   if (!event.touchesLength)
39     return false;
40   for (size_t i = 0; i < event.touchesLength; i++) {
41     if (event.touches[i].state != WebTouchPoint::StatePressed)
42       return false;
43   }
44   return true;
45 }
46
47 bool ShouldTouchTypeTriggerTimeout(WebInputEvent::Type type) {
48   return type == WebInputEvent::TouchStart ||
49          type == WebInputEvent::TouchMove;
50 }
51
52 }  // namespace
53
54
55 // Cancels a touch sequence if a touchstart or touchmove ack response is
56 // sufficiently delayed.
57 class TouchEventQueue::TouchTimeoutHandler {
58  public:
59   TouchTimeoutHandler(TouchEventQueue* touch_queue, size_t timeout_delay_ms)
60       : touch_queue_(touch_queue),
61         timeout_delay_(base::TimeDelta::FromMilliseconds(timeout_delay_ms)),
62         pending_ack_state_(PENDING_ACK_NONE),
63         timeout_monitor_(base::Bind(&TouchTimeoutHandler::OnTimeOut,
64                                     base::Unretained(this))) {}
65
66   ~TouchTimeoutHandler() {}
67
68   void Start(const TouchEventWithLatencyInfo& event) {
69     DCHECK_EQ(pending_ack_state_, PENDING_ACK_NONE);
70     DCHECK(ShouldTouchTypeTriggerTimeout(event.event.type));
71     timeout_event_ = event;
72     timeout_monitor_.Restart(timeout_delay_);
73   }
74
75   bool ConfirmTouchEvent(InputEventAckState ack_result) {
76     switch (pending_ack_state_) {
77       case PENDING_ACK_NONE:
78         timeout_monitor_.Stop();
79         return false;
80       case PENDING_ACK_ORIGINAL_EVENT:
81         if (AckedTimeoutEventRequiresCancel(ack_result)) {
82           SetPendingAckState(PENDING_ACK_CANCEL_EVENT);
83           TouchEventWithLatencyInfo cancel_event =
84               ObtainCancelEventForTouchEvent(timeout_event_);
85           touch_queue_->client_->SendTouchEventImmediately(cancel_event);
86         } else {
87           SetPendingAckState(PENDING_ACK_NONE);
88           touch_queue_->UpdateTouchAckStates(timeout_event_.event, ack_result);
89         }
90         return true;
91       case PENDING_ACK_CANCEL_EVENT:
92         SetPendingAckState(PENDING_ACK_NONE);
93         return true;
94     }
95     return false;
96   }
97
98   bool FilterEvent(const WebTouchEvent& event) {
99     return HasTimeoutEvent();
100   }
101
102   bool IsTimeoutTimerRunning() const {
103     return timeout_monitor_.IsRunning();
104   }
105
106   void Reset() {
107     pending_ack_state_ = PENDING_ACK_NONE;
108     timeout_monitor_.Stop();
109   }
110
111  private:
112   enum PendingAckState {
113     PENDING_ACK_NONE,
114     PENDING_ACK_ORIGINAL_EVENT,
115     PENDING_ACK_CANCEL_EVENT,
116   };
117
118   void OnTimeOut() {
119     SetPendingAckState(PENDING_ACK_ORIGINAL_EVENT);
120     touch_queue_->FlushQueue();
121   }
122
123   // Skip a cancel event if the timed-out event had no consumer and was the
124   // initial event in the gesture.
125   bool AckedTimeoutEventRequiresCancel(InputEventAckState ack_result) const {
126     DCHECK(HasTimeoutEvent());
127     if (ack_result != INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS)
128       return true;
129     return !IsNewTouchSequence(timeout_event_.event);
130   }
131
132   void SetPendingAckState(PendingAckState new_pending_ack_state) {
133     DCHECK_NE(pending_ack_state_, new_pending_ack_state);
134     switch (new_pending_ack_state) {
135       case PENDING_ACK_ORIGINAL_EVENT:
136         DCHECK_EQ(pending_ack_state_, PENDING_ACK_NONE);
137         TRACE_EVENT_ASYNC_BEGIN0("input", "TouchEventTimeout", this);
138         break;
139       case PENDING_ACK_CANCEL_EVENT:
140         DCHECK_EQ(pending_ack_state_, PENDING_ACK_ORIGINAL_EVENT);
141         DCHECK(!timeout_monitor_.IsRunning());
142         DCHECK(touch_queue_->empty());
143         TRACE_EVENT_ASYNC_STEP_INTO0(
144             "input", "TouchEventTimeout", this, "CancelEvent");
145         break;
146       case PENDING_ACK_NONE:
147         DCHECK(!timeout_monitor_.IsRunning());
148         DCHECK(touch_queue_->empty());
149         TRACE_EVENT_ASYNC_END0("input", "TouchEventTimeout", this);
150         break;
151     }
152     pending_ack_state_ = new_pending_ack_state;
153   }
154
155   bool HasTimeoutEvent() const {
156     return pending_ack_state_ != PENDING_ACK_NONE;
157   }
158
159
160   TouchEventQueue* touch_queue_;
161
162   // How long to wait on a touch ack before cancelling the touch sequence.
163   base::TimeDelta timeout_delay_;
164
165   // The touch event source for which we expect the next ack.
166   PendingAckState pending_ack_state_;
167
168   // The event for which the ack timeout is triggered.
169   TouchEventWithLatencyInfo timeout_event_;
170
171   // Provides timeout-based callback behavior.
172   TimeoutMonitor timeout_monitor_;
173 };
174
175 // Provides touchmove slop suppression for a single touch that remains within
176 // a given slop region, unless the touchstart is preventDefault'ed.
177 class TouchEventQueue::TouchMoveSlopSuppressor {
178  public:
179   // TODO(jdduke): Remove int cast on suppression length, crbug.com/336807.
180   TouchMoveSlopSuppressor(double slop_suppression_length_dips)
181       : slop_suppression_length_dips_squared_(
182             static_cast<int>(slop_suppression_length_dips) *
183             static_cast<int>(slop_suppression_length_dips)),
184         suppressing_touch_moves_(false) {}
185
186   bool FilterEvent(const WebTouchEvent& event) {
187     if (IsNewTouchSequence(event)) {
188       touch_sequence_start_position_ =
189           gfx::Point(event.touches[0].position);
190       suppressing_touch_moves_ = slop_suppression_length_dips_squared_ != 0;
191     }
192
193     if (event.type != WebInputEvent::TouchMove)
194       return false;
195
196     if (suppressing_touch_moves_) {
197       // Movement with a secondary pointer should terminate suppression.
198       if (event.touchesLength > 1) {
199         suppressing_touch_moves_ = false;
200       } else if (event.touchesLength == 1) {
201         // Movement outside of the slop region should terminate suppression.
202         // TODO(jdduke): Use strict inequality, crbug.com/336807.
203         gfx::PointF position(event.touches[0].position.x,
204                              event.touches[0].position.y);
205         if ((position - touch_sequence_start_position_).LengthSquared() >=
206                 slop_suppression_length_dips_squared_)
207           suppressing_touch_moves_ = false;
208       }
209     }
210     return suppressing_touch_moves_;
211   }
212
213   void ConfirmTouchEvent(InputEventAckState ack_result) {
214     if (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED)
215       suppressing_touch_moves_ = false;
216   }
217
218  private:
219   double slop_suppression_length_dips_squared_;
220   gfx::PointF touch_sequence_start_position_;
221   bool suppressing_touch_moves_;
222
223   DISALLOW_COPY_AND_ASSIGN(TouchMoveSlopSuppressor);
224 };
225
226 // This class represents a single coalesced touch event. However, it also keeps
227 // track of all the original touch-events that were coalesced into a single
228 // event. The coalesced event is forwarded to the renderer, while the original
229 // touch-events are sent to the Client (on ACK for the coalesced event) so that
230 // the Client receives the event with their original timestamp.
231 class CoalescedWebTouchEvent {
232  public:
233   CoalescedWebTouchEvent(const TouchEventWithLatencyInfo& event,
234                          bool ignore_ack)
235       : coalesced_event_(event),
236         ignore_ack_(ignore_ack) {
237     events_.push_back(event);
238     TRACE_EVENT_ASYNC_BEGIN0(
239         "input", "TouchEventQueue::QueueEvent", this);
240   }
241
242   ~CoalescedWebTouchEvent() {
243     TRACE_EVENT_ASYNC_END0(
244         "input", "TouchEventQueue::QueueEvent", this);
245   }
246
247   // Coalesces the event with the existing event if possible. Returns whether
248   // the event was coalesced.
249   bool CoalesceEventIfPossible(
250       const TouchEventWithLatencyInfo& event_with_latency) {
251     if (ignore_ack_)
252       return false;
253
254     if (!coalesced_event_.CanCoalesceWith(event_with_latency))
255       return false;
256
257     TRACE_EVENT_INSTANT0(
258         "input", "TouchEventQueue::MoveCoalesced", TRACE_EVENT_SCOPE_THREAD);
259     coalesced_event_.CoalesceWith(event_with_latency);
260     events_.push_back(event_with_latency);
261     return true;
262   }
263
264   const TouchEventWithLatencyInfo& coalesced_event() const {
265     return coalesced_event_;
266   }
267
268   WebTouchEventWithLatencyList::iterator begin() {
269     return events_.begin();
270   }
271
272   WebTouchEventWithLatencyList::iterator end() {
273     return events_.end();
274   }
275
276   size_t size() const { return events_.size(); }
277
278   bool ignore_ack() const { return ignore_ack_; }
279
280  private:
281   // This is the event that is forwarded to the renderer.
282   TouchEventWithLatencyInfo coalesced_event_;
283
284   // This is the list of the original events that were coalesced.
285   WebTouchEventWithLatencyList events_;
286
287   // If |ignore_ack_| is true, don't send this touch event to client
288   // when the event is acked.
289   bool ignore_ack_;
290
291   DISALLOW_COPY_AND_ASSIGN(CoalescedWebTouchEvent);
292 };
293
294 TouchEventQueue::TouchEventQueue(TouchEventQueueClient* client,
295                                  TouchScrollingMode mode,
296                                  double touchmove_suppression_length_dips)
297     : client_(client),
298       dispatching_touch_ack_(NULL),
299       dispatching_touch_(false),
300       touch_filtering_state_(TOUCH_FILTERING_STATE_DEFAULT),
301       ack_timeout_enabled_(false),
302       touchmove_slop_suppressor_(
303           new TouchMoveSlopSuppressor(touchmove_suppression_length_dips)),
304       absorbing_touch_moves_(false),
305       touch_scrolling_mode_(mode) {
306   DCHECK(client);
307 }
308
309 TouchEventQueue::~TouchEventQueue() {
310   if (!touch_queue_.empty())
311     STLDeleteElements(&touch_queue_);
312 }
313
314 void TouchEventQueue::QueueEvent(const TouchEventWithLatencyInfo& event) {
315   TRACE_EVENT0("input", "TouchEventQueue::QueueEvent");
316
317   // If the queueing of |event| was triggered by an ack dispatch, defer
318   // processing the event until the dispatch has finished.
319   if (touch_queue_.empty() && !dispatching_touch_ack_) {
320     // Optimization of the case without touch handlers.  Removing this path
321     // yields identical results, but this avoids unnecessary allocations.
322     if (touch_filtering_state_ == DROP_ALL_TOUCHES ||
323         (touch_filtering_state_ == DROP_TOUCHES_IN_SEQUENCE &&
324          !IsNewTouchSequence(event.event))) {
325       client_->OnTouchEventAck(event, INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
326       return;
327     }
328
329     // There is no touch event in the queue. Forward it to the renderer
330     // immediately.
331     touch_queue_.push_back(new CoalescedWebTouchEvent(event, false));
332     TryForwardNextEventToRenderer();
333     return;
334   }
335
336   // If the last queued touch-event was a touch-move, and the current event is
337   // also a touch-move, then the events can be coalesced into a single event.
338   if (touch_queue_.size() > 1) {
339     CoalescedWebTouchEvent* last_event = touch_queue_.back();
340     if (last_event->CoalesceEventIfPossible(event))
341       return;
342   }
343   touch_queue_.push_back(new CoalescedWebTouchEvent(event, false));
344 }
345
346 void TouchEventQueue::ProcessTouchAck(InputEventAckState ack_result,
347                                       const LatencyInfo& latency_info) {
348   TRACE_EVENT0("input", "TouchEventQueue::ProcessTouchAck");
349
350   DCHECK(!dispatching_touch_ack_);
351   dispatching_touch_ = false;
352
353   if (timeout_handler_ && timeout_handler_->ConfirmTouchEvent(ack_result))
354     return;
355
356   touchmove_slop_suppressor_->ConfirmTouchEvent(ack_result);
357
358   if (touch_queue_.empty())
359     return;
360
361   if (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED &&
362       touch_filtering_state_ == FORWARD_TOUCHES_UNTIL_TIMEOUT)
363     touch_filtering_state_ = FORWARD_ALL_TOUCHES;
364
365   const WebTouchEvent& acked_event =
366       touch_queue_.front()->coalesced_event().event;
367   UpdateTouchAckStates(acked_event, ack_result);
368   PopTouchEventToClient(ack_result, latency_info);
369   TryForwardNextEventToRenderer();
370 }
371
372 void TouchEventQueue::TryForwardNextEventToRenderer() {
373   DCHECK(!dispatching_touch_ack_);
374   // If there are queued touch events, then try to forward them to the renderer
375   // immediately, or ACK the events back to the client if appropriate.
376   while (!touch_queue_.empty()) {
377     const TouchEventWithLatencyInfo& touch =
378         touch_queue_.front()->coalesced_event();
379     PreFilterResult result = FilterBeforeForwarding(touch.event);
380     switch (result) {
381       case ACK_WITH_NO_CONSUMER_EXISTS:
382         PopTouchEventToClient(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS,
383                               LatencyInfo());
384         break;
385       case ACK_WITH_NOT_CONSUMED:
386         PopTouchEventToClient(INPUT_EVENT_ACK_STATE_NOT_CONSUMED,
387                               LatencyInfo());
388         break;
389       case FORWARD_TO_RENDERER:
390         ForwardToRenderer(touch);
391         return;
392     }
393   }
394 }
395
396 void TouchEventQueue::ForwardToRenderer(
397     const TouchEventWithLatencyInfo& touch) {
398   TRACE_EVENT0("input", "TouchEventQueue::ForwardToRenderer");
399
400   DCHECK(!dispatching_touch_);
401   DCHECK_NE(touch_filtering_state_, DROP_ALL_TOUCHES);
402
403   if (IsNewTouchSequence(touch.event)) {
404     touch_filtering_state_ =
405         ack_timeout_enabled_ ? FORWARD_TOUCHES_UNTIL_TIMEOUT
406                              : FORWARD_ALL_TOUCHES;
407     touch_ack_states_.clear();
408     absorbing_touch_moves_ = false;
409   }
410
411   // A synchronous ack will reset |dispatching_touch_|, in which case
412   // the touch timeout should not be started.
413   base::AutoReset<bool> dispatching_touch(&dispatching_touch_, true);
414   client_->SendTouchEventImmediately(touch);
415   if (dispatching_touch_ &&
416       touch_filtering_state_ == FORWARD_TOUCHES_UNTIL_TIMEOUT &&
417       ShouldTouchTypeTriggerTimeout(touch.event.type)) {
418     DCHECK(timeout_handler_);
419     timeout_handler_->Start(touch);
420   }
421 }
422
423 void TouchEventQueue::OnGestureScrollEvent(
424     const GestureEventWithLatencyInfo& gesture_event) {
425   if (gesture_event.event.type != blink::WebInputEvent::GestureScrollBegin)
426     return;
427
428   if (touch_scrolling_mode_ != TOUCH_SCROLLING_MODE_TOUCHCANCEL)
429     return;
430
431   // We assume that scroll events are generated synchronously from
432   // dispatching a touch event ack. This allows us to generate a synthetic
433   // cancel event that has the same touch ids as the touch event that
434   // is being acked. Otherwise, we don't perform the touch-cancel optimization.
435   if (!dispatching_touch_ack_)
436     return;
437
438   if (touch_filtering_state_ == DROP_TOUCHES_IN_SEQUENCE)
439     return;
440
441   touch_filtering_state_ = DROP_TOUCHES_IN_SEQUENCE;
442
443   // Fake a TouchCancel to cancel the touch points of the touch event
444   // that is currently being acked.
445   // Note: |dispatching_touch_ack_| is non-null when we reach here, meaning we
446   // are in the scope of PopTouchEventToClient() and that no touch event
447   // in the queue is waiting for ack from renderer. So we can just insert
448   // the touch cancel at the beginning of the queue.
449   touch_queue_.push_front(new CoalescedWebTouchEvent(
450       ObtainCancelEventForTouchEvent(
451           dispatching_touch_ack_->coalesced_event()), true));
452 }
453
454 void TouchEventQueue::OnGestureEventAck(
455     const GestureEventWithLatencyInfo& event,
456     InputEventAckState ack_result) {
457   if (touch_scrolling_mode_ != TOUCH_SCROLLING_MODE_ABSORB_TOUCHMOVE)
458     return;
459
460   if (event.event.type != blink::WebInputEvent::GestureScrollUpdate)
461     return;
462
463   // Suspend sending touchmove events as long as the scroll events are handled.
464   // Note that there's no guarantee that this ACK is for the most recent
465   // gesture event (or even part of the current sequence).  Worst case, the
466   // delay in updating the absorption state should only result in minor UI
467   // glitches.
468   // TODO(rbyers): Define precise timing requirements and potentially implement
469   // mitigations for races.
470   absorbing_touch_moves_ = (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED);
471 }
472
473 void TouchEventQueue::OnHasTouchEventHandlers(bool has_handlers) {
474   DCHECK(!dispatching_touch_ack_);
475   DCHECK(!dispatching_touch_);
476
477   if (has_handlers) {
478     if (touch_filtering_state_ == DROP_ALL_TOUCHES) {
479       // If no touch handler was previously registered, ensure that we don't
480       // send a partial touch sequence to the renderer.
481       DCHECK(touch_queue_.empty());
482       touch_filtering_state_ = DROP_TOUCHES_IN_SEQUENCE;
483     }
484   } else {
485     // TODO(jdduke): Synthesize a TouchCancel if necessary to update Blink touch
486     // state tracking (e.g., if the touch handler was removed mid-sequence).
487     touch_filtering_state_ = DROP_ALL_TOUCHES;
488     if (timeout_handler_)
489       timeout_handler_->Reset();
490     if (!touch_queue_.empty())
491       ProcessTouchAck(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS, LatencyInfo());
492     // As there is no touch handler, ack'ing the event should flush the queue.
493     DCHECK(touch_queue_.empty());
494   }
495 }
496
497 bool TouchEventQueue::IsPendingAckTouchStart() const {
498   DCHECK(!dispatching_touch_ack_);
499   if (touch_queue_.empty())
500     return false;
501
502   const blink::WebTouchEvent& event =
503       touch_queue_.front()->coalesced_event().event;
504   return (event.type == WebInputEvent::TouchStart);
505 }
506
507 void TouchEventQueue::SetAckTimeoutEnabled(bool enabled,
508                                            size_t ack_timeout_delay_ms) {
509   if (!enabled) {
510     // Avoid resetting |timeout_handler_|, as an outstanding timeout may
511     // be active and must be completed for ack handling consistency.
512     ack_timeout_enabled_ = false;
513     return;
514   }
515
516   ack_timeout_enabled_ = true;
517   if (!timeout_handler_)
518     timeout_handler_.reset(new TouchTimeoutHandler(this, ack_timeout_delay_ms));
519 }
520
521 bool TouchEventQueue::IsTimeoutRunningForTesting() const {
522   return timeout_handler_ && timeout_handler_->IsTimeoutTimerRunning();
523 }
524
525 const TouchEventWithLatencyInfo&
526 TouchEventQueue::GetLatestEventForTesting() const {
527   return touch_queue_.back()->coalesced_event();
528 }
529
530 void TouchEventQueue::FlushQueue() {
531   DCHECK(!dispatching_touch_ack_);
532   DCHECK(!dispatching_touch_);
533   if (touch_filtering_state_ != DROP_ALL_TOUCHES)
534     touch_filtering_state_ = DROP_TOUCHES_IN_SEQUENCE;
535   while (!touch_queue_.empty())
536     PopTouchEventToClient(INPUT_EVENT_ACK_STATE_NOT_CONSUMED, LatencyInfo());
537 }
538
539 void TouchEventQueue::PopTouchEventToClient(
540     InputEventAckState ack_result,
541     const LatencyInfo& renderer_latency_info) {
542   DCHECK(!dispatching_touch_ack_);
543   if (touch_queue_.empty())
544     return;
545   scoped_ptr<CoalescedWebTouchEvent> acked_event(touch_queue_.front());
546   touch_queue_.pop_front();
547
548   if (acked_event->ignore_ack())
549     return;
550
551   // Note that acking the touch-event may result in multiple gestures being sent
552   // to the renderer, or touch-events being queued.
553   base::AutoReset<CoalescedWebTouchEvent*>
554       dispatching_touch_ack(&dispatching_touch_ack_, acked_event.get());
555
556   for (WebTouchEventWithLatencyList::iterator iter = acked_event->begin(),
557        end = acked_event->end();
558        iter != end; ++iter) {
559     iter->latency.AddNewLatencyFrom(renderer_latency_info);
560     client_->OnTouchEventAck((*iter), ack_result);
561   }
562 }
563
564 TouchEventQueue::PreFilterResult
565 TouchEventQueue::FilterBeforeForwarding(const WebTouchEvent& event) {
566   if (timeout_handler_ && timeout_handler_->FilterEvent(event))
567     return ACK_WITH_NO_CONSUMER_EXISTS;
568
569   if (touchmove_slop_suppressor_->FilterEvent(event))
570     return ACK_WITH_NOT_CONSUMED;
571
572   if (touch_filtering_state_ == DROP_ALL_TOUCHES)
573     return ACK_WITH_NO_CONSUMER_EXISTS;
574
575   if (touch_filtering_state_ == DROP_TOUCHES_IN_SEQUENCE &&
576       event.type != WebInputEvent::TouchCancel) {
577     if (IsNewTouchSequence(event))
578       return FORWARD_TO_RENDERER;
579     return ACK_WITH_NOT_CONSUMED;
580   }
581
582   if (absorbing_touch_moves_ && event.type == WebInputEvent::TouchMove)
583     return ACK_WITH_NOT_CONSUMED;
584
585   // Touch press events should always be forwarded to the renderer.
586   if (event.type == WebInputEvent::TouchStart)
587     return FORWARD_TO_RENDERER;
588
589   for (unsigned int i = 0; i < event.touchesLength; ++i) {
590     const WebTouchPoint& point = event.touches[i];
591     // If a point has been stationary, then don't take it into account.
592     if (point.state == WebTouchPoint::StateStationary)
593       continue;
594
595     if (touch_ack_states_.count(point.id) > 0) {
596       if (touch_ack_states_.find(point.id)->second !=
597           INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS)
598         return FORWARD_TO_RENDERER;
599     } else {
600       // If the ACK status of a point is unknown, then the event should be
601       // forwarded to the renderer.
602       return FORWARD_TO_RENDERER;
603     }
604   }
605
606   return ACK_WITH_NO_CONSUMER_EXISTS;
607 }
608
609 void TouchEventQueue::UpdateTouchAckStates(const WebTouchEvent& event,
610                                            InputEventAckState ack_result) {
611   // Update the ACK status for each touch point in the ACKed event.
612   if (event.type == WebInputEvent::TouchEnd ||
613       event.type == WebInputEvent::TouchCancel) {
614     // The points have been released. Erase the ACK states.
615     for (unsigned i = 0; i < event.touchesLength; ++i) {
616       const WebTouchPoint& point = event.touches[i];
617       if (point.state == WebTouchPoint::StateReleased ||
618           point.state == WebTouchPoint::StateCancelled)
619         touch_ack_states_.erase(point.id);
620     }
621   } else if (event.type == WebInputEvent::TouchStart) {
622     for (unsigned i = 0; i < event.touchesLength; ++i) {
623       const WebTouchPoint& point = event.touches[i];
624       if (point.state == WebTouchPoint::StatePressed)
625         touch_ack_states_[point.id] = ack_result;
626     }
627   }
628 }
629
630 }  // namespace content