Home | History | Annotate | Download | only in input
      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 IsNewTouchGesture(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_->UpdateTouchAckStates(
     84               cancel_event.event, kDefaultNotForwardedAck);
     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 HasTimeoutEvent() const {
     99     return pending_ack_state_ != PENDING_ACK_NONE;
    100   }
    101 
    102   bool IsTimeoutTimerRunning() const {
    103     return timeout_monitor_.IsRunning();
    104   }
    105 
    106  private:
    107   enum PendingAckState {
    108     PENDING_ACK_NONE,
    109     PENDING_ACK_ORIGINAL_EVENT,
    110     PENDING_ACK_CANCEL_EVENT,
    111   };
    112 
    113   void OnTimeOut() {
    114     SetPendingAckState(PENDING_ACK_ORIGINAL_EVENT);
    115     touch_queue_->FlushQueue();
    116   }
    117 
    118   // Skip a cancel event if the timed-out event had no consumer and was the
    119   // initial event in the gesture.
    120   bool AckedTimeoutEventRequiresCancel(InputEventAckState ack_result) const {
    121     DCHECK(HasTimeoutEvent());
    122     if (ack_result != INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS)
    123       return true;
    124     return !IsNewTouchGesture(timeout_event_.event);
    125   }
    126 
    127   void SetPendingAckState(PendingAckState new_pending_ack_state) {
    128     DCHECK_NE(pending_ack_state_, new_pending_ack_state);
    129     switch (new_pending_ack_state) {
    130       case PENDING_ACK_ORIGINAL_EVENT:
    131         DCHECK_EQ(pending_ack_state_, PENDING_ACK_NONE);
    132         TRACE_EVENT_ASYNC_BEGIN0("input", "TouchEventTimeout", this);
    133         break;
    134       case PENDING_ACK_CANCEL_EVENT:
    135         DCHECK_EQ(pending_ack_state_, PENDING_ACK_ORIGINAL_EVENT);
    136         DCHECK(!timeout_monitor_.IsRunning());
    137         DCHECK(touch_queue_->empty());
    138         TRACE_EVENT_ASYNC_STEP_INTO0(
    139             "input", "TouchEventTimeout", this, "CancelEvent");
    140         break;
    141       case PENDING_ACK_NONE:
    142         DCHECK(!timeout_monitor_.IsRunning());
    143         DCHECK(touch_queue_->empty());
    144         TRACE_EVENT_ASYNC_END0("input", "TouchEventTimeout", this);
    145         break;
    146     }
    147     pending_ack_state_ = new_pending_ack_state;
    148   }
    149 
    150 
    151   TouchEventQueue* touch_queue_;
    152 
    153   // How long to wait on a touch ack before cancelling the touch sequence.
    154   base::TimeDelta timeout_delay_;
    155 
    156   // The touch event source for which we expect the next ack.
    157   PendingAckState pending_ack_state_;
    158 
    159   // The event for which the ack timeout is triggered.
    160   TouchEventWithLatencyInfo timeout_event_;
    161 
    162   // Provides timeout-based callback behavior.
    163   TimeoutMonitor timeout_monitor_;
    164 };
    165 
    166 
    167 // This class represents a single coalesced touch event. However, it also keeps
    168 // track of all the original touch-events that were coalesced into a single
    169 // event. The coalesced event is forwarded to the renderer, while the original
    170 // touch-events are sent to the Client (on ACK for the coalesced event) so that
    171 // the Client receives the event with their original timestamp.
    172 class CoalescedWebTouchEvent {
    173  public:
    174   CoalescedWebTouchEvent(const TouchEventWithLatencyInfo& event,
    175                          bool ignore_ack)
    176       : coalesced_event_(event),
    177         ignore_ack_(ignore_ack) {
    178     events_.push_back(event);
    179     TRACE_EVENT_ASYNC_BEGIN0(
    180         "input", "TouchEventQueue::QueueEvent", this);
    181   }
    182 
    183   ~CoalescedWebTouchEvent() {
    184     TRACE_EVENT_ASYNC_END0(
    185         "input", "TouchEventQueue::QueueEvent", this);
    186   }
    187 
    188   // Coalesces the event with the existing event if possible. Returns whether
    189   // the event was coalesced.
    190   bool CoalesceEventIfPossible(
    191       const TouchEventWithLatencyInfo& event_with_latency) {
    192     if (ignore_ack_)
    193       return false;
    194 
    195     if (!coalesced_event_.CanCoalesceWith(event_with_latency))
    196       return false;
    197 
    198     TRACE_EVENT_INSTANT0(
    199         "input", "TouchEventQueue::MoveCoalesced", TRACE_EVENT_SCOPE_THREAD);
    200     coalesced_event_.CoalesceWith(event_with_latency);
    201     events_.push_back(event_with_latency);
    202     return true;
    203   }
    204 
    205   const TouchEventWithLatencyInfo& coalesced_event() const {
    206     return coalesced_event_;
    207   }
    208 
    209   WebTouchEventWithLatencyList::iterator begin() {
    210     return events_.begin();
    211   }
    212 
    213   WebTouchEventWithLatencyList::iterator end() {
    214     return events_.end();
    215   }
    216 
    217   size_t size() const { return events_.size(); }
    218 
    219   bool ignore_ack() const { return ignore_ack_; }
    220 
    221  private:
    222   // This is the event that is forwarded to the renderer.
    223   TouchEventWithLatencyInfo coalesced_event_;
    224 
    225   // This is the list of the original events that were coalesced.
    226   WebTouchEventWithLatencyList events_;
    227 
    228   // If |ignore_ack_| is true, don't send this touch event to client
    229   // when the event is acked.
    230   bool ignore_ack_;
    231 
    232   DISALLOW_COPY_AND_ASSIGN(CoalescedWebTouchEvent);
    233 };
    234 
    235 TouchEventQueue::TouchEventQueue(TouchEventQueueClient* client)
    236     : client_(client),
    237       dispatching_touch_ack_(NULL),
    238       dispatching_touch_(false),
    239       no_touch_to_renderer_(false),
    240       renderer_is_consuming_touch_gesture_(false),
    241       ack_timeout_enabled_(false) {
    242   DCHECK(client);
    243 }
    244 
    245 TouchEventQueue::~TouchEventQueue() {
    246   if (!touch_queue_.empty())
    247     STLDeleteElements(&touch_queue_);
    248 }
    249 
    250 void TouchEventQueue::QueueEvent(const TouchEventWithLatencyInfo& event) {
    251   // If the queueing of |event| was triggered by an ack dispatch, defer
    252   // processing the event until the dispatch has finished.
    253   if (touch_queue_.empty() && !dispatching_touch_ack_) {
    254     // There is no touch event in the queue. Forward it to the renderer
    255     // immediately.
    256     touch_queue_.push_back(new CoalescedWebTouchEvent(event, false));
    257     TryForwardNextEventToRenderer();
    258     return;
    259   }
    260 
    261   // If the last queued touch-event was a touch-move, and the current event is
    262   // also a touch-move, then the events can be coalesced into a single event.
    263   if (touch_queue_.size() > 1) {
    264     CoalescedWebTouchEvent* last_event = touch_queue_.back();
    265     if (last_event->CoalesceEventIfPossible(event))
    266       return;
    267   }
    268   touch_queue_.push_back(new CoalescedWebTouchEvent(event, false));
    269 }
    270 
    271 void TouchEventQueue::ProcessTouchAck(InputEventAckState ack_result,
    272                                       const ui::LatencyInfo& latency_info) {
    273   DCHECK(!dispatching_touch_ack_);
    274   dispatching_touch_ = false;
    275 
    276   if (timeout_handler_ && timeout_handler_->ConfirmTouchEvent(ack_result))
    277     return;
    278 
    279   if (touch_queue_.empty())
    280     return;
    281 
    282   if (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED)
    283     renderer_is_consuming_touch_gesture_ = true;
    284 
    285   const WebTouchEvent& acked_event =
    286       touch_queue_.front()->coalesced_event().event;
    287   UpdateTouchAckStates(acked_event, ack_result);
    288   PopTouchEventToClient(ack_result, latency_info);
    289   TryForwardNextEventToRenderer();
    290 }
    291 
    292 void TouchEventQueue::TryForwardNextEventToRenderer() {
    293   DCHECK(!dispatching_touch_ack_);
    294   // If there are queued touch events, then try to forward them to the renderer
    295   // immediately, or ACK the events back to the client if appropriate.
    296   while (!touch_queue_.empty()) {
    297     const TouchEventWithLatencyInfo& touch =
    298         touch_queue_.front()->coalesced_event();
    299     if (IsNewTouchGesture(touch.event))
    300       renderer_is_consuming_touch_gesture_ = false;
    301     if (ShouldForwardToRenderer(touch.event)) {
    302       ForwardToRenderer(touch);
    303       break;
    304     }
    305     PopTouchEventToClient(kDefaultNotForwardedAck, ui::LatencyInfo());
    306   }
    307 }
    308 
    309 void TouchEventQueue::ForwardToRenderer(
    310     const TouchEventWithLatencyInfo& touch) {
    311   DCHECK(!dispatching_touch_);
    312   // A synchronous ack will reset |dispatching_touch_|, in which case
    313   // the touch timeout should not be started.
    314   base::AutoReset<bool> dispatching_touch(&dispatching_touch_, true);
    315   client_->SendTouchEventImmediately(touch);
    316   if (ack_timeout_enabled_ &&
    317       dispatching_touch_ &&
    318       !renderer_is_consuming_touch_gesture_ &&
    319       ShouldTouchTypeTriggerTimeout(touch.event.type)) {
    320     DCHECK(timeout_handler_);
    321     timeout_handler_->Start(touch);
    322   }
    323 }
    324 
    325 void TouchEventQueue::OnGestureScrollEvent(
    326     const GestureEventWithLatencyInfo& gesture_event) {
    327   blink::WebInputEvent::Type type = gesture_event.event.type;
    328   if (type == blink::WebInputEvent::GestureScrollBegin) {
    329     // We assume the scroll event are generated synchronously from
    330     // dispatching a touch event ack, so that we can fake a cancel
    331     // event that has the correct touch ids as the touch event that
    332     // is being acked. If not, we don't do the touch-cancel optimization.
    333     if (no_touch_to_renderer_ || !dispatching_touch_ack_)
    334       return;
    335     no_touch_to_renderer_ = true;
    336 
    337     // If we have a timeout event, a cancel has already been dispatched
    338     // for the current touch stream.
    339     if (HasTimeoutEvent())
    340       return;
    341 
    342     // Fake a TouchCancel to cancel the touch points of the touch event
    343     // that is currently being acked.
    344     // Note: |dispatching_touch_ack_| is non-null when we reach here, meaning we
    345     // are in the scope of PopTouchEventToClient() and that no touch event
    346     // in the queue is waiting for ack from renderer. So we can just insert
    347     // the touch cancel at the beginning of the queue.
    348     touch_queue_.push_front(new CoalescedWebTouchEvent(
    349         ObtainCancelEventForTouchEvent(
    350             dispatching_touch_ack_->coalesced_event()), true));
    351   } else if (type == blink::WebInputEvent::GestureScrollEnd ||
    352              type == blink::WebInputEvent::GestureFlingStart) {
    353     no_touch_to_renderer_ = false;
    354   }
    355 }
    356 
    357 void TouchEventQueue::FlushQueue() {
    358   DCHECK(!dispatching_touch_ack_);
    359   DCHECK(!dispatching_touch_);
    360   while (!touch_queue_.empty())
    361     PopTouchEventToClient(kDefaultNotForwardedAck, ui::LatencyInfo());
    362 }
    363 
    364 bool TouchEventQueue::IsPendingAckTouchStart() const {
    365   DCHECK(!dispatching_touch_ack_);
    366   if (touch_queue_.empty())
    367     return false;
    368 
    369   const blink::WebTouchEvent& event =
    370       touch_queue_.front()->coalesced_event().event;
    371   return (event.type == WebInputEvent::TouchStart);
    372 }
    373 
    374 void TouchEventQueue::SetAckTimeoutEnabled(bool enabled,
    375                                            size_t ack_timeout_delay_ms) {
    376   if (!enabled) {
    377     // Avoid resetting |timeout_handler_|, as an outstanding timeout may
    378     // be active and must be completed for ack handling consistency.
    379     ack_timeout_enabled_ = false;
    380     return;
    381   }
    382 
    383   ack_timeout_enabled_ = true;
    384   if (!timeout_handler_)
    385     timeout_handler_.reset(new TouchTimeoutHandler(this, ack_timeout_delay_ms));
    386 }
    387 
    388 bool TouchEventQueue::HasTimeoutEvent() const {
    389   return timeout_handler_ && timeout_handler_->HasTimeoutEvent();
    390 }
    391 
    392 bool TouchEventQueue::IsTimeoutRunningForTesting() const {
    393   return timeout_handler_ && timeout_handler_->IsTimeoutTimerRunning();
    394 }
    395 
    396 const TouchEventWithLatencyInfo&
    397 TouchEventQueue::GetLatestEventForTesting() const {
    398   return touch_queue_.back()->coalesced_event();
    399 }
    400 
    401 void TouchEventQueue::PopTouchEventToClient(
    402     InputEventAckState ack_result,
    403     const ui::LatencyInfo& renderer_latency_info) {
    404   DCHECK(!dispatching_touch_ack_);
    405   if (touch_queue_.empty())
    406     return;
    407   scoped_ptr<CoalescedWebTouchEvent> acked_event(touch_queue_.front());
    408   touch_queue_.pop_front();
    409 
    410   if (acked_event->ignore_ack())
    411     return;
    412 
    413   // Note that acking the touch-event may result in multiple gestures being sent
    414   // to the renderer, or touch-events being queued.
    415   base::AutoReset<CoalescedWebTouchEvent*>
    416       dispatching_touch_ack(&dispatching_touch_ack_, acked_event.get());
    417 
    418   for (WebTouchEventWithLatencyList::iterator iter = acked_event->begin(),
    419        end = acked_event->end();
    420        iter != end; ++iter) {
    421     iter->latency.AddNewLatencyFrom(renderer_latency_info);
    422     client_->OnTouchEventAck((*iter), ack_result);
    423   }
    424 }
    425 
    426 bool TouchEventQueue::ShouldForwardToRenderer(
    427     const WebTouchEvent& event) const {
    428   if (HasTimeoutEvent())
    429     return false;
    430 
    431   if (no_touch_to_renderer_ &&
    432       event.type != blink::WebInputEvent::TouchCancel)
    433     return false;
    434 
    435   // Touch press events should always be forwarded to the renderer.
    436   if (event.type == WebInputEvent::TouchStart)
    437     return true;
    438 
    439   for (unsigned int i = 0; i < event.touchesLength; ++i) {
    440     const WebTouchPoint& point = event.touches[i];
    441     // If a point has been stationary, then don't take it into account.
    442     if (point.state == WebTouchPoint::StateStationary)
    443       continue;
    444 
    445     if (touch_ack_states_.count(point.id) > 0) {
    446       if (touch_ack_states_.find(point.id)->second !=
    447           INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS)
    448         return true;
    449     } else {
    450       // If the ACK status of a point is unknown, then the event should be
    451       // forwarded to the renderer.
    452       return true;
    453     }
    454   }
    455 
    456   return false;
    457 }
    458 
    459 void TouchEventQueue::UpdateTouchAckStates(const WebTouchEvent& event,
    460                                            InputEventAckState ack_result) {
    461   // Update the ACK status for each touch point in the ACKed event.
    462   if (event.type == WebInputEvent::TouchEnd ||
    463       event.type == WebInputEvent::TouchCancel) {
    464     // The points have been released. Erase the ACK states.
    465     for (unsigned i = 0; i < event.touchesLength; ++i) {
    466       const WebTouchPoint& point = event.touches[i];
    467       if (point.state == WebTouchPoint::StateReleased ||
    468           point.state == WebTouchPoint::StateCancelled)
    469         touch_ack_states_.erase(point.id);
    470     }
    471   } else if (event.type == WebInputEvent::TouchStart) {
    472     for (unsigned i = 0; i < event.touchesLength; ++i) {
    473       const WebTouchPoint& point = event.touches[i];
    474       if (point.state == WebTouchPoint::StatePressed)
    475         touch_ack_states_[point.id] = ack_result;
    476     }
    477   }
    478 }
    479 
    480 }  // namespace content
    481