Index: content/browser/renderer_host/input/touch_event_queue.cc |
diff --git a/content/browser/renderer_host/input/touch_event_queue.cc b/content/browser/renderer_host/input/touch_event_queue.cc |
index 4ea8be040660bc364ef6beabb4e1840b92b0ef5a..3524b18250e1cb22162b755122230d8e5affd38d 100644 |
--- a/content/browser/renderer_host/input/touch_event_queue.cc |
+++ b/content/browser/renderer_host/input/touch_event_queue.cc |
@@ -5,13 +5,165 @@ |
#include "content/browser/renderer_host/input/touch_event_queue.h" |
#include "base/auto_reset.h" |
+#include "base/command_line.h" |
#include "base/debug/trace_event.h" |
#include "base/stl_util.h" |
+#include "content/browser/renderer_host/input/timeout_monitor.h" |
+#include "content/common/input/web_input_event_traits.h" |
+#include "content/public/common/content_switches.h" |
+ |
+using blink::WebInputEvent; |
+using blink::WebTouchEvent; |
+using blink::WebTouchPoint; |
namespace content { |
+namespace { |
+ |
+const InputEventAckState kDefaultNotForwardedAck = |
+ INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS; |
typedef std::vector<TouchEventWithLatencyInfo> WebTouchEventWithLatencyList; |
+TouchEventWithLatencyInfo ObtainCancelEventForTouchEvent( |
+ const TouchEventWithLatencyInfo& event_to_cancel) { |
+ TouchEventWithLatencyInfo event = event_to_cancel; |
+ event.event.type = WebInputEvent::TouchCancel; |
+ for (size_t i = 0; i < event.event.touchesLength; i++) |
+ event.event.touches[i].state = WebTouchPoint::StateCancelled; |
+ return event; |
+} |
+ |
+bool IsNewTouchGesture(const WebTouchEvent& event) { |
+ if (event.type != WebInputEvent::TouchStart) |
+ return false; |
+ if (!event.touchesLength) |
+ return false; |
+ for (size_t i = 0; i < event.touchesLength; i++) { |
+ if (event.touches[i].state != WebTouchPoint::StatePressed) |
+ return false; |
+ } |
+ return true; |
+} |
+ |
+bool ShouldTouchTypeTriggerTimeout(WebInputEvent::Type type) { |
+ return type == WebInputEvent::TouchStart || |
+ type == WebInputEvent::TouchMove; |
+} |
+ |
+} // namespace |
+ |
+class TouchEventQueue::TouchTimeoutHandler { |
+ public: |
+ TouchTimeoutHandler(TouchEventQueue* touch_queue, size_t timeout_delay_ms) |
+ : touch_queue_(touch_queue), |
+ timeout_delay_(base::TimeDelta::FromMilliseconds(timeout_delay_ms)), |
+ pending_ack_state_(PENDING_ACK_NONE), |
+ timeout_monitor_(base::Bind(&TouchTimeoutHandler::OnTimeOut, |
+ base::Unretained(this))) {} |
+ |
+ ~TouchTimeoutHandler() {} |
+ |
+ void Start(const TouchEventWithLatencyInfo& event) { |
+ DCHECK_EQ(pending_ack_state_, PENDING_ACK_NONE); |
+ DCHECK(ShouldTouchTypeTriggerTimeout(event.event.type)); |
+ timeout_event_ = event; |
+ timeout_monitor_.Restart(timeout_delay_); |
+ } |
+ |
+ bool ConfirmTouchEvent(InputEventAckState ack_result) { |
+ switch (pending_ack_state_) { |
+ case PENDING_ACK_NONE: |
+ 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_->UpdateTouchAckStates( |
+ cancel_event.event, kDefaultNotForwardedAck); |
+ touch_queue_->client_->SendTouchEventImmediately(cancel_event); |
+ } else { |
+ SetPendingAckState(PENDING_ACK_NONE); |
+ touch_queue_->UpdateTouchAckStates(timeout_event_.event, ack_result); |
+ } |
+ return true; |
+ case PENDING_ACK_CANCEL_EVENT: |
+ SetPendingAckState(PENDING_ACK_NONE); |
+ return true; |
+ } |
+ return false; |
+ } |
+ |
+ bool HasTimeoutEvent() const { |
+ return pending_ack_state_ != PENDING_ACK_NONE; |
+ } |
+ |
+ bool IsTimeoutTimerRunning() const { |
+ return timeout_monitor_.IsRunning(); |
+ } |
+ |
+ private: |
+ enum PendingAckState { |
+ PENDING_ACK_NONE, |
+ PENDING_ACK_ORIGINAL_EVENT, |
+ PENDING_ACK_CANCEL_EVENT, |
+ }; |
+ |
+ void OnTimeOut() { |
+ 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 !IsNewTouchGesture(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; |
+ } |
+ |
+ |
+ TouchEventQueue* touch_queue_; |
+ |
+ // How long to wait on a touch ack before cancelling the touch sequence. |
+ base::TimeDelta timeout_delay_; |
+ |
+ // 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_; |
+}; |
+ |
+ |
// 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 |
@@ -19,9 +171,10 @@ typedef std::vector<TouchEventWithLatencyInfo> WebTouchEventWithLatencyList; |
// the Client receives the event with their original timestamp. |
class CoalescedWebTouchEvent { |
public: |
- explicit CoalescedWebTouchEvent(const TouchEventWithLatencyInfo& event) |
+ CoalescedWebTouchEvent(const TouchEventWithLatencyInfo& event, |
+ bool ignore_ack) |
: coalesced_event_(event), |
- ignore_ack_(false) { |
+ ignore_ack_(ignore_ack) { |
events_.push_back(event); |
TRACE_EVENT_ASYNC_BEGIN0( |
"input", "TouchEventQueue::QueueEvent", this); |
@@ -64,7 +217,6 @@ class CoalescedWebTouchEvent { |
size_t size() const { return events_.size(); } |
bool ignore_ack() const { return ignore_ack_; } |
- void set_ignore_ack(bool value) { ignore_ack_ = value; } |
private: |
// This is the event that is forwarded to the renderer. |
@@ -83,7 +235,10 @@ class CoalescedWebTouchEvent { |
TouchEventQueue::TouchEventQueue(TouchEventQueueClient* client) |
: client_(client), |
dispatching_touch_ack_(NULL), |
- no_touch_to_renderer_(false) { |
+ dispatching_touch_(false), |
+ no_touch_to_renderer_(false), |
+ renderer_is_consuming_touch_gesture_(false), |
+ ack_timeout_enabled_(false) { |
DCHECK(client); |
} |
@@ -98,7 +253,7 @@ void TouchEventQueue::QueueEvent(const TouchEventWithLatencyInfo& event) { |
if (touch_queue_.empty() && !dispatching_touch_ack_) { |
// There is no touch event in the queue. Forward it to the renderer |
// immediately. |
- touch_queue_.push_back(new CoalescedWebTouchEvent(event)); |
+ touch_queue_.push_back(new CoalescedWebTouchEvent(event, false)); |
TryForwardNextEventToRenderer(); |
return; |
} |
@@ -110,35 +265,26 @@ void TouchEventQueue::QueueEvent(const TouchEventWithLatencyInfo& event) { |
if (last_event->CoalesceEventIfPossible(event)) |
return; |
} |
- touch_queue_.push_back(new CoalescedWebTouchEvent(event)); |
+ touch_queue_.push_back(new CoalescedWebTouchEvent(event, false)); |
} |
void TouchEventQueue::ProcessTouchAck(InputEventAckState ack_result, |
const ui::LatencyInfo& latency_info) { |
DCHECK(!dispatching_touch_ack_); |
+ dispatching_touch_ = false; |
+ |
+ if (timeout_handler_ && timeout_handler_->ConfirmTouchEvent(ack_result)) |
+ return; |
+ |
if (touch_queue_.empty()) |
return; |
- // Update the ACK status for each touch point in the ACKed event. |
- const blink::WebTouchEvent& event = |
- touch_queue_.front()->coalesced_event().event; |
- if (event.type == blink::WebInputEvent::TouchEnd || |
- event.type == blink::WebInputEvent::TouchCancel) { |
- // The points have been released. Erase the ACK states. |
- for (unsigned i = 0; i < event.touchesLength; ++i) { |
- const blink::WebTouchPoint& point = event.touches[i]; |
- if (point.state == blink::WebTouchPoint::StateReleased || |
- point.state == blink::WebTouchPoint::StateCancelled) |
- touch_ack_states_.erase(point.id); |
- } |
- } else if (event.type == blink::WebInputEvent::TouchStart) { |
- for (unsigned i = 0; i < event.touchesLength; ++i) { |
- const blink::WebTouchPoint& point = event.touches[i]; |
- if (point.state == blink::WebTouchPoint::StatePressed) |
- touch_ack_states_[point.id] = ack_result; |
- } |
- } |
+ if (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED) |
+ renderer_is_consuming_touch_gesture_ = true; |
+ const WebTouchEvent& acked_event = |
+ touch_queue_.front()->coalesced_event().event; |
+ UpdateTouchAckStates(acked_event, ack_result); |
PopTouchEventToClient(ack_result, latency_info); |
TryForwardNextEventToRenderer(); |
} |
@@ -150,12 +296,29 @@ void TouchEventQueue::TryForwardNextEventToRenderer() { |
while (!touch_queue_.empty()) { |
const TouchEventWithLatencyInfo& touch = |
touch_queue_.front()->coalesced_event(); |
+ if (IsNewTouchGesture(touch.event)) |
+ renderer_is_consuming_touch_gesture_ = false; |
if (ShouldForwardToRenderer(touch.event)) { |
- client_->SendTouchEventImmediately(touch); |
+ ForwardToRenderer(touch); |
break; |
} |
- PopTouchEventToClient(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS, |
- ui::LatencyInfo()); |
+ PopTouchEventToClient(kDefaultNotForwardedAck, ui::LatencyInfo()); |
+ } |
+} |
+ |
+void TouchEventQueue::ForwardToRenderer( |
+ const TouchEventWithLatencyInfo& touch) { |
+ DCHECK(!dispatching_touch_); |
+ // A synchronous ack will reset |dispatching_touch_|, in which case |
+ // the touch timeout should not be started. |
+ base::AutoReset<bool> dispatching_touch(&dispatching_touch_, true); |
+ client_->SendTouchEventImmediately(touch); |
+ if (ack_timeout_enabled_ && |
+ dispatching_touch_ && |
+ !renderer_is_consuming_touch_gesture_ && |
+ ShouldTouchTypeTriggerTimeout(touch.event.type)) { |
+ DCHECK(timeout_handler_); |
+ timeout_handler_->Start(touch); |
} |
} |
@@ -170,24 +333,21 @@ void TouchEventQueue::OnGestureScrollEvent( |
if (no_touch_to_renderer_ || !dispatching_touch_ack_) |
return; |
no_touch_to_renderer_ = true; |
+ |
+ // If we have a timeout event, a cancel has already been dispatched |
+ // for the current touch stream. |
+ if (HasTimeoutEvent()) |
+ return; |
+ |
// Fake a TouchCancel to cancel the touch points of the touch event |
// that is currently being acked. |
- TouchEventWithLatencyInfo cancel_event = |
- dispatching_touch_ack_->coalesced_event(); |
- cancel_event.event.type = blink::WebInputEvent::TouchCancel; |
- for (size_t i = 0; i < cancel_event.event.touchesLength; i++) |
- cancel_event.event.touches[i].state = |
- blink::WebTouchPoint::StateCancelled; |
- CoalescedWebTouchEvent* coalesced_cancel_event = |
- new CoalescedWebTouchEvent(cancel_event); |
- // Ignore the ack of the touch cancel so when it is acked, it won't get |
- // sent to gesture recognizer. |
- coalesced_cancel_event->set_ignore_ack(true); |
- // |dispatching_touch_ack_| is non-null when we reach here, meaning we |
+ // Note: |dispatching_touch_ack_| is non-null when we reach here, meaning we |
// are in the scope of PopTouchEventToClient() and that no touch event |
// in the queue is waiting for ack from renderer. So we can just insert |
// the touch cancel at the beginning of the queue. |
- touch_queue_.push_front(coalesced_cancel_event); |
+ touch_queue_.push_front(new CoalescedWebTouchEvent( |
+ ObtainCancelEventForTouchEvent( |
+ dispatching_touch_ack_->coalesced_event()), true)); |
} else if (type == blink::WebInputEvent::GestureScrollEnd || |
type == blink::WebInputEvent::GestureFlingStart) { |
no_touch_to_renderer_ = false; |
@@ -196,9 +356,9 @@ void TouchEventQueue::OnGestureScrollEvent( |
void TouchEventQueue::FlushQueue() { |
DCHECK(!dispatching_touch_ack_); |
+ DCHECK(!dispatching_touch_); |
while (!touch_queue_.empty()) |
- PopTouchEventToClient(INPUT_EVENT_ACK_STATE_NOT_CONSUMED, |
- ui::LatencyInfo()); |
+ PopTouchEventToClient(kDefaultNotForwardedAck, ui::LatencyInfo()); |
} |
bool TouchEventQueue::IsPendingAckTouchStart() const { |
@@ -208,20 +368,40 @@ bool TouchEventQueue::IsPendingAckTouchStart() const { |
const blink::WebTouchEvent& event = |
touch_queue_.front()->coalesced_event().event; |
- return (event.type == blink::WebInputEvent::TouchStart); |
+ return (event.type == WebInputEvent::TouchStart); |
} |
-size_t TouchEventQueue::GetQueueSize() const { |
- return touch_queue_.size(); |
+void TouchEventQueue::SetAckTimeoutEnabled(bool enabled, |
+ size_t ack_timeout_delay_ms) { |
+ if (!enabled) { |
+ // Avoid resetting |timeout_handler_|, as an outstanding timeout may |
+ // be active and must be completed for ack handling consistency. |
+ ack_timeout_enabled_ = false; |
+ return; |
+ } |
+ |
+ ack_timeout_enabled_ = true; |
+ if (!timeout_handler_) |
+ timeout_handler_.reset(new TouchTimeoutHandler(this, ack_timeout_delay_ms)); |
} |
-const TouchEventWithLatencyInfo& TouchEventQueue::GetLatestEvent() const { |
+bool TouchEventQueue::HasTimeoutEvent() const { |
+ return timeout_handler_ && timeout_handler_->HasTimeoutEvent(); |
+} |
+ |
+bool TouchEventQueue::IsTimeoutRunningForTesting() const { |
+ return timeout_handler_ && timeout_handler_->IsTimeoutTimerRunning(); |
+} |
+ |
+const TouchEventWithLatencyInfo& |
+TouchEventQueue::GetLatestEventForTesting() const { |
return touch_queue_.back()->coalesced_event(); |
} |
void TouchEventQueue::PopTouchEventToClient( |
InputEventAckState ack_result, |
const ui::LatencyInfo& renderer_latency_info) { |
+ DCHECK(!dispatching_touch_ack_); |
if (touch_queue_.empty()) |
return; |
scoped_ptr<CoalescedWebTouchEvent> acked_event(touch_queue_.front()); |
@@ -244,19 +424,22 @@ void TouchEventQueue::PopTouchEventToClient( |
} |
bool TouchEventQueue::ShouldForwardToRenderer( |
- const blink::WebTouchEvent& event) const { |
+ const WebTouchEvent& event) const { |
+ if (HasTimeoutEvent()) |
+ return false; |
+ |
if (no_touch_to_renderer_ && |
event.type != blink::WebInputEvent::TouchCancel) |
return false; |
// Touch press events should always be forwarded to the renderer. |
- if (event.type == blink::WebInputEvent::TouchStart) |
+ if (event.type == WebInputEvent::TouchStart) |
return true; |
for (unsigned int i = 0; i < event.touchesLength; ++i) { |
- const blink::WebTouchPoint& point = event.touches[i]; |
+ const WebTouchPoint& point = event.touches[i]; |
// If a point has been stationary, then don't take it into account. |
- if (point.state == blink::WebTouchPoint::StateStationary) |
+ if (point.state == WebTouchPoint::StateStationary) |
continue; |
if (touch_ack_states_.count(point.id) > 0) { |
@@ -273,4 +456,25 @@ bool TouchEventQueue::ShouldForwardToRenderer( |
return false; |
} |
+void TouchEventQueue::UpdateTouchAckStates(const WebTouchEvent& event, |
+ InputEventAckState ack_result) { |
+ // Update the ACK status for each touch point in the ACKed event. |
+ if (event.type == WebInputEvent::TouchEnd || |
+ event.type == WebInputEvent::TouchCancel) { |
+ // The points have been released. Erase the ACK states. |
+ for (unsigned i = 0; i < event.touchesLength; ++i) { |
+ const WebTouchPoint& point = event.touches[i]; |
+ if (point.state == WebTouchPoint::StateReleased || |
+ point.state == WebTouchPoint::StateCancelled) |
+ touch_ack_states_.erase(point.id); |
+ } |
+ } else if (event.type == WebInputEvent::TouchStart) { |
+ for (unsigned i = 0; i < event.touchesLength; ++i) { |
+ const WebTouchPoint& point = event.touches[i]; |
+ if (point.state == WebTouchPoint::StatePressed) |
+ touch_ack_states_[point.id] = ack_result; |
+ } |
+ } |
+} |
+ |
} // namespace content |