Index: content/browser/renderer_host/input/touch_to_gesture_queue.cc |
diff --git a/content/browser/renderer_host/input/touch_to_gesture_queue.cc b/content/browser/renderer_host/input/touch_to_gesture_queue.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..f03f8bd110eb7e1fe06990c4a8279afc9d2abd25 |
--- /dev/null |
+++ b/content/browser/renderer_host/input/touch_to_gesture_queue.cc |
@@ -0,0 +1,257 @@ |
+// 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/touch_to_gesture_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 { |
+ |
+bool IsTouchSequenceBegin(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 IsScrollStart(const WebGestureEvent& event) { |
+ return event.type == WebInputEvent::GestureScrollBegin; |
+} |
+ |
+bool IsFlingCancel(const WebGestureEvent& event) { |
+ return event.type == WebInputEvent::GestureFlingCancel; |
+} |
+ |
+bool IsFlingStart(const WebGestureEvent& event) { |
+ return event.type == WebInputEvent::GestureFlingStart; |
+} |
+ |
+bool IsTapStart(const WebGestureEvent& event) { |
+ return event.type == WebInputEvent::GestureTapDown; |
+} |
+ |
+bool IsTapEnd(const WebGestureEvent& event) { |
+ switch (event.type) { |
+ case WebInputEvent::GestureTapCancel: |
+ case WebInputEvent::GestureTap: |
+ case WebInputEvent::GestureTapUnconfirmed: |
+ case WebInputEvent::GestureDoubleTap: |
+ return true; |
+ default: |
+ break; |
+ } |
+ return false; |
+} |
+ |
+bool IsTimeout(const WebGestureEvent& event) { |
tdresser
2014/01/14 16:25:30
Perhaps IsTimeout -> IsTriggeredByTimeout?
jdduke (slow)
2014/01/14 23:24:03
Done.
|
+ switch (event.type) { |
+ case WebInputEvent::GestureShowPress: |
+ case WebInputEvent::GestureLongPress: |
+ case WebInputEvent::GestureTapUnconfirmed: |
+ return true; |
+ default: |
+ break; |
+ } |
+ return false; |
+} |
+ |
+InputEventAckState Intersection(InputEventAckState ack_old, |
tdresser
2014/01/14 16:25:30
The word "Intersection" applied to an enum could b
jdduke (slow)
2014/01/14 23:24:03
Hmm, I'll noodle on this.
|
+ 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 |
+ |
+TouchToGestureQueue::TouchToGestureQueue(TouchToGestureQueueClient* client) |
+ : client_(client), |
+ handling_touch_event_(false), |
+ outstanding_tap_(false), |
+ outstanding_fling_(false) { |
+ DCHECK(client_); |
+} |
+ |
+TouchToGestureQueue::~TouchToGestureQueue() {} |
+ |
+void TouchToGestureQueue::OnTouchEventHandlingBegin( |
+ const WebTouchEvent& event) { |
+ DCHECK(!handling_touch_event_); |
+ handling_touch_event_ = true; |
+ |
+ if (IsTouchSequenceBegin(event)) |
+ sequences_.push_back(TouchToGestureSequence()); |
+ |
+ Tail().Push(event); |
+} |
+ |
+void TouchToGestureQueue::OnTouchEventHandlingEnd() { |
+ DCHECK(handling_touch_event_); |
+ DCHECK(!sequences_.empty()); |
+ |
+ handling_touch_event_ = false; |
+ client_->ForwardTouchEvent(Tail().LastTouch()); |
+} |
+ |
+void TouchToGestureQueue::OnGestureEvent(const WebGestureEvent& event) { |
+ if (handling_touch_event_) { |
+ Tail().Push(event); |
+ return; |
+ } |
+ |
+ if (IsTimeout(event)) { |
+ // Touches preceding the timeout event are still awaiting an ack. |
+ if (!Tail().IsEmpty()) |
+ Tail().Push(event); |
+ // Touches preceding the timeout have been handled and gestures are allowed. |
+ else if (Tail().IsGestureAllowed()) |
+ ForwardGestureEvent(event); |
+ // The timeout gesture should be dropped. |
+ return; |
+ } |
+ |
+ // Synthetic gestures may be generated by the platform, in which case they |
+ // should simply be forwarded immediately. |
+ ForwardGestureEvent(event); |
+} |
+ |
+void TouchToGestureQueue::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. |
+ if (Head().IsEmpty()) { |
+ CancelTapIfNecessary(); |
+ CancelFlingIfNecessary(); |
+ sequences_.pop_front(); |
+ } |
+ |
+ // TODO(jdduke): Settle on touch-slop disposition behavior, crbug/333947. |
+ TouchToGesture touch_to_gesture = Head().OnTouchEventAck(ack_state); |
+ for (size_t i = 0; i < touch_to_gesture.gestures.size(); ++i) |
+ ForwardGestureEvent(touch_to_gesture.gestures[i]); |
+ |
+ // Immediately cancel a TapDown if TouchStart went unconsumed, but a |
+ // subsequent TouchMove is consumed. |
+ if (!Head().IsGestureAllowed()) |
+ CancelTapIfNecessary(); |
+} |
+ |
+void TouchToGestureQueue::ForwardGestureEvent(const WebGestureEvent& event) { |
tdresser
2014/01/14 16:25:30
I don't think this is any more readable than a big
jdduke (slow)
2014/01/14 23:24:03
You're totally right, this originally started with
|
+ if (IsTapStart(event)) |
+ outstanding_tap_ = true; |
+ else if (IsTapEnd(event)) |
+ outstanding_tap_ = false; |
+ else if (IsFlingStart(event)) |
+ outstanding_fling_ = true; |
+ else if (IsFlingCancel(event)) |
+ outstanding_fling_ = false; |
+ else if (IsScrollStart(event)) |
+ CancelTapIfNecessary(); |
+ |
+ client_->ForwardGestureEvent(event); |
+} |
+ |
+void TouchToGestureQueue::CancelTapIfNecessary() { |
+ if (!outstanding_tap_) |
+ return; |
+ |
+ ForwardGestureEvent(CreateGesture(WebInputEvent::GestureTapCancel)); |
+ DCHECK(!outstanding_tap_); |
+} |
+ |
+void TouchToGestureQueue::CancelFlingIfNecessary() { |
+ if (!outstanding_fling_) |
+ return; |
+ |
+ ForwardGestureEvent(CreateGesture(WebInputEvent::GestureFlingCancel)); |
+ DCHECK(!outstanding_fling_); |
+} |
+ |
+TouchToGestureQueue::TouchToGestureSequence& TouchToGestureQueue::Head() { |
+ DCHECK(!sequences_.empty()); |
+ return sequences_.front(); |
+} |
+ |
+TouchToGestureQueue::TouchToGestureSequence& TouchToGestureQueue::Tail() { |
+ DCHECK(!sequences_.empty()); |
+ return sequences_.back(); |
+} |
+ |
+TouchToGestureQueue::TouchToGestureSequence::TouchToGestureSequence() |
+ : ack_state(INPUT_EVENT_ACK_STATE_UNKNOWN) {} |
+ |
+TouchToGestureQueue::TouchToGestureSequence::~TouchToGestureSequence() {} |
+ |
+void TouchToGestureQueue::TouchToGestureSequence::Push( |
+ const blink::WebTouchEvent& event) { |
+ TouchToGesture touch_to_gesture; |
+ touch_to_gesture.touch = event; |
+ sequence.push_back(touch_to_gesture); |
+} |
+ |
+void TouchToGestureQueue::TouchToGestureSequence::Push( |
+ const blink::WebGestureEvent& event) { |
+ DCHECK(!IsEmpty()); |
+ sequence.back().gestures.push_back(event); |
+} |
+ |
+TouchToGestureQueue::TouchToGesture |
+TouchToGestureQueue::TouchToGestureSequence::OnTouchEventAck( |
+ InputEventAckState new_ack_state) { |
+ DCHECK(!IsEmpty()); |
+ |
+ ack_state = Intersection(ack_state, new_ack_state); |
+ DCHECK_NE(ack_state, INPUT_EVENT_ACK_STATE_UNKNOWN); |
+ |
+ TouchToGesture touch_to_gesture = |
+ IsGestureAllowed() ? sequence.front() : TouchToGesture(); |
+ sequence.pop_front(); |
+ return touch_to_gesture; |
+} |
+ |
+bool TouchToGestureQueue::TouchToGestureSequence::IsGestureAllowed() const { |
+ return ack_state == INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS || |
+ ack_state == INPUT_EVENT_ACK_STATE_NOT_CONSUMED; |
+} |
+ |
+bool TouchToGestureQueue::TouchToGestureSequence::IsEmpty() const { |
+ return sequence.empty(); |
+} |
+ |
+const blink::WebTouchEvent& |
+TouchToGestureQueue::TouchToGestureSequence::LastTouch() const { |
+ DCHECK(!IsEmpty()); |
+ return sequence.back().touch; |
+} |
+ |
+} // namespace content |