Chromium Code Reviews| 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..d3f1fefb84fd637480139ede2a19857bb894082e 100644 |
| --- a/content/browser/renderer_host/input/touch_event_queue.cc |
| +++ b/content/browser/renderer_host/input/touch_event_queue.cc |
| @@ -5,12 +5,155 @@ |
| #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; |
|
sadrul
2013/12/06 07:35:18
This isn't used anywhere?
jdduke (slow)
2013/12/06 17:17:20
Done.
|
| + |
| +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); |
| + DCHECK(event.event.type == WebInputEvent::TouchStart || |
| + event.event.type == WebInputEvent::TouchMove); |
| + 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. |
| + bool AckedTimeoutEventRequiresCancel(InputEventAckState ack_result) const { |
| + DCHECK(HasTimeoutEvent()); |
| + if (ack_result != INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS) |
| + return true; |
| + if (timeout_event_.event.type != WebInputEvent::TouchStart) |
| + return true; |
| + for (unsigned i = 0; i < timeout_event_.event.touchesLength; ++i) { |
| + const WebTouchPoint& point = timeout_event_.event.touches[i]; |
| + if (point.state != WebTouchPoint::StatePressed) |
| + return true; |
| + } |
| + return false; |
| + } |
| + |
| + 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 |
| @@ -19,9 +162,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, |
|
sadrul
2013/12/06 07:35:18
remove explicit
jdduke (slow)
2013/12/06 17:17:20
Done.
|
| + 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 +208,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 +226,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 +244,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 +256,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_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 +287,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; |
|
sadrul
2013/12/06 07:35:18
Do you also want to check if touch.event.touchesLe
jdduke (slow)
2013/12/06 17:17:20
Of course! I guess in theory we could get multipl
|
| 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 +325,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,22 +348,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()); |
| } |
| -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)); |
| +} |
| + |
| +bool TouchEventQueue::HasTimeoutEvent() const { |
| + return timeout_handler_ && timeout_handler_->HasTimeoutEvent(); |
| +} |
| + |
| +bool TouchEventQueue::IsTimeoutRunningForTesting() const { |
| + return timeout_handler_ && timeout_handler_->IsTimeoutTimerRunning(); |
| } |
| -const TouchEventWithLatencyInfo& TouchEventQueue::GetLatestEvent() const { |
| +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 +406,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 +438,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 |