| 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
|
|
|