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