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