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