Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1494)

Unified Diff: content/browser/renderer_host/input/touch_event_queue.cc

Issue 48973005: Move TouchEvent timeout code to the TouchEventQueue (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Rebase Created 7 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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

Powered by Google App Engine
This is Rietveld 408576698