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/debug/trace_event.h"
      9 #include "base/stl_util.h"
     10 
     11 namespace content {
     12 
     13 typedef std::vector<TouchEventWithLatencyInfo> WebTouchEventWithLatencyList;
     14 
     15 // This class represents a single coalesced touch event. However, it also keeps
     16 // track of all the original touch-events that were coalesced into a single
     17 // event. The coalesced event is forwarded to the renderer, while the original
     18 // touch-events are sent to the Client (on ACK for the coalesced event) so that
     19 // the Client receives the event with their original timestamp.
     20 class CoalescedWebTouchEvent {
     21  public:
     22   explicit CoalescedWebTouchEvent(const TouchEventWithLatencyInfo& event)
     23       : coalesced_event_(event) {
     24     events_.push_back(event);
     25     TRACE_EVENT_ASYNC_BEGIN0(
     26         "input", "TouchEventQueue::QueueEvent", this);
     27   }
     28 
     29   ~CoalescedWebTouchEvent() {
     30     TRACE_EVENT_ASYNC_END0(
     31         "input", "TouchEventQueue::QueueEvent", this);
     32   }
     33 
     34   // Coalesces the event with the existing event if possible. Returns whether
     35   // the event was coalesced.
     36   bool CoalesceEventIfPossible(
     37       const TouchEventWithLatencyInfo& event_with_latency) {
     38     if (coalesced_event_.event.type == WebKit::WebInputEvent::TouchMove &&
     39         event_with_latency.event.type == WebKit::WebInputEvent::TouchMove &&
     40         coalesced_event_.event.modifiers ==
     41         event_with_latency.event.modifiers &&
     42         coalesced_event_.event.touchesLength ==
     43         event_with_latency.event.touchesLength) {
     44       TRACE_EVENT_INSTANT0(
     45           "input", "TouchEventQueue::MoveCoalesced", TRACE_EVENT_SCOPE_THREAD);
     46       events_.push_back(event_with_latency);
     47       // The WebTouchPoints include absolute position information. So it is
     48       // sufficient to simply replace the previous event with the new event.
     49       // However, it is necessary to make sure that all the points have the
     50       // correct state, i.e. the touch-points that moved in the last event, but
     51       // didn't change in the current event, will have Stationary state. It is
     52       // necessary to change them back to Moved state.
     53       const WebKit::WebTouchEvent last_event = coalesced_event_.event;
     54       const ui::LatencyInfo last_latency = coalesced_event_.latency;
     55       coalesced_event_ = event_with_latency;
     56       coalesced_event_.latency.MergeWith(last_latency);
     57       for (unsigned i = 0; i < last_event.touchesLength; ++i) {
     58         if (last_event.touches[i].state == WebKit::WebTouchPoint::StateMoved)
     59           coalesced_event_.event.touches[i].state =
     60               WebKit::WebTouchPoint::StateMoved;
     61       }
     62       return true;
     63     }
     64 
     65     return false;
     66   }
     67 
     68   const TouchEventWithLatencyInfo& coalesced_event() const {
     69     return coalesced_event_;
     70   }
     71 
     72   WebTouchEventWithLatencyList::const_iterator begin() const {
     73     return events_.begin();
     74   }
     75 
     76   WebTouchEventWithLatencyList::const_iterator end() const {
     77     return events_.end();
     78   }
     79 
     80   size_t size() const { return events_.size(); }
     81 
     82  private:
     83   // This is the event that is forwarded to the renderer.
     84   TouchEventWithLatencyInfo coalesced_event_;
     85 
     86   // This is the list of the original events that were coalesced.
     87   WebTouchEventWithLatencyList events_;
     88 
     89   DISALLOW_COPY_AND_ASSIGN(CoalescedWebTouchEvent);
     90 };
     91 
     92 TouchEventQueue::TouchEventQueue(TouchEventQueueClient* client)
     93     : client_(client),
     94       dispatching_touch_ack_(false) {
     95   DCHECK(client);
     96 }
     97 
     98 TouchEventQueue::~TouchEventQueue() {
     99   if (!touch_queue_.empty())
    100     STLDeleteElements(&touch_queue_);
    101 }
    102 
    103 void TouchEventQueue::QueueEvent(const TouchEventWithLatencyInfo& event) {
    104   // If the queueing of |event| was triggered by an ack dispatch, defer
    105   // processing the event until the dispatch has finished.
    106   if (touch_queue_.empty() && !dispatching_touch_ack_) {
    107     // There is no touch event in the queue. Forward it to the renderer
    108     // immediately.
    109     touch_queue_.push_back(new CoalescedWebTouchEvent(event));
    110     if (ShouldForwardToRenderer(event.event))
    111       client_->SendTouchEventImmediately(event);
    112     else
    113       PopTouchEventToClient(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS,
    114                             ui::LatencyInfo());
    115     return;
    116   }
    117 
    118   // If the last queued touch-event was a touch-move, and the current event is
    119   // also a touch-move, then the events can be coalesced into a single event.
    120   if (touch_queue_.size() > 1) {
    121     CoalescedWebTouchEvent* last_event = touch_queue_.back();
    122     if (last_event->CoalesceEventIfPossible(event))
    123       return;
    124   }
    125   touch_queue_.push_back(new CoalescedWebTouchEvent(event));
    126 }
    127 
    128 void TouchEventQueue::ProcessTouchAck(InputEventAckState ack_result,
    129                                       const ui::LatencyInfo& latency_info) {
    130   DCHECK(!dispatching_touch_ack_);
    131   if (touch_queue_.empty())
    132     return;
    133 
    134   // Update the ACK status for each touch point in the ACKed event.
    135   const WebKit::WebTouchEvent& event =
    136       touch_queue_.front()->coalesced_event().event;
    137   if (event.type == WebKit::WebInputEvent::TouchEnd ||
    138       event.type == WebKit::WebInputEvent::TouchCancel) {
    139     // The points have been released. Erase the ACK states.
    140     for (unsigned i = 0; i < event.touchesLength; ++i) {
    141       const WebKit::WebTouchPoint& point = event.touches[i];
    142       if (point.state == WebKit::WebTouchPoint::StateReleased ||
    143           point.state == WebKit::WebTouchPoint::StateCancelled)
    144         touch_ack_states_.erase(point.id);
    145     }
    146   } else if (event.type == WebKit::WebInputEvent::TouchStart) {
    147     for (unsigned i = 0; i < event.touchesLength; ++i) {
    148       const WebKit::WebTouchPoint& point = event.touches[i];
    149       if (point.state == WebKit::WebTouchPoint::StatePressed)
    150         touch_ack_states_[point.id] = ack_result;
    151     }
    152   }
    153 
    154   PopTouchEventToClient(ack_result, latency_info);
    155 
    156   // If there are queued touch events, then try to forward them to the renderer
    157   // immediately, or ACK the events back to the client if appropriate.
    158   while (!touch_queue_.empty()) {
    159     const TouchEventWithLatencyInfo& touch =
    160         touch_queue_.front()->coalesced_event();
    161     if (ShouldForwardToRenderer(touch.event)) {
    162       client_->SendTouchEventImmediately(touch);
    163       break;
    164     }
    165     PopTouchEventToClient(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS,
    166                           ui::LatencyInfo());
    167   }
    168 }
    169 
    170 void TouchEventQueue::FlushQueue() {
    171   DCHECK(!dispatching_touch_ack_);
    172   while (!touch_queue_.empty())
    173     PopTouchEventToClient(INPUT_EVENT_ACK_STATE_NOT_CONSUMED,
    174                           ui::LatencyInfo());
    175 }
    176 
    177 size_t TouchEventQueue::GetQueueSize() const {
    178   return touch_queue_.size();
    179 }
    180 
    181 const TouchEventWithLatencyInfo& TouchEventQueue::GetLatestEvent() const {
    182   return touch_queue_.back()->coalesced_event();
    183 }
    184 
    185 void TouchEventQueue::PopTouchEventToClient(
    186     InputEventAckState ack_result,
    187     const ui::LatencyInfo& renderer_latency_info) {
    188   if (touch_queue_.empty())
    189     return;
    190   scoped_ptr<CoalescedWebTouchEvent> acked_event(touch_queue_.front());
    191   touch_queue_.pop_front();
    192 
    193   // Note that acking the touch-event may result in multiple gestures being sent
    194   // to the renderer, or touch-events being queued.
    195   base::AutoReset<bool> dispatching_touch_ack(&dispatching_touch_ack_, true);
    196 
    197   base::TimeTicks now = base::TimeTicks::HighResNow();
    198   for (WebTouchEventWithLatencyList::const_iterator iter = acked_event->begin(),
    199        end = acked_event->end();
    200        iter != end; ++iter) {
    201     ui::LatencyInfo* latency = const_cast<ui::LatencyInfo*>(&(iter->latency));
    202     latency->AddNewLatencyFrom(renderer_latency_info);
    203     latency->AddLatencyNumberWithTimestamp(
    204         ui::INPUT_EVENT_LATENCY_ACKED_COMPONENT, 0, 0, now, 1);
    205     client_->OnTouchEventAck((*iter), ack_result);
    206   }
    207 }
    208 
    209 bool TouchEventQueue::ShouldForwardToRenderer(
    210     const WebKit::WebTouchEvent& event) const {
    211   // Touch press events should always be forwarded to the renderer.
    212   if (event.type == WebKit::WebInputEvent::TouchStart)
    213     return true;
    214 
    215   for (unsigned int i = 0; i < event.touchesLength; ++i) {
    216     const WebKit::WebTouchPoint& point = event.touches[i];
    217     // If a point has been stationary, then don't take it into account.
    218     if (point.state == WebKit::WebTouchPoint::StateStationary)
    219       continue;
    220 
    221     if (touch_ack_states_.count(point.id) > 0) {
    222       if (touch_ack_states_.find(point.id)->second !=
    223           INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS)
    224         return true;
    225     } else {
    226       // If the ACK status of a point is unknown, then the event should be
    227       // forwarded to the renderer.
    228       return true;
    229     }
    230   }
    231 
    232   return false;
    233 }
    234 
    235 }  // namespace content
    236