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 #include "content/browser/renderer_host/input/timeout_monitor.h" 11 #include "content/common/input/web_touch_event_traits.h" 12 #include "ui/gfx/geometry/point_f.h" 13 14 using blink::WebInputEvent; 15 using blink::WebTouchEvent; 16 using blink::WebTouchPoint; 17 using ui::LatencyInfo; 18 19 namespace content { 20 namespace { 21 22 // Time interval at which touchmove events will be forwarded to the client while 23 // scrolling is active and possible. 24 const double kAsyncTouchMoveIntervalSec = .2; 25 26 // A slop region just larger than that used by many web applications. When 27 // touchmove's are being sent asynchronously, movement outside this region will 28 // trigger an immediate async touchmove to cancel potential tap-related logic. 29 const double kApplicationSlopRegionLengthDipsSqared = 15. * 15.; 30 31 // Using a small epsilon when comparing slop distances allows pixel perfect 32 // slop determination when using fractional DIP coordinates (assuming the slop 33 // region and DPI scale are reasonably proportioned). 34 const float kSlopEpsilon = .05f; 35 36 TouchEventWithLatencyInfo ObtainCancelEventForTouchEvent( 37 const TouchEventWithLatencyInfo& event_to_cancel) { 38 TouchEventWithLatencyInfo event = event_to_cancel; 39 WebTouchEventTraits::ResetTypeAndTouchStates( 40 WebInputEvent::TouchCancel, 41 // TODO(rbyers): Shouldn't we use a fresh timestamp? 42 event.event.timeStampSeconds, 43 &event.event); 44 return event; 45 } 46 47 bool ShouldTouchTriggerTimeout(const WebTouchEvent& event) { 48 return (event.type == WebInputEvent::TouchStart || 49 event.type == WebInputEvent::TouchMove) && 50 !WebInputEventTraits::IgnoresAckDisposition(event); 51 } 52 53 bool OutsideApplicationSlopRegion(const WebTouchEvent& event, 54 const gfx::PointF& anchor) { 55 return (gfx::PointF(event.touches[0].position) - anchor).LengthSquared() > 56 kApplicationSlopRegionLengthDipsSqared; 57 } 58 59 } // namespace 60 61 62 // Cancels a touch sequence if a touchstart or touchmove ack response is 63 // sufficiently delayed. 64 class TouchEventQueue::TouchTimeoutHandler { 65 public: 66 TouchTimeoutHandler(TouchEventQueue* touch_queue, 67 base::TimeDelta timeout_delay) 68 : touch_queue_(touch_queue), 69 timeout_delay_(timeout_delay), 70 pending_ack_state_(PENDING_ACK_NONE), 71 timeout_monitor_(base::Bind(&TouchTimeoutHandler::OnTimeOut, 72 base::Unretained(this))), 73 enabled_(true), 74 enabled_for_current_sequence_(false) { 75 DCHECK(timeout_delay != base::TimeDelta()); 76 } 77 78 ~TouchTimeoutHandler() {} 79 80 void StartIfNecessary(const TouchEventWithLatencyInfo& event) { 81 DCHECK_EQ(pending_ack_state_, PENDING_ACK_NONE); 82 if (!enabled_) 83 return; 84 85 if (!ShouldTouchTriggerTimeout(event.event)) 86 return; 87 88 if (WebTouchEventTraits::IsTouchSequenceStart(event.event)) 89 enabled_for_current_sequence_ = true; 90 91 if (!enabled_for_current_sequence_) 92 return; 93 94 timeout_event_ = event; 95 timeout_monitor_.Restart(timeout_delay_); 96 } 97 98 bool ConfirmTouchEvent(InputEventAckState ack_result) { 99 switch (pending_ack_state_) { 100 case PENDING_ACK_NONE: 101 if (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED) 102 enabled_for_current_sequence_ = false; 103 timeout_monitor_.Stop(); 104 return false; 105 case PENDING_ACK_ORIGINAL_EVENT: 106 if (AckedTimeoutEventRequiresCancel(ack_result)) { 107 SetPendingAckState(PENDING_ACK_CANCEL_EVENT); 108 TouchEventWithLatencyInfo cancel_event = 109 ObtainCancelEventForTouchEvent(timeout_event_); 110 touch_queue_->SendTouchEventImmediately(cancel_event); 111 } else { 112 SetPendingAckState(PENDING_ACK_NONE); 113 touch_queue_->UpdateTouchConsumerStates(timeout_event_.event, 114 ack_result); 115 } 116 return true; 117 case PENDING_ACK_CANCEL_EVENT: 118 SetPendingAckState(PENDING_ACK_NONE); 119 return true; 120 } 121 return false; 122 } 123 124 bool FilterEvent(const WebTouchEvent& event) { 125 return HasTimeoutEvent(); 126 } 127 128 void SetEnabled(bool enabled) { 129 if (enabled_ == enabled) 130 return; 131 132 enabled_ = enabled; 133 134 if (enabled_) 135 return; 136 137 enabled_for_current_sequence_ = false; 138 // Only reset the |timeout_handler_| if the timer is running and has not 139 // yet timed out. This ensures that an already timed out sequence is 140 // properly flushed by the handler. 141 if (IsTimeoutTimerRunning()) { 142 pending_ack_state_ = PENDING_ACK_NONE; 143 timeout_monitor_.Stop(); 144 } 145 } 146 147 bool IsTimeoutTimerRunning() const { return timeout_monitor_.IsRunning(); } 148 149 bool enabled() const { return enabled_; } 150 151 private: 152 enum PendingAckState { 153 PENDING_ACK_NONE, 154 PENDING_ACK_ORIGINAL_EVENT, 155 PENDING_ACK_CANCEL_EVENT, 156 }; 157 158 void OnTimeOut() { 159 SetPendingAckState(PENDING_ACK_ORIGINAL_EVENT); 160 touch_queue_->FlushQueue(); 161 } 162 163 // Skip a cancel event if the timed-out event had no consumer and was the 164 // initial event in the gesture. 165 bool AckedTimeoutEventRequiresCancel(InputEventAckState ack_result) const { 166 DCHECK(HasTimeoutEvent()); 167 if (ack_result != INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS) 168 return true; 169 return !WebTouchEventTraits::IsTouchSequenceStart(timeout_event_.event); 170 } 171 172 void SetPendingAckState(PendingAckState new_pending_ack_state) { 173 DCHECK_NE(pending_ack_state_, new_pending_ack_state); 174 switch (new_pending_ack_state) { 175 case PENDING_ACK_ORIGINAL_EVENT: 176 DCHECK_EQ(pending_ack_state_, PENDING_ACK_NONE); 177 TRACE_EVENT_ASYNC_BEGIN0("input", "TouchEventTimeout", this); 178 break; 179 case PENDING_ACK_CANCEL_EVENT: 180 DCHECK_EQ(pending_ack_state_, PENDING_ACK_ORIGINAL_EVENT); 181 DCHECK(!timeout_monitor_.IsRunning()); 182 DCHECK(touch_queue_->empty()); 183 TRACE_EVENT_ASYNC_STEP_INTO0( 184 "input", "TouchEventTimeout", this, "CancelEvent"); 185 break; 186 case PENDING_ACK_NONE: 187 DCHECK(!timeout_monitor_.IsRunning()); 188 DCHECK(touch_queue_->empty()); 189 TRACE_EVENT_ASYNC_END0("input", "TouchEventTimeout", this); 190 break; 191 } 192 pending_ack_state_ = new_pending_ack_state; 193 } 194 195 bool HasTimeoutEvent() const { 196 return pending_ack_state_ != PENDING_ACK_NONE; 197 } 198 199 200 TouchEventQueue* touch_queue_; 201 202 // How long to wait on a touch ack before cancelling the touch sequence. 203 base::TimeDelta timeout_delay_; 204 205 // The touch event source for which we expect the next ack. 206 PendingAckState pending_ack_state_; 207 208 // The event for which the ack timeout is triggered. 209 TouchEventWithLatencyInfo timeout_event_; 210 211 // Provides timeout-based callback behavior. 212 TimeoutMonitor timeout_monitor_; 213 214 bool enabled_; 215 bool enabled_for_current_sequence_; 216 }; 217 218 // Provides touchmove slop suppression for a single touch that remains within 219 // a given slop region, unless the touchstart is preventDefault'ed. 220 // TODO(jdduke): Use a flag bundled with each TouchEvent declaring whether it 221 // has exceeded the slop region, removing duplicated slop determination logic. 222 class TouchEventQueue::TouchMoveSlopSuppressor { 223 public: 224 TouchMoveSlopSuppressor(double slop_suppression_length_dips) 225 : slop_suppression_length_dips_squared_(0), 226 suppressing_touchmoves_(false) { 227 if (slop_suppression_length_dips) { 228 slop_suppression_length_dips += kSlopEpsilon; 229 slop_suppression_length_dips_squared_ = 230 slop_suppression_length_dips * slop_suppression_length_dips; 231 } 232 } 233 234 bool FilterEvent(const WebTouchEvent& event) { 235 if (WebTouchEventTraits::IsTouchSequenceStart(event)) { 236 touch_sequence_start_position_ = 237 gfx::PointF(event.touches[0].position); 238 suppressing_touchmoves_ = slop_suppression_length_dips_squared_ != 0; 239 } 240 241 if (event.type == WebInputEvent::TouchEnd || 242 event.type == WebInputEvent::TouchCancel) 243 suppressing_touchmoves_ = false; 244 245 if (event.type != WebInputEvent::TouchMove) 246 return false; 247 248 if (suppressing_touchmoves_) { 249 // Movement with a secondary pointer should terminate suppression. 250 if (event.touchesLength > 1) { 251 suppressing_touchmoves_ = false; 252 } else if (event.touchesLength == 1) { 253 // Movement outside of the slop region should terminate suppression. 254 gfx::PointF position(event.touches[0].position); 255 if ((position - touch_sequence_start_position_).LengthSquared() > 256 slop_suppression_length_dips_squared_) 257 suppressing_touchmoves_ = false; 258 } 259 } 260 return suppressing_touchmoves_; 261 } 262 263 void ConfirmTouchEvent(InputEventAckState ack_result) { 264 if (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED) 265 suppressing_touchmoves_ = false; 266 } 267 268 bool suppressing_touchmoves() const { return suppressing_touchmoves_; } 269 270 private: 271 double slop_suppression_length_dips_squared_; 272 gfx::PointF touch_sequence_start_position_; 273 bool suppressing_touchmoves_; 274 275 DISALLOW_COPY_AND_ASSIGN(TouchMoveSlopSuppressor); 276 }; 277 278 // This class represents a single coalesced touch event. However, it also keeps 279 // track of all the original touch-events that were coalesced into a single 280 // event. The coalesced event is forwarded to the renderer, while the original 281 // touch-events are sent to the Client (on ACK for the coalesced event) so that 282 // the Client receives the event with their original timestamp. 283 class CoalescedWebTouchEvent { 284 public: 285 CoalescedWebTouchEvent(const TouchEventWithLatencyInfo& event, 286 bool suppress_client_ack) 287 : coalesced_event_(event), suppress_client_ack_(suppress_client_ack) { 288 TRACE_EVENT_ASYNC_BEGIN0("input", "TouchEventQueue::QueueEvent", this); 289 } 290 291 ~CoalescedWebTouchEvent() { 292 TRACE_EVENT_ASYNC_END0("input", "TouchEventQueue::QueueEvent", this); 293 } 294 295 // Coalesces the event with the existing event if possible. Returns whether 296 // the event was coalesced. 297 bool CoalesceEventIfPossible( 298 const TouchEventWithLatencyInfo& event_with_latency) { 299 if (suppress_client_ack_) 300 return false; 301 302 if (!coalesced_event_.CanCoalesceWith(event_with_latency)) 303 return false; 304 305 // Addition of the first event to |uncoaleseced_events_to_ack_| is deferred 306 // until the first coalesced event, optimizing the (common) case where the 307 // event is not coalesced at all. 308 if (uncoaleseced_events_to_ack_.empty()) 309 uncoaleseced_events_to_ack_.push_back(coalesced_event_); 310 311 TRACE_EVENT_INSTANT0( 312 "input", "TouchEventQueue::MoveCoalesced", TRACE_EVENT_SCOPE_THREAD); 313 coalesced_event_.CoalesceWith(event_with_latency); 314 uncoaleseced_events_to_ack_.push_back(event_with_latency); 315 DCHECK_GE(uncoaleseced_events_to_ack_.size(), 2U); 316 return true; 317 } 318 319 void DispatchAckToClient(InputEventAckState ack_result, 320 const ui::LatencyInfo* optional_latency_info, 321 TouchEventQueueClient* client) { 322 DCHECK(client); 323 if (suppress_client_ack_) 324 return; 325 326 if (uncoaleseced_events_to_ack_.empty()) { 327 if (optional_latency_info) 328 coalesced_event_.latency.AddNewLatencyFrom(*optional_latency_info); 329 client->OnTouchEventAck(coalesced_event_, ack_result); 330 return; 331 } 332 333 DCHECK_GE(uncoaleseced_events_to_ack_.size(), 2U); 334 for (WebTouchEventWithLatencyList::iterator 335 iter = uncoaleseced_events_to_ack_.begin(), 336 end = uncoaleseced_events_to_ack_.end(); 337 iter != end; 338 ++iter) { 339 if (optional_latency_info) 340 iter->latency.AddNewLatencyFrom(*optional_latency_info); 341 client->OnTouchEventAck(*iter, ack_result); 342 } 343 } 344 345 const TouchEventWithLatencyInfo& coalesced_event() const { 346 return coalesced_event_; 347 } 348 349 private: 350 // This is the event that is forwarded to the renderer. 351 TouchEventWithLatencyInfo coalesced_event_; 352 353 // This is the list of the original events that were coalesced, each requiring 354 // future ack dispatch to the client. 355 // Note that this will be empty if no coalescing has occurred. 356 typedef std::vector<TouchEventWithLatencyInfo> WebTouchEventWithLatencyList; 357 WebTouchEventWithLatencyList uncoaleseced_events_to_ack_; 358 359 bool suppress_client_ack_; 360 361 DISALLOW_COPY_AND_ASSIGN(CoalescedWebTouchEvent); 362 }; 363 364 TouchEventQueue::Config::Config() 365 : touchmove_slop_suppression_length_dips(0), 366 touch_scrolling_mode(TOUCH_SCROLLING_MODE_DEFAULT), 367 touch_ack_timeout_delay(base::TimeDelta::FromMilliseconds(200)), 368 touch_ack_timeout_supported(false) { 369 } 370 371 TouchEventQueue::TouchEventQueue(TouchEventQueueClient* client, 372 const Config& config) 373 : client_(client), 374 dispatching_touch_ack_(NULL), 375 dispatching_touch_(false), 376 has_handlers_(true), 377 drop_remaining_touches_in_sequence_(false), 378 touchmove_slop_suppressor_(new TouchMoveSlopSuppressor( 379 config.touchmove_slop_suppression_length_dips)), 380 send_touch_events_async_(false), 381 needs_async_touchmove_for_outer_slop_region_(false), 382 last_sent_touch_timestamp_sec_(0), 383 touch_scrolling_mode_(config.touch_scrolling_mode) { 384 DCHECK(client); 385 if (config.touch_ack_timeout_supported) { 386 timeout_handler_.reset( 387 new TouchTimeoutHandler(this, config.touch_ack_timeout_delay)); 388 } 389 } 390 391 TouchEventQueue::~TouchEventQueue() { 392 if (!touch_queue_.empty()) 393 STLDeleteElements(&touch_queue_); 394 } 395 396 void TouchEventQueue::QueueEvent(const TouchEventWithLatencyInfo& event) { 397 TRACE_EVENT0("input", "TouchEventQueue::QueueEvent"); 398 399 // If the queueing of |event| was triggered by an ack dispatch, defer 400 // processing the event until the dispatch has finished. 401 if (touch_queue_.empty() && !dispatching_touch_ack_) { 402 // Optimization of the case without touch handlers. Removing this path 403 // yields identical results, but this avoids unnecessary allocations. 404 PreFilterResult filter_result = FilterBeforeForwarding(event.event); 405 if (filter_result != FORWARD_TO_RENDERER) { 406 client_->OnTouchEventAck(event, 407 filter_result == ACK_WITH_NO_CONSUMER_EXISTS 408 ? INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS 409 : INPUT_EVENT_ACK_STATE_NOT_CONSUMED); 410 return; 411 } 412 413 // There is no touch event in the queue. Forward it to the renderer 414 // immediately. 415 touch_queue_.push_back(new CoalescedWebTouchEvent(event, false)); 416 ForwardNextEventToRenderer(); 417 return; 418 } 419 420 // If the last queued touch-event was a touch-move, and the current event is 421 // also a touch-move, then the events can be coalesced into a single event. 422 if (touch_queue_.size() > 1) { 423 CoalescedWebTouchEvent* last_event = touch_queue_.back(); 424 if (last_event->CoalesceEventIfPossible(event)) 425 return; 426 } 427 touch_queue_.push_back(new CoalescedWebTouchEvent(event, false)); 428 } 429 430 void TouchEventQueue::ProcessTouchAck(InputEventAckState ack_result, 431 const LatencyInfo& latency_info) { 432 TRACE_EVENT0("input", "TouchEventQueue::ProcessTouchAck"); 433 434 DCHECK(!dispatching_touch_ack_); 435 dispatching_touch_ = false; 436 437 if (timeout_handler_ && timeout_handler_->ConfirmTouchEvent(ack_result)) 438 return; 439 440 touchmove_slop_suppressor_->ConfirmTouchEvent(ack_result); 441 442 if (touch_queue_.empty()) 443 return; 444 445 PopTouchEventToClient(ack_result, latency_info); 446 TryForwardNextEventToRenderer(); 447 } 448 449 void TouchEventQueue::TryForwardNextEventToRenderer() { 450 DCHECK(!dispatching_touch_ack_); 451 // If there are queued touch events, then try to forward them to the renderer 452 // immediately, or ACK the events back to the client if appropriate. 453 while (!touch_queue_.empty()) { 454 PreFilterResult filter_result = 455 FilterBeforeForwarding(touch_queue_.front()->coalesced_event().event); 456 switch (filter_result) { 457 case ACK_WITH_NO_CONSUMER_EXISTS: 458 PopTouchEventToClient(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS); 459 break; 460 case ACK_WITH_NOT_CONSUMED: 461 PopTouchEventToClient(INPUT_EVENT_ACK_STATE_NOT_CONSUMED); 462 break; 463 case FORWARD_TO_RENDERER: 464 ForwardNextEventToRenderer(); 465 return; 466 } 467 } 468 } 469 470 void TouchEventQueue::ForwardNextEventToRenderer() { 471 TRACE_EVENT0("input", "TouchEventQueue::ForwardNextEventToRenderer"); 472 473 DCHECK(!empty()); 474 DCHECK(!dispatching_touch_); 475 TouchEventWithLatencyInfo touch = touch_queue_.front()->coalesced_event(); 476 477 if (send_touch_events_async_ && 478 touch.event.type == WebInputEvent::TouchMove) { 479 // Throttling touchmove's in a continuous touchmove stream while scrolling 480 // reduces the risk of jank. However, it's still important that the web 481 // application be sent touches at key points in the gesture stream, 482 // e.g., when the application slop region is exceeded or touchmove 483 // coalescing fails because of different modifiers. 484 const bool send_touchmove_now = 485 size() > 1 || 486 (touch.event.timeStampSeconds >= 487 last_sent_touch_timestamp_sec_ + kAsyncTouchMoveIntervalSec) || 488 (needs_async_touchmove_for_outer_slop_region_ && 489 OutsideApplicationSlopRegion(touch.event, 490 touch_sequence_start_position_)) || 491 (pending_async_touchmove_ && 492 !pending_async_touchmove_->CanCoalesceWith(touch)); 493 494 if (!send_touchmove_now) { 495 if (!pending_async_touchmove_) { 496 pending_async_touchmove_.reset(new TouchEventWithLatencyInfo(touch)); 497 } else { 498 DCHECK(pending_async_touchmove_->CanCoalesceWith(touch)); 499 pending_async_touchmove_->CoalesceWith(touch); 500 } 501 DCHECK_EQ(1U, size()); 502 PopTouchEventToClient(INPUT_EVENT_ACK_STATE_NOT_CONSUMED); 503 // It's possible (though unlikely) that ack'ing the current touch will 504 // trigger the queueing of another touch event (e.g., a touchcancel). As 505 // forwarding of the queued event will be deferred while the ack is being 506 // dispatched (see |OnTouchEvent()|), try forwarding it now. 507 TryForwardNextEventToRenderer(); 508 return; 509 } 510 } 511 512 last_sent_touch_timestamp_sec_ = touch.event.timeStampSeconds; 513 514 // Flush any pending async touch move. If it can be combined with the current 515 // (touchmove) event, great, otherwise send it immediately but separately. Its 516 // ack will trigger forwarding of the original |touch| event. 517 if (pending_async_touchmove_) { 518 if (pending_async_touchmove_->CanCoalesceWith(touch)) { 519 pending_async_touchmove_->CoalesceWith(touch); 520 pending_async_touchmove_->event.cancelable = !send_touch_events_async_; 521 touch = *pending_async_touchmove_.Pass(); 522 } else { 523 scoped_ptr<TouchEventWithLatencyInfo> async_move = 524 pending_async_touchmove_.Pass(); 525 async_move->event.cancelable = false; 526 touch_queue_.push_front(new CoalescedWebTouchEvent(*async_move, true)); 527 SendTouchEventImmediately(*async_move); 528 return; 529 } 530 } 531 532 // Note: Marking touchstart events as not-cancelable prevents them from 533 // blocking subsequent gestures, but it may not be the best long term solution 534 // for tracking touch point dispatch. 535 if (send_touch_events_async_) 536 touch.event.cancelable = false; 537 538 // A synchronous ack will reset |dispatching_touch_|, in which case 539 // the touch timeout should not be started. 540 base::AutoReset<bool> dispatching_touch(&dispatching_touch_, true); 541 SendTouchEventImmediately(touch); 542 if (dispatching_touch_ && timeout_handler_) 543 timeout_handler_->StartIfNecessary(touch); 544 } 545 546 void TouchEventQueue::OnGestureScrollEvent( 547 const GestureEventWithLatencyInfo& gesture_event) { 548 if (gesture_event.event.type == blink::WebInputEvent::GestureScrollBegin) { 549 if (!touch_consumer_states_.is_empty() && 550 !drop_remaining_touches_in_sequence_) { 551 DCHECK(!touchmove_slop_suppressor_->suppressing_touchmoves()) 552 << "A touch handler should be offered a touchmove before scrolling."; 553 } 554 if (touch_scrolling_mode_ == TOUCH_SCROLLING_MODE_ASYNC_TOUCHMOVE && 555 !drop_remaining_touches_in_sequence_ && 556 touch_consumer_states_.is_empty()) { 557 // If no touch points have a consumer, prevent all subsequent touch events 558 // received during the scroll from reaching the renderer. This ensures 559 // that the first touchstart the renderer sees in any given sequence can 560 // always be preventDefault'ed (cancelable == true). 561 // TODO(jdduke): Revisit if touchstarts during scroll are made cancelable. 562 drop_remaining_touches_in_sequence_ = true; 563 } 564 565 if (touch_scrolling_mode_ == TOUCH_SCROLLING_MODE_ASYNC_TOUCHMOVE) { 566 needs_async_touchmove_for_outer_slop_region_ = true; 567 pending_async_touchmove_.reset(); 568 } 569 570 return; 571 } 572 573 if (gesture_event.event.type != blink::WebInputEvent::GestureScrollUpdate) 574 return; 575 576 if (touch_scrolling_mode_ == TOUCH_SCROLLING_MODE_ASYNC_TOUCHMOVE) 577 send_touch_events_async_ = true; 578 579 if (touch_scrolling_mode_ != TOUCH_SCROLLING_MODE_TOUCHCANCEL) 580 return; 581 582 // We assume that scroll events are generated synchronously from 583 // dispatching a touch event ack. This allows us to generate a synthetic 584 // cancel event that has the same touch ids as the touch event that 585 // is being acked. Otherwise, we don't perform the touch-cancel optimization. 586 if (!dispatching_touch_ack_) 587 return; 588 589 if (drop_remaining_touches_in_sequence_) 590 return; 591 592 drop_remaining_touches_in_sequence_ = true; 593 594 // Fake a TouchCancel to cancel the touch points of the touch event 595 // that is currently being acked. 596 // Note: |dispatching_touch_ack_| is non-null when we reach here, meaning we 597 // are in the scope of PopTouchEventToClient() and that no touch event 598 // in the queue is waiting for ack from renderer. So we can just insert 599 // the touch cancel at the beginning of the queue. 600 touch_queue_.push_front(new CoalescedWebTouchEvent( 601 ObtainCancelEventForTouchEvent( 602 dispatching_touch_ack_->coalesced_event()), true)); 603 } 604 605 void TouchEventQueue::OnGestureEventAck( 606 const GestureEventWithLatencyInfo& event, 607 InputEventAckState ack_result) { 608 if (touch_scrolling_mode_ != TOUCH_SCROLLING_MODE_ASYNC_TOUCHMOVE) 609 return; 610 611 if (event.event.type != blink::WebInputEvent::GestureScrollUpdate) 612 return; 613 614 // Throttle sending touchmove events as long as the scroll events are handled. 615 // Note that there's no guarantee that this ACK is for the most recent 616 // gesture event (or even part of the current sequence). Worst case, the 617 // delay in updating the absorption state will result in minor UI glitches. 618 // A valid |pending_async_touchmove_| will be flushed when the next event is 619 // forwarded. 620 send_touch_events_async_ = (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED); 621 if (!send_touch_events_async_) 622 needs_async_touchmove_for_outer_slop_region_ = false; 623 } 624 625 void TouchEventQueue::OnHasTouchEventHandlers(bool has_handlers) { 626 DCHECK(!dispatching_touch_ack_); 627 DCHECK(!dispatching_touch_); 628 has_handlers_ = has_handlers; 629 } 630 631 bool TouchEventQueue::IsPendingAckTouchStart() const { 632 DCHECK(!dispatching_touch_ack_); 633 if (touch_queue_.empty()) 634 return false; 635 636 const blink::WebTouchEvent& event = 637 touch_queue_.front()->coalesced_event().event; 638 return (event.type == WebInputEvent::TouchStart); 639 } 640 641 void TouchEventQueue::SetAckTimeoutEnabled(bool enabled) { 642 if (timeout_handler_) 643 timeout_handler_->SetEnabled(enabled); 644 } 645 646 bool TouchEventQueue::IsAckTimeoutEnabled() const { 647 return timeout_handler_ && timeout_handler_->enabled(); 648 } 649 650 bool TouchEventQueue::HasPendingAsyncTouchMoveForTesting() const { 651 return pending_async_touchmove_; 652 } 653 654 bool TouchEventQueue::IsTimeoutRunningForTesting() const { 655 return timeout_handler_ && timeout_handler_->IsTimeoutTimerRunning(); 656 } 657 658 const TouchEventWithLatencyInfo& 659 TouchEventQueue::GetLatestEventForTesting() const { 660 return touch_queue_.back()->coalesced_event(); 661 } 662 663 void TouchEventQueue::FlushQueue() { 664 DCHECK(!dispatching_touch_ack_); 665 DCHECK(!dispatching_touch_); 666 pending_async_touchmove_.reset(); 667 drop_remaining_touches_in_sequence_ = true; 668 while (!touch_queue_.empty()) 669 PopTouchEventToClient(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS); 670 } 671 672 void TouchEventQueue::PopTouchEventToClient(InputEventAckState ack_result) { 673 AckTouchEventToClient(ack_result, PopTouchEvent(), NULL); 674 } 675 676 void TouchEventQueue::PopTouchEventToClient( 677 InputEventAckState ack_result, 678 const LatencyInfo& renderer_latency_info) { 679 AckTouchEventToClient(ack_result, PopTouchEvent(), &renderer_latency_info); 680 } 681 682 void TouchEventQueue::AckTouchEventToClient( 683 InputEventAckState ack_result, 684 scoped_ptr<CoalescedWebTouchEvent> acked_event, 685 const ui::LatencyInfo* optional_latency_info) { 686 DCHECK(acked_event); 687 DCHECK(!dispatching_touch_ack_); 688 UpdateTouchConsumerStates(acked_event->coalesced_event().event, ack_result); 689 690 // Note that acking the touch-event may result in multiple gestures being sent 691 // to the renderer, or touch-events being queued. 692 base::AutoReset<const CoalescedWebTouchEvent*> dispatching_touch_ack( 693 &dispatching_touch_ack_, acked_event.get()); 694 acked_event->DispatchAckToClient(ack_result, optional_latency_info, client_); 695 } 696 697 scoped_ptr<CoalescedWebTouchEvent> TouchEventQueue::PopTouchEvent() { 698 DCHECK(!touch_queue_.empty()); 699 scoped_ptr<CoalescedWebTouchEvent> event(touch_queue_.front()); 700 touch_queue_.pop_front(); 701 return event.Pass(); 702 } 703 704 void TouchEventQueue::SendTouchEventImmediately( 705 const TouchEventWithLatencyInfo& touch) { 706 if (needs_async_touchmove_for_outer_slop_region_) { 707 // Any event other than a touchmove (e.g., touchcancel or secondary 708 // touchstart) after a scroll has started will interrupt the need to send a 709 // an outer slop-region exceeding touchmove. 710 if (touch.event.type != WebInputEvent::TouchMove || 711 OutsideApplicationSlopRegion(touch.event, 712 touch_sequence_start_position_)) 713 needs_async_touchmove_for_outer_slop_region_ = false; 714 } 715 716 client_->SendTouchEventImmediately(touch); 717 } 718 719 TouchEventQueue::PreFilterResult 720 TouchEventQueue::FilterBeforeForwarding(const WebTouchEvent& event) { 721 if (timeout_handler_ && timeout_handler_->FilterEvent(event)) 722 return ACK_WITH_NO_CONSUMER_EXISTS; 723 724 if (touchmove_slop_suppressor_->FilterEvent(event)) 725 return ACK_WITH_NOT_CONSUMED; 726 727 if (WebTouchEventTraits::IsTouchSequenceStart(event)) { 728 touch_consumer_states_.clear(); 729 send_touch_events_async_ = false; 730 pending_async_touchmove_.reset(); 731 touch_sequence_start_position_ = gfx::PointF(event.touches[0].position); 732 drop_remaining_touches_in_sequence_ = false; 733 if (!has_handlers_) { 734 drop_remaining_touches_in_sequence_ = true; 735 return ACK_WITH_NO_CONSUMER_EXISTS; 736 } 737 } 738 739 if (drop_remaining_touches_in_sequence_ && 740 event.type != WebInputEvent::TouchCancel) { 741 return ACK_WITH_NO_CONSUMER_EXISTS; 742 } 743 744 if (event.type == WebInputEvent::TouchStart) 745 return has_handlers_ ? FORWARD_TO_RENDERER : ACK_WITH_NO_CONSUMER_EXISTS; 746 747 for (unsigned int i = 0; i < event.touchesLength; ++i) { 748 const WebTouchPoint& point = event.touches[i]; 749 // If a point has been stationary, then don't take it into account. 750 if (point.state == WebTouchPoint::StateStationary) 751 continue; 752 753 if (touch_consumer_states_.has_bit(point.id)) 754 return FORWARD_TO_RENDERER; 755 } 756 757 return ACK_WITH_NO_CONSUMER_EXISTS; 758 } 759 760 void TouchEventQueue::UpdateTouchConsumerStates(const WebTouchEvent& event, 761 InputEventAckState ack_result) { 762 // Update the ACK status for each touch point in the ACKed event. 763 if (event.type == WebInputEvent::TouchEnd || 764 event.type == WebInputEvent::TouchCancel) { 765 // The points have been released. Erase the ACK states. 766 for (unsigned i = 0; i < event.touchesLength; ++i) { 767 const WebTouchPoint& point = event.touches[i]; 768 if (point.state == WebTouchPoint::StateReleased || 769 point.state == WebTouchPoint::StateCancelled) 770 touch_consumer_states_.clear_bit(point.id); 771 } 772 } else if (event.type == WebInputEvent::TouchStart) { 773 for (unsigned i = 0; i < event.touchesLength; ++i) { 774 const WebTouchPoint& point = event.touches[i]; 775 if (point.state == WebTouchPoint::StatePressed) { 776 if (ack_result != INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS) 777 touch_consumer_states_.mark_bit(point.id); 778 else 779 touch_consumer_states_.clear_bit(point.id); 780 } 781 } 782 } 783 } 784 785 } // namespace content 786