Chromium Code Reviews| Index: content/browser/renderer_host/input/gesture_event_queue.cc |
| diff --git a/content/browser/renderer_host/input/gesture_event_queue.cc b/content/browser/renderer_host/input/gesture_event_queue.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..d35f960de16399c73901c95787002891eafa3ac9 |
| --- /dev/null |
| +++ b/content/browser/renderer_host/input/gesture_event_queue.cc |
| @@ -0,0 +1,198 @@ |
| +// Copyright 2013 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "content/browser/renderer_host/input/gesture_event_queue.h" |
| + |
| +#include "base/auto_reset.h" |
| +#include "base/logging.h" |
| +#include "content/common/input/web_input_event_traits.h" |
| + |
| +using blink::WebGestureEvent; |
| +using blink::WebInputEvent; |
| +using blink::WebTouchEvent; |
| +using blink::WebTouchPoint; |
| + |
| +namespace content { |
| +namespace { |
| + |
| +InputEventAckState TransitionToMostRestrictiveState( |
|
tdresser
2014/01/16 14:31:45
TransitionToMostRestrictiveState implies that this
jdduke (slow)
2014/01/16 17:17:53
Yeah, except the order matters (i.e., we never go
|
| + InputEventAckState ack_old, |
| + InputEventAckState ack_new) { |
| + if (ack_new == INPUT_EVENT_ACK_STATE_UNKNOWN) { |
| + NOTREACHED() << "New acks should never be undefined."; |
| + return ack_old; |
| + } |
| + |
| + if (ack_old == INPUT_EVENT_ACK_STATE_UNKNOWN || |
| + ack_old == INPUT_EVENT_ACK_STATE_NOT_CONSUMED) |
| + return ack_new; |
| + |
| + // If a partial touch sequence has been consumed, or has no consumer, |
| + // subsequent touches have no bearing on the overall gesture disposition. |
| + return ack_old; |
| +} |
| + |
| +WebGestureEvent CreateGesture(WebInputEvent::Type type) { |
| + DCHECK(WebInputEvent::isGestureEventType(type)); |
| + WebGestureEvent event; |
| + event.type = type; |
| + event.sourceDevice = WebGestureEvent::Touchscreen; |
| + return event; |
| +} |
| + |
| +} // namespace |
| + |
| +GestureEventQueue::GestureEventQueue(GestureEventQueueClient* client) |
| + : client_(client), |
| + outstanding_tap_(false), |
| + outstanding_fling_(false) { |
| + DCHECK(client_); |
| +} |
| + |
| +GestureEventQueue::~GestureEventQueue() {} |
| + |
| +void GestureEventQueue::OnGestureEventPacket(const GestureEventPacket& packet) { |
| + switch (packet.gesture_source()) { |
| + case GestureEventPacket::TOUCH_BEGIN: |
| + sequences_.push_back(GestureSequence()); |
| + break; |
| + case GestureEventPacket::TOUCH: |
| + break; |
| + case GestureEventPacket::TOUCH_TIMEOUT: |
| + // Touches preceding the timeout event are still awaiting an ack. |
| + if (!Tail().IsEmpty()) |
| + break; |
| + // Touches preceding the timeout were handled and gestures are allowed. |
| + if (Tail().IsGestureAllowed()) |
| + Forward(packet); |
| + // The timeout gesture should be dropped. |
| + return; |
| + case GestureEventPacket::SYNTHETIC: |
|
tdresser
2014/01/16 14:31:45
Couldn't this violate some ordering constraints?
jdduke (slow)
2014/01/16 17:17:53
Yeah, though on Android that constraint is already
tdresser
2014/01/16 18:28:29
SGTM.
|
| + Forward(packet); |
| + return; |
| + } |
| + |
| + Tail().Push(packet); |
| +} |
| + |
| +void GestureEventQueue::OnTouchEventAck(InputEventAckState ack_state) { |
| + // Transition to the next touch sequence. This transition is deferred |
| + // until the ack in the event that the sequence receives a timeout event. |
|
tdresser
2014/01/16 14:31:45
This is the only place where you're transitioning
jdduke (slow)
2014/01/16 17:17:53
It's a terrible comment. I was thinking of adding
|
| + if (Head().IsEmpty()) { |
| + CancelTapIfNecessary(); |
| + CancelFlingIfNecessary(); |
| + sequences_.pop_front(); |
| + } |
| + |
| + Forward(Head().OnTouchEventAck(ack_state)); |
| + |
| + // Immediately cancel a TapDown if TouchStart went unconsumed, but a |
| + // subsequent TouchMove is consumed. |
| + if (!Head().IsGestureAllowed()) |
| + CancelTapIfNecessary(); |
| +} |
| + |
| +void GestureEventQueue::Forward(const GestureEventPacket& packet) { |
| + for (size_t i = 0; i < packet.gesture_count(); ++i) |
| + Forward(packet.gesture(i)); |
| +} |
| + |
| +void GestureEventQueue::Forward(const WebGestureEvent& event) { |
| + switch (event.type) { |
| + case WebInputEvent::GestureScrollBegin: |
| + CancelTapIfNecessary(); |
| + break; |
| + case WebInputEvent::GestureTapDown: |
| + outstanding_tap_ = true; |
| + break; |
| + case WebInputEvent::GestureTapCancel: |
| + case WebInputEvent::GestureTap: |
| + case WebInputEvent::GestureTapUnconfirmed: |
| + case WebInputEvent::GestureDoubleTap: |
| + outstanding_tap_ = false; |
| + break; |
| + case WebInputEvent::GestureFlingStart: |
| + outstanding_fling_ = true; |
| + break; |
| + case WebInputEvent::GestureFlingCancel: |
| + outstanding_fling_ = false; |
| + break; |
| + default: |
| + break; |
| + } |
| + client_->ForwardGestureEvent(event); |
| +} |
| + |
| +void GestureEventQueue::CancelTapIfNecessary() { |
| + if (!outstanding_tap_) |
| + return; |
| + |
| + Forward(CreateGesture(WebInputEvent::GestureTapCancel)); |
| + DCHECK(!outstanding_tap_); |
| +} |
| + |
| +void GestureEventQueue::CancelFlingIfNecessary() { |
| + if (!outstanding_fling_) |
| + return; |
| + |
| + Forward(CreateGesture(WebInputEvent::GestureFlingCancel)); |
| + DCHECK(!outstanding_fling_); |
| +} |
| + |
| +GestureEventQueue::GestureSequence& GestureEventQueue::Head() { |
| + DCHECK(!sequences_.empty()); |
| + return sequences_.front(); |
| +} |
| + |
| +GestureEventQueue::GestureSequence& GestureEventQueue::Tail() { |
| + DCHECK(!sequences_.empty()); |
| + return sequences_.back(); |
| +} |
| + |
| + |
| +GestureEventQueue::GestureSequence::GestureSequence() |
| + : ack_state_(INPUT_EVENT_ACK_STATE_UNKNOWN) {} |
| + |
| +GestureEventQueue::GestureSequence::~GestureSequence() {} |
| + |
| +void GestureEventQueue::GestureSequence::Push( |
| + const GestureEventPacket& packet) { |
| + DCHECK_NE(GestureEventPacket::SYNTHETIC, packet.gesture_source()); |
| + if (packet.gesture_source() == GestureEventPacket::TOUCH_TIMEOUT) { |
| + // Timeout-derived gestures should have beeen forwarded or dropped |
|
tdresser
2014/01/16 14:31:45
beeen -> been
jdduke (slow)
2014/01/16 17:17:53
Done.
|
| + // immediately if the sequence was empty. |
| + DCHECK(!IsEmpty()); |
| + |
| + // Merge the timeout packet with the previous packet, simplifying dispatch |
| + // when the touch ack preceding the timeout is received. |
| + for (size_t i = 0; i < packet.gesture_count(); ++i) |
| + sequence_.back().Push(packet.gesture(i)); |
| + return; |
| + } |
| + sequence_.push_back(packet); |
| +} |
| + |
| +GestureEventPacket GestureEventQueue::GestureSequence::OnTouchEventAck( |
| + InputEventAckState new_ack_state) { |
| + DCHECK(!IsEmpty()); |
| + |
| + ack_state_ = TransitionToMostRestrictiveState(ack_state_, new_ack_state); |
| + DCHECK_NE(ack_state_, INPUT_EVENT_ACK_STATE_UNKNOWN); |
| + |
| + GestureEventPacket gestures = |
| + IsGestureAllowed() ? sequence_.front() : GestureEventPacket(); |
| + sequence_.pop_front(); |
| + return gestures; |
| +} |
| + |
| +bool GestureEventQueue::GestureSequence::IsGestureAllowed() const { |
| + return ack_state_ == INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS || |
| + ack_state_ == INPUT_EVENT_ACK_STATE_NOT_CONSUMED; |
| +} |
| + |
| +bool GestureEventQueue::GestureSequence::IsEmpty() const { |
| + return sequence_.empty(); |
| +} |
| + |
| +} // namespace content |