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 ecc12056729c1ec58ffeea36e929288e7f08c5ca..7d29f0f0e775fa8936bbb70cd3538aa2c5fb052a 100644 |
--- a/content/browser/renderer_host/input/touch_event_queue.cc |
+++ b/content/browser/renderer_host/input/touch_event_queue.cc |
@@ -5,12 +5,121 @@ |
#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; |
+typedef std::map<int, InputEventAckState> AckStates; |
+ |
+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; |
+} |
+ |
+} // 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); |
+ timeout_event_ = event; |
+ timeout_monitor_.Restart(timeout_delay_); |
+ } |
+ |
+ bool ConfirmTouchEvent() { |
+ switch (pending_ack_state_) { |
+ case PENDING_ACK_NONE: |
+ timeout_monitor_.Stop(); |
+ return false; |
+ case PENDING_ACK_ORIGINAL_EVENT: |
Xianzhu
2013/12/05 17:48:40
In original Java method, because we can only have
jdduke (slow)
2013/12/05 18:26:04
The restriction is important only when we care abo
Xianzhu
2013/12/05 18:30:18
Then pending_ack_state_ could be pending_ack_count
|
+ DCHECK(!timeout_monitor_.IsRunning()); |
+ DCHECK(touch_queue_->empty()); |
+ TRACE_EVENT_ASYNC_STEP_INTO0( |
+ "input", "TouchEventQueue::TouchEventTimeout", this, "CancelEvent"); |
+ pending_ack_state_ = PENDING_ACK_CANCEL_EVENT; |
+ return true; |
+ case PENDING_ACK_CANCEL_EVENT: |
+ DCHECK(!timeout_monitor_.IsRunning()); |
+ DCHECK(touch_queue_->empty()); |
+ TRACE_EVENT_ASYNC_BEGIN0( |
Xianzhu
2013/12/05 17:48:40
TRACE_EVENT_ASYNC_END0?
jdduke (slow)
2013/12/05 18:26:04
Good catch, fixed.
|
+ "input", "TouchEventQueue::TouchEventTimeout", this); |
+ pending_ack_state_ = 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: |
+ void OnTimeOut() { |
+ TRACE_EVENT_ASYNC_BEGIN0( |
+ "input", "TouchEventQueue::TouchEventTimeout", this); |
+ pending_ack_state_ = PENDING_ACK_ORIGINAL_EVENT; |
+ touch_queue_->FlushQueue(); |
+ DCHECK(touch_queue_->empty()); |
+ |
+ TouchEventWithLatencyInfo cancel_event = |
+ ObtainCancelEventForTouchEvent(timeout_event_); |
+ touch_queue_->UpdateTouchAckStates( |
+ cancel_event.event, kDefaultNotForwardedAck); |
+ touch_queue_->client_->SendTouchEventImmediately(cancel_event); |
+ } |
+ |
+ |
+ 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. |
+ enum PendingAckState { |
+ PENDING_ACK_NONE, |
+ PENDING_ACK_ORIGINAL_EVENT, |
+ PENDING_ACK_CANCEL_EVENT, |
+ }; |
+ 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 |
@@ -19,9 +128,10 @@ typedef std::vector<TouchEventWithLatencyInfo> WebTouchEventWithLatencyList; |
// the Client receives the event with their original timestamp. |
class CoalescedWebTouchEvent { |
public: |
- explicit CoalescedWebTouchEvent(const TouchEventWithLatencyInfo& event) |
+ explicit 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 +174,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 +192,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_gesture_(false), |
+ ack_timeout_enabled_(false) { |
DCHECK(client); |
} |
@@ -98,7 +210,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 +222,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()) |
+ 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_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 +253,30 @@ void TouchEventQueue::TryForwardNextEventToRenderer() { |
while (!touch_queue_.empty()) { |
const TouchEventWithLatencyInfo& touch = |
touch_queue_.front()->coalesced_event(); |
+ if (touch.event.type == WebInputEvent::TouchStart) |
+ renderer_is_consuming_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_gesture_ && |
+ (touch.event.type == WebInputEvent::TouchStart || |
+ touch.event.type == WebInputEvent::TouchMove)) { |
+ DCHECK(timeout_handler_); |
+ timeout_handler_->Start(touch); |
} |
} |
@@ -170,24 +291,22 @@ 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,22 +315,42 @@ 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()); |
+} |
+ |
+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)); |
} |
-size_t TouchEventQueue::GetQueueSize() const { |
- return touch_queue_.size(); |
+bool TouchEventQueue::HasTimeoutEvent() const { |
+ return timeout_handler_ && timeout_handler_->HasTimeoutEvent(); |
} |
-const TouchEventWithLatencyInfo& TouchEventQueue::GetLatestEvent() const { |
+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()); |
@@ -234,19 +373,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) { |
@@ -263,4 +405,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 |