Upstream version 5.34.92.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
15 using blink::WebInputEvent;
16 using blink::WebTouchEvent;
17 using blink::WebTouchPoint;
18
19 namespace content {
20 namespace {
21
22 const InputEventAckState kDefaultNotForwardedAck =
23     INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS;
24
25 typedef std::vector<TouchEventWithLatencyInfo> WebTouchEventWithLatencyList;
26
27 TouchEventWithLatencyInfo ObtainCancelEventForTouchEvent(
28     const TouchEventWithLatencyInfo& event_to_cancel) {
29   TouchEventWithLatencyInfo event = event_to_cancel;
30   event.event.type = WebInputEvent::TouchCancel;
31   for (size_t i = 0; i < event.event.touchesLength; i++)
32     event.event.touches[i].state = WebTouchPoint::StateCancelled;
33   return event;
34 }
35
36 bool IsNewTouchSequence(const WebTouchEvent& event) {
37   if (event.type != WebInputEvent::TouchStart)
38     return false;
39   if (!event.touchesLength)
40     return false;
41   for (size_t i = 0; i < event.touchesLength; i++) {
42     if (event.touches[i].state != WebTouchPoint::StatePressed)
43       return false;
44   }
45   return true;
46 }
47
48 bool ShouldTouchTypeTriggerTimeout(WebInputEvent::Type type) {
49   return type == WebInputEvent::TouchStart ||
50          type == WebInputEvent::TouchMove;
51 }
52
53 }  // namespace
54
55 class TouchEventQueue::TouchTimeoutHandler {
56  public:
57   TouchTimeoutHandler(TouchEventQueue* touch_queue, size_t timeout_delay_ms)
58       : touch_queue_(touch_queue),
59         timeout_delay_(base::TimeDelta::FromMilliseconds(timeout_delay_ms)),
60         pending_ack_state_(PENDING_ACK_NONE),
61         timeout_monitor_(base::Bind(&TouchTimeoutHandler::OnTimeOut,
62                                     base::Unretained(this))) {}
63
64   ~TouchTimeoutHandler() {}
65
66   void Start(const TouchEventWithLatencyInfo& event) {
67     DCHECK_EQ(pending_ack_state_, PENDING_ACK_NONE);
68     DCHECK(ShouldTouchTypeTriggerTimeout(event.event.type));
69     timeout_event_ = event;
70     timeout_monitor_.Restart(timeout_delay_);
71   }
72
73   bool ConfirmTouchEvent(InputEventAckState ack_result) {
74     switch (pending_ack_state_) {
75       case PENDING_ACK_NONE:
76         timeout_monitor_.Stop();
77         return false;
78       case PENDING_ACK_ORIGINAL_EVENT:
79         if (AckedTimeoutEventRequiresCancel(ack_result)) {
80           SetPendingAckState(PENDING_ACK_CANCEL_EVENT);
81           TouchEventWithLatencyInfo cancel_event =
82               ObtainCancelEventForTouchEvent(timeout_event_);
83           touch_queue_->client_->SendTouchEventImmediately(cancel_event);
84         } else {
85           SetPendingAckState(PENDING_ACK_NONE);
86           touch_queue_->UpdateTouchAckStates(timeout_event_.event, ack_result);
87         }
88         return true;
89       case PENDING_ACK_CANCEL_EVENT:
90         SetPendingAckState(PENDING_ACK_NONE);
91         return true;
92     }
93     return false;
94   }
95
96   bool HasTimeoutEvent() const {
97     return pending_ack_state_ != PENDING_ACK_NONE;
98   }
99
100   bool IsTimeoutTimerRunning() const {
101     return timeout_monitor_.IsRunning();
102   }
103
104   void Reset() {
105     pending_ack_state_ = PENDING_ACK_NONE;
106     timeout_monitor_.Stop();
107   }
108
109  private:
110   enum PendingAckState {
111     PENDING_ACK_NONE,
112     PENDING_ACK_ORIGINAL_EVENT,
113     PENDING_ACK_CANCEL_EVENT,
114   };
115
116   void OnTimeOut() {
117     SetPendingAckState(PENDING_ACK_ORIGINAL_EVENT);
118     touch_queue_->FlushQueue();
119   }
120
121   // Skip a cancel event if the timed-out event had no consumer and was the
122   // initial event in the gesture.
123   bool AckedTimeoutEventRequiresCancel(InputEventAckState ack_result) const {
124     DCHECK(HasTimeoutEvent());
125     if (ack_result != INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS)
126       return true;
127     return !IsNewTouchSequence(timeout_event_.event);
128   }
129
130   void SetPendingAckState(PendingAckState new_pending_ack_state) {
131     DCHECK_NE(pending_ack_state_, new_pending_ack_state);
132     switch (new_pending_ack_state) {
133       case PENDING_ACK_ORIGINAL_EVENT:
134         DCHECK_EQ(pending_ack_state_, PENDING_ACK_NONE);
135         TRACE_EVENT_ASYNC_BEGIN0("input", "TouchEventTimeout", this);
136         break;
137       case PENDING_ACK_CANCEL_EVENT:
138         DCHECK_EQ(pending_ack_state_, PENDING_ACK_ORIGINAL_EVENT);
139         DCHECK(!timeout_monitor_.IsRunning());
140         DCHECK(touch_queue_->empty());
141         TRACE_EVENT_ASYNC_STEP_INTO0(
142             "input", "TouchEventTimeout", this, "CancelEvent");
143         break;
144       case PENDING_ACK_NONE:
145         DCHECK(!timeout_monitor_.IsRunning());
146         DCHECK(touch_queue_->empty());
147         TRACE_EVENT_ASYNC_END0("input", "TouchEventTimeout", this);
148         break;
149     }
150     pending_ack_state_ = new_pending_ack_state;
151   }
152
153
154   TouchEventQueue* touch_queue_;
155
156   // How long to wait on a touch ack before cancelling the touch sequence.
157   base::TimeDelta timeout_delay_;
158
159   // The touch event source for which we expect the next ack.
160   PendingAckState pending_ack_state_;
161
162   // The event for which the ack timeout is triggered.
163   TouchEventWithLatencyInfo timeout_event_;
164
165   // Provides timeout-based callback behavior.
166   TimeoutMonitor timeout_monitor_;
167 };
168
169
170 // This class represents a single coalesced touch event. However, it also keeps
171 // track of all the original touch-events that were coalesced into a single
172 // event. The coalesced event is forwarded to the renderer, while the original
173 // touch-events are sent to the Client (on ACK for the coalesced event) so that
174 // the Client receives the event with their original timestamp.
175 class CoalescedWebTouchEvent {
176  public:
177   CoalescedWebTouchEvent(const TouchEventWithLatencyInfo& event,
178                          bool ignore_ack)
179       : coalesced_event_(event),
180         ignore_ack_(ignore_ack) {
181     events_.push_back(event);
182     TRACE_EVENT_ASYNC_BEGIN0(
183         "input", "TouchEventQueue::QueueEvent", this);
184   }
185
186   ~CoalescedWebTouchEvent() {
187     TRACE_EVENT_ASYNC_END0(
188         "input", "TouchEventQueue::QueueEvent", this);
189   }
190
191   // Coalesces the event with the existing event if possible. Returns whether
192   // the event was coalesced.
193   bool CoalesceEventIfPossible(
194       const TouchEventWithLatencyInfo& event_with_latency) {
195     if (ignore_ack_)
196       return false;
197
198     if (!coalesced_event_.CanCoalesceWith(event_with_latency))
199       return false;
200
201     TRACE_EVENT_INSTANT0(
202         "input", "TouchEventQueue::MoveCoalesced", TRACE_EVENT_SCOPE_THREAD);
203     coalesced_event_.CoalesceWith(event_with_latency);
204     events_.push_back(event_with_latency);
205     return true;
206   }
207
208   const TouchEventWithLatencyInfo& coalesced_event() const {
209     return coalesced_event_;
210   }
211
212   WebTouchEventWithLatencyList::iterator begin() {
213     return events_.begin();
214   }
215
216   WebTouchEventWithLatencyList::iterator end() {
217     return events_.end();
218   }
219
220   size_t size() const { return events_.size(); }
221
222   bool ignore_ack() const { return ignore_ack_; }
223
224  private:
225   // This is the event that is forwarded to the renderer.
226   TouchEventWithLatencyInfo coalesced_event_;
227
228   // This is the list of the original events that were coalesced.
229   WebTouchEventWithLatencyList events_;
230
231   // If |ignore_ack_| is true, don't send this touch event to client
232   // when the event is acked.
233   bool ignore_ack_;
234
235   DISALLOW_COPY_AND_ASSIGN(CoalescedWebTouchEvent);
236 };
237
238 TouchEventQueue::TouchEventQueue(TouchEventQueueClient* client)
239     : client_(client),
240       dispatching_touch_ack_(NULL),
241       dispatching_touch_(false),
242       touch_filtering_state_(TOUCH_FILTERING_STATE_DEFAULT),
243       ack_timeout_enabled_(false) {
244   DCHECK(client);
245 }
246
247 TouchEventQueue::~TouchEventQueue() {
248   if (!touch_queue_.empty())
249     STLDeleteElements(&touch_queue_);
250 }
251
252 void TouchEventQueue::QueueEvent(const TouchEventWithLatencyInfo& event) {
253   // If the queueing of |event| was triggered by an ack dispatch, defer
254   // processing the event until the dispatch has finished.
255   if (touch_queue_.empty() && !dispatching_touch_ack_) {
256     // Optimization of the case without touch handlers.  Removing this path
257     // yields identical results, but this avoids unnecessary allocations.
258     if (touch_filtering_state_ == DROP_ALL_TOUCHES ||
259         (touch_filtering_state_ == DROP_TOUCHES_IN_SEQUENCE &&
260          !IsNewTouchSequence(event.event))) {
261       client_->OnTouchEventAck(event, kDefaultNotForwardedAck);
262       return;
263     }
264
265     // There is no touch event in the queue. Forward it to the renderer
266     // immediately.
267     touch_queue_.push_back(new CoalescedWebTouchEvent(event, false));
268     TryForwardNextEventToRenderer();
269     return;
270   }
271
272   // If the last queued touch-event was a touch-move, and the current event is
273   // also a touch-move, then the events can be coalesced into a single event.
274   if (touch_queue_.size() > 1) {
275     CoalescedWebTouchEvent* last_event = touch_queue_.back();
276     if (last_event->CoalesceEventIfPossible(event))
277       return;
278   }
279   touch_queue_.push_back(new CoalescedWebTouchEvent(event, false));
280 }
281
282 void TouchEventQueue::ProcessTouchAck(InputEventAckState ack_result,
283                                       const ui::LatencyInfo& latency_info) {
284   DCHECK(!dispatching_touch_ack_);
285   dispatching_touch_ = false;
286
287   if (timeout_handler_ && timeout_handler_->ConfirmTouchEvent(ack_result))
288     return;
289
290   if (touch_queue_.empty())
291     return;
292
293   if (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED &&
294       touch_filtering_state_ == FORWARD_TOUCHES_UNTIL_TIMEOUT)
295     touch_filtering_state_ = FORWARD_ALL_TOUCHES;
296
297   const WebTouchEvent& acked_event =
298       touch_queue_.front()->coalesced_event().event;
299   UpdateTouchAckStates(acked_event, ack_result);
300   PopTouchEventToClient(ack_result, latency_info);
301   TryForwardNextEventToRenderer();
302 }
303
304 void TouchEventQueue::TryForwardNextEventToRenderer() {
305   DCHECK(!dispatching_touch_ack_);
306   // If there are queued touch events, then try to forward them to the renderer
307   // immediately, or ACK the events back to the client if appropriate.
308   while (!touch_queue_.empty()) {
309     const TouchEventWithLatencyInfo& touch =
310         touch_queue_.front()->coalesced_event();
311     if (ShouldForwardToRenderer(touch.event)) {
312       ForwardToRenderer(touch);
313       break;
314     }
315     PopTouchEventToClient(kDefaultNotForwardedAck, ui::LatencyInfo());
316   }
317 }
318
319 void TouchEventQueue::ForwardToRenderer(
320     const TouchEventWithLatencyInfo& touch) {
321   DCHECK(!dispatching_touch_);
322   DCHECK_NE(touch_filtering_state_, DROP_ALL_TOUCHES);
323
324   if (IsNewTouchSequence(touch.event)) {
325     touch_filtering_state_ =
326         ack_timeout_enabled_ ? FORWARD_TOUCHES_UNTIL_TIMEOUT
327                              : FORWARD_ALL_TOUCHES;
328     touch_ack_states_.clear();
329   }
330
331   // A synchronous ack will reset |dispatching_touch_|, in which case
332   // the touch timeout should not be started.
333   base::AutoReset<bool> dispatching_touch(&dispatching_touch_, true);
334   client_->SendTouchEventImmediately(touch);
335   if (dispatching_touch_ &&
336       touch_filtering_state_ == FORWARD_TOUCHES_UNTIL_TIMEOUT &&
337       ShouldTouchTypeTriggerTimeout(touch.event.type)) {
338     DCHECK(timeout_handler_);
339     timeout_handler_->Start(touch);
340   }
341 }
342
343 void TouchEventQueue::OnGestureScrollEvent(
344     const GestureEventWithLatencyInfo& gesture_event) {
345   if (gesture_event.event.type != blink::WebInputEvent::GestureScrollBegin)
346     return;
347
348   // We assume that scroll events are generated synchronously from
349   // dispatching a touch event ack. This allows us to generate a synthetic
350   // cancel event that has the same touch ids as the touch event that
351   // is being acked. Otherwise, we don't perform the touch-cancel optimization.
352   if (!dispatching_touch_ack_)
353     return;
354
355   if (touch_filtering_state_ == DROP_TOUCHES_IN_SEQUENCE)
356     return;
357
358   touch_filtering_state_ = DROP_TOUCHES_IN_SEQUENCE;
359
360   // Fake a TouchCancel to cancel the touch points of the touch event
361   // that is currently being acked.
362   // Note: |dispatching_touch_ack_| is non-null when we reach here, meaning we
363   // are in the scope of PopTouchEventToClient() and that no touch event
364   // in the queue is waiting for ack from renderer. So we can just insert
365   // the touch cancel at the beginning of the queue.
366   touch_queue_.push_front(new CoalescedWebTouchEvent(
367       ObtainCancelEventForTouchEvent(
368           dispatching_touch_ack_->coalesced_event()), true));
369 }
370
371 void TouchEventQueue::OnHasTouchEventHandlers(bool has_handlers) {
372   DCHECK(!dispatching_touch_ack_);
373   DCHECK(!dispatching_touch_);
374
375   if (has_handlers) {
376     if (touch_filtering_state_ == DROP_ALL_TOUCHES) {
377       // If no touch handler was previously registered, ensure that we don't
378       // send a partial touch sequence to the renderer.
379       DCHECK(touch_queue_.empty());
380       touch_filtering_state_ = DROP_TOUCHES_IN_SEQUENCE;
381     }
382   } else {
383     // TODO(jdduke): Synthesize a TouchCancel if necessary to update Blink touch
384     // state tracking (e.g., if the touch handler was removed mid-sequence).
385     touch_filtering_state_ = DROP_ALL_TOUCHES;
386     if (timeout_handler_)
387       timeout_handler_->Reset();
388     if (!touch_queue_.empty())
389       ProcessTouchAck(kDefaultNotForwardedAck, ui::LatencyInfo());
390     // As there is no touch handler, ack'ing the event should flush the queue.
391     DCHECK(touch_queue_.empty());
392   }
393 }
394
395 bool TouchEventQueue::IsPendingAckTouchStart() const {
396   DCHECK(!dispatching_touch_ack_);
397   if (touch_queue_.empty())
398     return false;
399
400   const blink::WebTouchEvent& event =
401       touch_queue_.front()->coalesced_event().event;
402   return (event.type == WebInputEvent::TouchStart);
403 }
404
405 void TouchEventQueue::SetAckTimeoutEnabled(bool enabled,
406                                            size_t ack_timeout_delay_ms) {
407   if (!enabled) {
408     // Avoid resetting |timeout_handler_|, as an outstanding timeout may
409     // be active and must be completed for ack handling consistency.
410     ack_timeout_enabled_ = false;
411     return;
412   }
413
414   ack_timeout_enabled_ = true;
415   if (!timeout_handler_)
416     timeout_handler_.reset(new TouchTimeoutHandler(this, ack_timeout_delay_ms));
417 }
418
419 bool TouchEventQueue::HasTimeoutEvent() const {
420   return timeout_handler_ && timeout_handler_->HasTimeoutEvent();
421 }
422
423 bool TouchEventQueue::IsTimeoutRunningForTesting() const {
424   return timeout_handler_ && timeout_handler_->IsTimeoutTimerRunning();
425 }
426
427 const TouchEventWithLatencyInfo&
428 TouchEventQueue::GetLatestEventForTesting() const {
429   return touch_queue_.back()->coalesced_event();
430 }
431
432 void TouchEventQueue::FlushQueue() {
433   DCHECK(!dispatching_touch_ack_);
434   DCHECK(!dispatching_touch_);
435   if (touch_filtering_state_ != DROP_ALL_TOUCHES)
436     touch_filtering_state_ = DROP_TOUCHES_IN_SEQUENCE;
437   while (!touch_queue_.empty())
438     PopTouchEventToClient(kDefaultNotForwardedAck, ui::LatencyInfo());
439 }
440
441 void TouchEventQueue::PopTouchEventToClient(
442     InputEventAckState ack_result,
443     const ui::LatencyInfo& renderer_latency_info) {
444   DCHECK(!dispatching_touch_ack_);
445   if (touch_queue_.empty())
446     return;
447   scoped_ptr<CoalescedWebTouchEvent> acked_event(touch_queue_.front());
448   touch_queue_.pop_front();
449
450   if (acked_event->ignore_ack())
451     return;
452
453   // Note that acking the touch-event may result in multiple gestures being sent
454   // to the renderer, or touch-events being queued.
455   base::AutoReset<CoalescedWebTouchEvent*>
456       dispatching_touch_ack(&dispatching_touch_ack_, acked_event.get());
457
458   for (WebTouchEventWithLatencyList::iterator iter = acked_event->begin(),
459        end = acked_event->end();
460        iter != end; ++iter) {
461     iter->latency.AddNewLatencyFrom(renderer_latency_info);
462     client_->OnTouchEventAck((*iter), ack_result);
463   }
464 }
465
466 bool TouchEventQueue::ShouldForwardToRenderer(
467     const WebTouchEvent& event) const {
468   if (HasTimeoutEvent())
469     return false;
470
471   if (touch_filtering_state_ == DROP_ALL_TOUCHES)
472     return false;
473
474   if (touch_filtering_state_ == DROP_TOUCHES_IN_SEQUENCE &&
475       event.type != WebInputEvent::TouchCancel) {
476     if (IsNewTouchSequence(event))
477       return true;
478     return false;
479   }
480
481   // Touch press events should always be forwarded to the renderer.
482   if (event.type == WebInputEvent::TouchStart)
483     return true;
484
485   for (unsigned int i = 0; i < event.touchesLength; ++i) {
486     const WebTouchPoint& point = event.touches[i];
487     // If a point has been stationary, then don't take it into account.
488     if (point.state == WebTouchPoint::StateStationary)
489       continue;
490
491     if (touch_ack_states_.count(point.id) > 0) {
492       if (touch_ack_states_.find(point.id)->second !=
493           INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS)
494         return true;
495     } else {
496       // If the ACK status of a point is unknown, then the event should be
497       // forwarded to the renderer.
498       return true;
499     }
500   }
501
502   return false;
503 }
504
505 void TouchEventQueue::UpdateTouchAckStates(const WebTouchEvent& event,
506                                            InputEventAckState ack_result) {
507   // Update the ACK status for each touch point in the ACKed event.
508   if (event.type == WebInputEvent::TouchEnd ||
509       event.type == WebInputEvent::TouchCancel) {
510     // The points have been released. Erase the ACK states.
511     for (unsigned i = 0; i < event.touchesLength; ++i) {
512       const WebTouchPoint& point = event.touches[i];
513       if (point.state == WebTouchPoint::StateReleased ||
514           point.state == WebTouchPoint::StateCancelled)
515         touch_ack_states_.erase(point.id);
516     }
517   } else if (event.type == WebInputEvent::TouchStart) {
518     for (unsigned i = 0; i < event.touchesLength; ++i) {
519       const WebTouchPoint& point = event.touches[i];
520       if (point.state == WebTouchPoint::StatePressed)
521         touch_ack_states_[point.id] = ack_result;
522     }
523   }
524 }
525
526 }  // namespace content