// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "content/browser/renderer_host/input/touch_event_queue.h" #include #include "base/auto_reset.h" #include "base/macros.h" #include "base/metrics/histogram_macros.h" #include "base/stl_util.h" #include "base/trace_event/trace_event.h" #include "content/browser/renderer_host/input/timeout_monitor.h" #include "content/common/input/web_touch_event_traits.h" #include "ui/gfx/geometry/point_f.h" using blink::WebInputEvent; using blink::WebTouchEvent; using blink::WebTouchPoint; using ui::LatencyInfo; namespace content { namespace { // Time interval at which touchmove events will be forwarded to the client while // scrolling is active and possible. const double kAsyncTouchMoveIntervalSec = .2; // A sanity check on touches received to ensure that touch movement outside // the platform slop region will cause scrolling, as indicated by the event's // |causesScrollingIfUncanceled| bit. const double kMaxConceivablePlatformSlopRegionLengthDipsSquared = 60. * 60.; TouchEventWithLatencyInfo ObtainCancelEventForTouchEvent( const TouchEventWithLatencyInfo& event_to_cancel) { TouchEventWithLatencyInfo event = event_to_cancel; WebTouchEventTraits::ResetTypeAndTouchStates( WebInputEvent::TouchCancel, // TODO(rbyers): Shouldn't we use a fresh timestamp? event.event.timeStampSeconds, &event.event); return event; } bool ShouldTouchTriggerTimeout(const WebTouchEvent& event) { return (event.type == WebInputEvent::TouchStart || event.type == WebInputEvent::TouchMove) && WebInputEventTraits::WillReceiveAckFromRenderer(event) && event.cancelable; } // Compare all properties of touch points to determine the state. bool HasPointChanged(const WebTouchPoint& point_1, const WebTouchPoint& point_2) { DCHECK_EQ(point_1.id, point_2.id); if (point_1.screenPosition != point_2.screenPosition || point_1.position != point_2.position || point_1.radiusX != point_2.radiusX || point_1.radiusY != point_2.radiusY || point_1.rotationAngle != point_2.rotationAngle || point_1.force != point_2.force || point_1.tiltX != point_2.tiltX || point_1.tiltY != point_2.tiltY) { return true; } return false; } } // namespace // Cancels a touch sequence if a touchstart or touchmove ack response is // sufficiently delayed. class TouchEventQueue::TouchTimeoutHandler { public: TouchTimeoutHandler(TouchEventQueue* touch_queue, base::TimeDelta desktop_timeout_delay, base::TimeDelta mobile_timeout_delay) : touch_queue_(touch_queue), desktop_timeout_delay_(desktop_timeout_delay), mobile_timeout_delay_(mobile_timeout_delay), use_mobile_timeout_(false), pending_ack_state_(PENDING_ACK_NONE), timeout_monitor_(base::Bind(&TouchTimeoutHandler::OnTimeOut, base::Unretained(this))), enabled_(true), enabled_for_current_sequence_(false), sequence_awaiting_uma_update_(false), sequence_using_mobile_timeout_(false) { SetUseMobileTimeout(false); } ~TouchTimeoutHandler() { LogSequenceEndForUMAIfNecessary(false); } void StartIfNecessary(const TouchEventWithLatencyInfo& event) { if (pending_ack_state_ != PENDING_ACK_NONE) return; if (!enabled_) return; const base::TimeDelta timeout_delay = GetTimeoutDelay(); if (timeout_delay.is_zero()) return; if (!ShouldTouchTriggerTimeout(event.event)) return; if (WebTouchEventTraits::IsTouchSequenceStart(event.event)) { LogSequenceStartForUMA(); enabled_for_current_sequence_ = true; } if (!enabled_for_current_sequence_) return; timeout_event_ = event; timeout_monitor_.Restart(timeout_delay); } bool ConfirmTouchEvent(InputEventAckState ack_result) { switch (pending_ack_state_) { case PENDING_ACK_NONE: if (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED) enabled_for_current_sequence_ = false; timeout_monitor_.Stop(); return false; case PENDING_ACK_ORIGINAL_EVENT: if (AckedTimeoutEventRequiresCancel(ack_result)) { SetPendingAckState(PENDING_ACK_CANCEL_EVENT); TouchEventWithLatencyInfo cancel_event = ObtainCancelEventForTouchEvent(timeout_event_); touch_queue_->SendTouchEventImmediately(&cancel_event); } else { SetPendingAckState(PENDING_ACK_NONE); touch_queue_->UpdateTouchConsumerStates(timeout_event_.event, ack_result); } return true; case PENDING_ACK_CANCEL_EVENT: SetPendingAckState(PENDING_ACK_NONE); return true; } return false; } bool FilterEvent(const WebTouchEvent& event) { if (!HasTimeoutEvent()) return false; if (WebTouchEventTraits::IsTouchSequenceStart(event)) { // If a new sequence is observed while we're still waiting on the // timed-out sequence response, also count the new sequence as timed-out. LogSequenceStartForUMA(); LogSequenceEndForUMAIfNecessary(true); } return true; } void SetEnabled(bool enabled) { if (enabled_ == enabled) return; enabled_ = enabled; if (enabled_) return; enabled_for_current_sequence_ = false; // Only reset the |timeout_handler_| if the timer is running and has not // yet timed out. This ensures that an already timed out sequence is // properly flushed by the handler. if (IsTimeoutTimerRunning()) { pending_ack_state_ = PENDING_ACK_NONE; timeout_monitor_.Stop(); } } void SetUseMobileTimeout(bool use_mobile_timeout) { use_mobile_timeout_ = use_mobile_timeout; } bool IsTimeoutTimerRunning() const { return timeout_monitor_.IsRunning(); } bool IsEnabled() const { return enabled_ && !GetTimeoutDelay().is_zero(); } private: enum PendingAckState { PENDING_ACK_NONE, PENDING_ACK_ORIGINAL_EVENT, PENDING_ACK_CANCEL_EVENT, }; void OnTimeOut() { LogSequenceEndForUMAIfNecessary(true); SetPendingAckState(PENDING_ACK_ORIGINAL_EVENT); touch_queue_->FlushQueue(); } // Skip a cancel event if the timed-out event had no consumer and was the // initial event in the gesture. bool AckedTimeoutEventRequiresCancel(InputEventAckState ack_result) const { DCHECK(HasTimeoutEvent()); if (ack_result != INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS) return true; return !WebTouchEventTraits::IsTouchSequenceStart(timeout_event_.event); } void SetPendingAckState(PendingAckState new_pending_ack_state) { DCHECK_NE(pending_ack_state_, new_pending_ack_state); switch (new_pending_ack_state) { case PENDING_ACK_ORIGINAL_EVENT: DCHECK_EQ(pending_ack_state_, PENDING_ACK_NONE); TRACE_EVENT_ASYNC_BEGIN0("input", "TouchEventTimeout", this); break; case PENDING_ACK_CANCEL_EVENT: DCHECK_EQ(pending_ack_state_, PENDING_ACK_ORIGINAL_EVENT); DCHECK(!timeout_monitor_.IsRunning()); DCHECK(touch_queue_->empty()); TRACE_EVENT_ASYNC_STEP_INTO0( "input", "TouchEventTimeout", this, "CancelEvent"); break; case PENDING_ACK_NONE: DCHECK(!timeout_monitor_.IsRunning()); DCHECK(touch_queue_->empty()); TRACE_EVENT_ASYNC_END0("input", "TouchEventTimeout", this); break; } pending_ack_state_ = new_pending_ack_state; } void LogSequenceStartForUMA() { // Always flush any unlogged entries before starting a new one. LogSequenceEndForUMAIfNecessary(false); sequence_awaiting_uma_update_ = true; sequence_using_mobile_timeout_ = use_mobile_timeout_; } void LogSequenceEndForUMAIfNecessary(bool timed_out) { if (!sequence_awaiting_uma_update_) return; sequence_awaiting_uma_update_ = false; if (sequence_using_mobile_timeout_) { UMA_HISTOGRAM_BOOLEAN("Event.Touch.TimedOutOnMobileSite", timed_out); } else { UMA_HISTOGRAM_BOOLEAN("Event.Touch.TimedOutOnDesktopSite", timed_out); } } base::TimeDelta GetTimeoutDelay() const { return use_mobile_timeout_ ? mobile_timeout_delay_ : desktop_timeout_delay_; } bool HasTimeoutEvent() const { return pending_ack_state_ != PENDING_ACK_NONE; } TouchEventQueue* touch_queue_; // How long to wait on a touch ack before cancelling the touch sequence. const base::TimeDelta desktop_timeout_delay_; const base::TimeDelta mobile_timeout_delay_; bool use_mobile_timeout_; // The touch event source for which we expect the next ack. PendingAckState pending_ack_state_; // The event for which the ack timeout is triggered. TouchEventWithLatencyInfo timeout_event_; // Provides timeout-based callback behavior. TimeoutMonitor timeout_monitor_; bool enabled_; bool enabled_for_current_sequence_; // Bookkeeping to classify and log whether a touch sequence times out. bool sequence_awaiting_uma_update_; bool sequence_using_mobile_timeout_; }; // Provides touchmove slop suppression for a touch sequence until a // (unprevented) touch will trigger immediate scrolling. class TouchEventQueue::TouchMoveSlopSuppressor { public: TouchMoveSlopSuppressor() : suppressing_touchmoves_(false) {} bool FilterEvent(const WebTouchEvent& event) { if (WebTouchEventTraits::IsTouchSequenceStart(event)) { suppressing_touchmoves_ = true; touch_start_location_ = gfx::PointF(event.touches[0].position); } if (event.type == WebInputEvent::TouchEnd || event.type == WebInputEvent::TouchCancel) suppressing_touchmoves_ = false; if (event.type != WebInputEvent::TouchMove) return false; if (suppressing_touchmoves_) { if (event.touchesLength > 1) { suppressing_touchmoves_ = false; } else if (event.movedBeyondSlopRegion) { suppressing_touchmoves_ = false; } else { // No sane slop region should be larger than 60 DIPs. DCHECK_LT((gfx::PointF(event.touches[0].position) - touch_start_location_).LengthSquared(), kMaxConceivablePlatformSlopRegionLengthDipsSquared); } } return suppressing_touchmoves_; } void ConfirmTouchEvent(InputEventAckState ack_result) { if (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED) suppressing_touchmoves_ = false; } bool suppressing_touchmoves() const { return suppressing_touchmoves_; } private: bool suppressing_touchmoves_; // Sanity check that the upstream touch provider is properly reporting whether // the touch sequence will cause scrolling. gfx::PointF touch_start_location_; DISALLOW_COPY_AND_ASSIGN(TouchMoveSlopSuppressor); }; // This class represents a single coalesced touch event. However, it also keeps // track of all the original touch-events that were coalesced into a single // event. The coalesced event is forwarded to the renderer, while the original // touch-events are sent to the Client (on ACK for the coalesced event) so that // the Client receives the event with their original timestamp. class CoalescedWebTouchEvent { public: CoalescedWebTouchEvent(const TouchEventWithLatencyInfo& event, bool suppress_client_ack) : coalesced_event_(event), suppress_client_ack_(suppress_client_ack) { TRACE_EVENT_ASYNC_BEGIN0("input", "TouchEventQueue::QueueEvent", this); } ~CoalescedWebTouchEvent() { TRACE_EVENT_ASYNC_END0("input", "TouchEventQueue::QueueEvent", this); } // Coalesces the event with the existing event if possible. Returns whether // the event was coalesced. bool CoalesceEventIfPossible( const TouchEventWithLatencyInfo& event_with_latency) { if (suppress_client_ack_) return false; if (!coalesced_event_.CanCoalesceWith(event_with_latency)) return false; // Addition of the first event to |uncoaleseced_events_to_ack_| is deferred // until the first coalesced event, optimizing the (common) case where the // event is not coalesced at all. if (uncoaleseced_events_to_ack_.empty()) uncoaleseced_events_to_ack_.push_back(coalesced_event_); TRACE_EVENT_INSTANT0( "input", "TouchEventQueue::MoveCoalesced", TRACE_EVENT_SCOPE_THREAD); coalesced_event_.CoalesceWith(event_with_latency); uncoaleseced_events_to_ack_.push_back(event_with_latency); DCHECK_GE(uncoaleseced_events_to_ack_.size(), 2U); return true; } void DispatchAckToClient(InputEventAckState ack_result, const ui::LatencyInfo* optional_latency_info, TouchEventQueueClient* client) { DCHECK(client); if (suppress_client_ack_) return; if (uncoaleseced_events_to_ack_.empty()) { if (optional_latency_info) coalesced_event_.latency.AddNewLatencyFrom(*optional_latency_info); client->OnTouchEventAck(coalesced_event_, ack_result); return; } DCHECK_GE(uncoaleseced_events_to_ack_.size(), 2U); for (WebTouchEventWithLatencyList::iterator iter = uncoaleseced_events_to_ack_.begin(), end = uncoaleseced_events_to_ack_.end(); iter != end; ++iter) { if (optional_latency_info) iter->latency.AddNewLatencyFrom(*optional_latency_info); client->OnTouchEventAck(*iter, ack_result); } } const TouchEventWithLatencyInfo& coalesced_event() const { return coalesced_event_; } private: // This is the event that is forwarded to the renderer. TouchEventWithLatencyInfo coalesced_event_; // This is the list of the original events that were coalesced, each requiring // future ack dispatch to the client. // Note that this will be empty if no coalescing has occurred. typedef std::vector WebTouchEventWithLatencyList; WebTouchEventWithLatencyList uncoaleseced_events_to_ack_; bool suppress_client_ack_; DISALLOW_COPY_AND_ASSIGN(CoalescedWebTouchEvent); }; TouchEventQueue::Config::Config() : desktop_touch_ack_timeout_delay(base::TimeDelta::FromMilliseconds(200)), mobile_touch_ack_timeout_delay(base::TimeDelta::FromMilliseconds(1000)), touch_ack_timeout_supported(false) { } TouchEventQueue::TouchEventQueue(TouchEventQueueClient* client, const Config& config) : client_(client), dispatching_touch_ack_(false), dispatching_touch_(false), has_handlers_(true), has_handler_for_current_sequence_(false), drop_remaining_touches_in_sequence_(false), touchmove_slop_suppressor_(new TouchMoveSlopSuppressor), send_touch_events_async_(false), last_sent_touch_timestamp_sec_(0) { DCHECK(client); if (config.touch_ack_timeout_supported) { timeout_handler_.reset( new TouchTimeoutHandler(this, config.desktop_touch_ack_timeout_delay, config.mobile_touch_ack_timeout_delay)); } } TouchEventQueue::~TouchEventQueue() { if (!touch_queue_.empty()) STLDeleteElements(&touch_queue_); } void TouchEventQueue::QueueEvent(const TouchEventWithLatencyInfo& event) { TRACE_EVENT0("input", "TouchEventQueue::QueueEvent"); // If the queueing of |event| was triggered by an ack dispatch, defer // processing the event until the dispatch has finished. if (touch_queue_.empty() && !dispatching_touch_ack_) { // Optimization of the case without touch handlers. Removing this path // yields identical results, but this avoids unnecessary allocations. PreFilterResult filter_result = FilterBeforeForwarding(event.event); if (filter_result != FORWARD_TO_RENDERER) { client_->OnFilteringTouchEvent(event.event); client_->OnTouchEventAck(event, filter_result == ACK_WITH_NO_CONSUMER_EXISTS ? INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS : INPUT_EVENT_ACK_STATE_NOT_CONSUMED); return; } // There is no touch event in the queue. Forward it to the renderer // immediately. touch_queue_.push_back(new CoalescedWebTouchEvent(event, false)); ForwardNextEventToRenderer(); return; } // If the last queued touch-event was a touch-move, and the current event is // also a touch-move, then the events can be coalesced into a single event. if (touch_queue_.size() > 1) { CoalescedWebTouchEvent* last_event = touch_queue_.back(); if (last_event->CoalesceEventIfPossible(event)) return; } touch_queue_.push_back(new CoalescedWebTouchEvent(event, false)); } void TouchEventQueue::ProcessTouchAck(InputEventAckState ack_result, const LatencyInfo& latency_info, const uint32_t unique_touch_event_id) { TRACE_EVENT0("input", "TouchEventQueue::ProcessTouchAck"); // We receive an ack for async touchmove from render. if (!ack_pending_async_touchmove_ids_.empty() && ack_pending_async_touchmove_ids_.front() == unique_touch_event_id) { // Remove the first touchmove from the ack_pending_async_touchmove queue. ack_pending_async_touchmove_ids_.pop_front(); // Send the next pending async touch move once we receive all acks back. if (pending_async_touchmove_ && ack_pending_async_touchmove_ids_.empty()) { DCHECK(touch_queue_.empty()); // Dispatch the next pending async touch move when time expires. if (pending_async_touchmove_->event.timeStampSeconds >= last_sent_touch_timestamp_sec_ + kAsyncTouchMoveIntervalSec) { FlushPendingAsyncTouchmove(); } } return; } DCHECK(!dispatching_touch_ack_); dispatching_touch_ = false; if (timeout_handler_ && timeout_handler_->ConfirmTouchEvent(ack_result)) return; touchmove_slop_suppressor_->ConfirmTouchEvent(ack_result); if (touch_queue_.empty()) return; DCHECK_EQ(touch_queue_.front()->coalesced_event().event.uniqueTouchEventId, unique_touch_event_id); PopTouchEventToClient(ack_result, latency_info); TryForwardNextEventToRenderer(); } void TouchEventQueue::TryForwardNextEventToRenderer() { DCHECK(!dispatching_touch_ack_); // If there are queued touch events, then try to forward them to the renderer // immediately, or ACK the events back to the client if appropriate. while (!touch_queue_.empty()) { const WebTouchEvent& event = touch_queue_.front()->coalesced_event().event; PreFilterResult filter_result = FilterBeforeForwarding(event); if (filter_result != FORWARD_TO_RENDERER) client_->OnFilteringTouchEvent(event); switch (filter_result) { case ACK_WITH_NO_CONSUMER_EXISTS: PopTouchEventToClient(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS); break; case ACK_WITH_NOT_CONSUMED: PopTouchEventToClient(INPUT_EVENT_ACK_STATE_NOT_CONSUMED); break; case FORWARD_TO_RENDERER: ForwardNextEventToRenderer(); return; } } } void TouchEventQueue::ForwardNextEventToRenderer() { TRACE_EVENT0("input", "TouchEventQueue::ForwardNextEventToRenderer"); DCHECK(!empty()); DCHECK(!dispatching_touch_); TouchEventWithLatencyInfo touch = touch_queue_.front()->coalesced_event(); if (send_touch_events_async_ && touch.event.type == WebInputEvent::TouchMove) { // Throttling touchmove's in a continuous touchmove stream while scrolling // reduces the risk of jank. However, it's still important that the web // application be sent touches at key points in the gesture stream, // e.g., when the application slop region is exceeded or touchmove // coalescing fails because of different modifiers. bool send_touchmove_now = size() > 1; send_touchmove_now |= pending_async_touchmove_ && !pending_async_touchmove_->CanCoalesceWith(touch); send_touchmove_now |= ack_pending_async_touchmove_ids_.empty() && (touch.event.timeStampSeconds >= last_sent_touch_timestamp_sec_ + kAsyncTouchMoveIntervalSec); if (!send_touchmove_now) { if (!pending_async_touchmove_) { pending_async_touchmove_.reset(new TouchEventWithLatencyInfo(touch)); } else { DCHECK(pending_async_touchmove_->CanCoalesceWith(touch)); pending_async_touchmove_->CoalesceWith(touch); } DCHECK_EQ(1U, size()); PopTouchEventToClient(INPUT_EVENT_ACK_STATE_NOT_CONSUMED); // It's possible (though unlikely) that ack'ing the current touch will // trigger the queueing of another touch event (e.g., a touchcancel). As // forwarding of the queued event will be deferred while the ack is being // dispatched (see |OnTouchEvent()|), try forwarding it now. TryForwardNextEventToRenderer(); return; } } last_sent_touch_timestamp_sec_ = touch.event.timeStampSeconds; // Flush any pending async touch move. If it can be combined with the current // (touchmove) event, great, otherwise send it immediately but separately. Its // ack will trigger forwarding of the original |touch| event. if (pending_async_touchmove_) { if (pending_async_touchmove_->CanCoalesceWith(touch)) { pending_async_touchmove_->CoalesceWith(touch); pending_async_touchmove_->event.cancelable = !send_touch_events_async_; touch = *pending_async_touchmove_; pending_async_touchmove_.reset(); } else { FlushPendingAsyncTouchmove(); return; } } // Note: Touchstart events are marked cancelable to allow transitions between // platform scrolling and JS pinching. Touchend events, however, remain // uncancelable, mitigating the risk of jank when transitioning to a fling. if (send_touch_events_async_ && touch.event.type != WebInputEvent::TouchStart) touch.event.cancelable = false; SendTouchEventImmediately(&touch); } void TouchEventQueue::FlushPendingAsyncTouchmove() { DCHECK(!dispatching_touch_); scoped_ptr touch = std::move(pending_async_touchmove_); touch->event.cancelable = false; touch_queue_.push_front(new CoalescedWebTouchEvent(*touch, true)); SendTouchEventImmediately(touch.get()); } void TouchEventQueue::OnGestureScrollEvent( const GestureEventWithLatencyInfo& gesture_event) { if (gesture_event.event.type == blink::WebInputEvent::GestureScrollBegin) { if (has_handler_for_current_sequence_ && !drop_remaining_touches_in_sequence_) { DCHECK(!touchmove_slop_suppressor_->suppressing_touchmoves()) << "A touch handler should be offered a touchmove before scrolling."; } pending_async_touchmove_.reset(); return; } if (gesture_event.event.type == blink::WebInputEvent::GestureScrollUpdate && gesture_event.event.resendingPluginId == -1) { send_touch_events_async_ = true; } } void TouchEventQueue::OnGestureEventAck( const GestureEventWithLatencyInfo& event, InputEventAckState ack_result) { // Throttle sending touchmove events as long as the scroll events are handled. // Note that there's no guarantee that this ACK is for the most recent // gesture event (or even part of the current sequence). Worst case, the // delay in updating the absorption state will result in minor UI glitches. // A valid |pending_async_touchmove_| will be flushed when the next event is // forwarded. Scroll updates that are being resent from a GuestView are // ignored. if (event.event.type == blink::WebInputEvent::GestureScrollUpdate && event.event.resendingPluginId == -1) { send_touch_events_async_ = (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED); } } void TouchEventQueue::OnHasTouchEventHandlers(bool has_handlers) { DCHECK(!dispatching_touch_ack_); DCHECK(!dispatching_touch_); has_handlers_ = has_handlers; } bool TouchEventQueue::IsPendingAckTouchStart() const { DCHECK(!dispatching_touch_ack_); if (touch_queue_.empty()) return false; const blink::WebTouchEvent& event = touch_queue_.front()->coalesced_event().event; return (event.type == WebInputEvent::TouchStart); } void TouchEventQueue::SetAckTimeoutEnabled(bool enabled) { if (timeout_handler_) timeout_handler_->SetEnabled(enabled); } void TouchEventQueue::SetIsMobileOptimizedSite(bool mobile_optimized_site) { if (timeout_handler_) timeout_handler_->SetUseMobileTimeout(mobile_optimized_site); } bool TouchEventQueue::IsAckTimeoutEnabled() const { return timeout_handler_ && timeout_handler_->IsEnabled(); } bool TouchEventQueue::HasPendingAsyncTouchMoveForTesting() const { return !!pending_async_touchmove_; } bool TouchEventQueue::IsTimeoutRunningForTesting() const { return timeout_handler_ && timeout_handler_->IsTimeoutTimerRunning(); } const TouchEventWithLatencyInfo& TouchEventQueue::GetLatestEventForTesting() const { return touch_queue_.back()->coalesced_event(); } void TouchEventQueue::FlushQueue() { DCHECK(!dispatching_touch_ack_); DCHECK(!dispatching_touch_); pending_async_touchmove_.reset(); drop_remaining_touches_in_sequence_ = true; while (!touch_queue_.empty()) PopTouchEventToClient(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS); } void TouchEventQueue::PopTouchEventToClient(InputEventAckState ack_result) { AckTouchEventToClient(ack_result, PopTouchEvent(), nullptr); } void TouchEventQueue::PopTouchEventToClient( InputEventAckState ack_result, const LatencyInfo& renderer_latency_info) { AckTouchEventToClient(ack_result, PopTouchEvent(), &renderer_latency_info); } void TouchEventQueue::AckTouchEventToClient( InputEventAckState ack_result, scoped_ptr acked_event, const ui::LatencyInfo* optional_latency_info) { DCHECK(acked_event); DCHECK(!dispatching_touch_ack_); UpdateTouchConsumerStates(acked_event->coalesced_event().event, ack_result); // Note that acking the touch-event may result in multiple gestures being sent // to the renderer, or touch-events being queued. base::AutoReset dispatching_touch_ack(&dispatching_touch_ack_, true); acked_event->DispatchAckToClient(ack_result, optional_latency_info, client_); } scoped_ptr TouchEventQueue::PopTouchEvent() { DCHECK(!touch_queue_.empty()); scoped_ptr event(touch_queue_.front()); touch_queue_.pop_front(); return event; } void TouchEventQueue::SendTouchEventImmediately( TouchEventWithLatencyInfo* touch) { // For touchmove events, compare touch points position from current event // to last sent event and update touch points state. if (touch->event.type == WebInputEvent::TouchMove) { CHECK(last_sent_touchevent_); for (unsigned int i = 0; i < last_sent_touchevent_->touchesLength; ++i) { const WebTouchPoint& last_touch_point = last_sent_touchevent_->touches[i]; // Touches with same id may not have same index in Touches array. for (unsigned int j = 0; j < touch->event.touchesLength; ++j) { const WebTouchPoint& current_touchmove_point = touch->event.touches[j]; if (current_touchmove_point.id != last_touch_point.id) continue; if (!HasPointChanged(last_touch_point, current_touchmove_point)) touch->event.touches[j].state = WebTouchPoint::StateStationary; break; } } } if (last_sent_touchevent_) *last_sent_touchevent_ = touch->event; else last_sent_touchevent_.reset(new WebTouchEvent(touch->event)); base::AutoReset dispatching_touch(&dispatching_touch_, true); client_->SendTouchEventImmediately(*touch); // A synchronous ack will reset |dispatching_touch_|, in which case the touch // timeout should not be started and the count also should not be increased. if (dispatching_touch_) { if (touch->event.type == WebInputEvent::TouchMove && !touch->event.cancelable) { // When we send out a uncancelable touch move, we increase the count and // we do not process input event ack any more, we will just ack to client // and wait for the ack from render. Also we will remove it from the front // of the queue. ack_pending_async_touchmove_ids_.push_back( touch->event.uniqueTouchEventId); dispatching_touch_ = false; PopTouchEventToClient(INPUT_EVENT_ACK_STATE_IGNORED); TryForwardNextEventToRenderer(); return; } if (timeout_handler_) timeout_handler_->StartIfNecessary(*touch); } } TouchEventQueue::PreFilterResult TouchEventQueue::FilterBeforeForwarding(const WebTouchEvent& event) { if (WebTouchEventTraits::IsTouchSequenceStart(event)) { has_handler_for_current_sequence_ = false; send_touch_events_async_ = false; pending_async_touchmove_.reset(); last_sent_touchevent_.reset(); touch_sequence_start_position_ = gfx::PointF(event.touches[0].position); drop_remaining_touches_in_sequence_ = false; if (!has_handlers_) { drop_remaining_touches_in_sequence_ = true; return ACK_WITH_NO_CONSUMER_EXISTS; } } if (timeout_handler_ && timeout_handler_->FilterEvent(event)) return ACK_WITH_NO_CONSUMER_EXISTS; if (touchmove_slop_suppressor_->FilterEvent(event)) return ACK_WITH_NOT_CONSUMED; if (drop_remaining_touches_in_sequence_ && event.type != WebInputEvent::TouchCancel) { return ACK_WITH_NO_CONSUMER_EXISTS; } if (event.type == WebInputEvent::TouchStart) { return (has_handlers_ || has_handler_for_current_sequence_) ? FORWARD_TO_RENDERER : ACK_WITH_NO_CONSUMER_EXISTS; } if (has_handler_for_current_sequence_) { // Only forward a touch if it has a non-stationary pointer that is active // in the current touch sequence. for (size_t i = 0; i < event.touchesLength; ++i) { const WebTouchPoint& point = event.touches[i]; if (point.state == WebTouchPoint::StateStationary) continue; // |last_sent_touchevent_| will be non-null as long as there is an // active touch sequence being forwarded to the renderer. if (!last_sent_touchevent_) continue; for (size_t j = 0; j < last_sent_touchevent_->touchesLength; ++j) { if (point.id != last_sent_touchevent_->touches[j].id) continue; if (event.type != WebInputEvent::TouchMove) return FORWARD_TO_RENDERER; // All pointers in TouchMove events may have state as StateMoved, // even though none of the pointers have not changed in real. // Forward these events when at least one pointer has changed. if (HasPointChanged(last_sent_touchevent_->touches[j], point)) return FORWARD_TO_RENDERER; // This is a TouchMove event for which we have yet to find a // non-stationary pointer. Continue checking the next pointers // in the |event|. break; } } } return ACK_WITH_NO_CONSUMER_EXISTS; } void TouchEventQueue::UpdateTouchConsumerStates(const WebTouchEvent& event, InputEventAckState ack_result) { if (event.type == WebInputEvent::TouchStart) { if (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED) send_touch_events_async_ = false; has_handler_for_current_sequence_ |= ack_result != INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS; } else if (WebTouchEventTraits::IsTouchSequenceEnd(event)) { has_handler_for_current_sequence_ = false; } } } // namespace content