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