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..1933e33ff350f1b32d20b13d940725947da1e1e5 |
--- /dev/null |
+++ b/content/browser/renderer_host/input/gesture_event_queue.cc |
@@ -0,0 +1,233 @@ |
+// 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/bind.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 { |
+ |
+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), |
+ packet_sender_(base::Bind(&GestureEventQueue::SendPacket, |
+ base::Unretained(this))), |
+ 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: |
Rick Byers
2014/01/21 20:10:10
This special handling for TOUCH_TIMEOUT and SYNTHE
jdduke (slow)
2014/01/21 20:52:38
It's not redundant, unfortunately. Synthetic pac
Rick Byers
2014/01/23 20:45:29
Sorry, what I meant was that we've got two separat
|
+ // Handle the timeout packet immediately if the packet preceding the |
+ // timeout has already been dispatched. |
+ if (Tail().IsEmpty()) { |
+ if (Tail().IsGestureAllowed()) |
+ SendPacket(packet); |
+ return; |
+ } |
+ break; |
+ |
+ case GestureEventPacket::SYNTHETIC: |
+ // Handle the synthetic packet immediately if no other packets pending. |
Rick Byers
2014/01/21 20:10:10
Can you talk a bit about why synthetic gestures ar
jdduke (slow)
2014/01/21 20:52:38
This is done for things like WebView's zoom contro
Rick Byers
2014/01/23 20:45:29
Ah, thanks! I had forgotten we used synthetic ges
|
+ if (sequences_.empty() || Tail().IsEmpty()) { |
+ SendPacket(packet); |
+ return; |
+ } |
+ // Otherwise, queue the packet and process it in-order, irrespective |
+ // of the touch sequence disposition. |
+ break; |
+ } |
+ |
+ Tail().Push(packet); |
+} |
+ |
+void GestureEventQueue::OnTouchEventAck(InputEventAckState ack_state) { |
+ if (Head().IsEmpty()) { |
+ CancelTapIfNecessary(); |
+ CancelFlingIfNecessary(); |
+ sequences_.pop_front(); |
+ } |
+ |
+ Head().OnTouchEventAck(ack_state, packet_sender_); |
+ |
+ // Immediately cancel a TapDown if TouchStart went unconsumed, but a |
+ // subsequent TouchMove is consumed. |
+ if (!Head().IsGestureAllowed()) |
+ CancelTapIfNecessary(); |
+} |
+ |
+InputEventAckState GestureEventQueue::GetMostRestrictiveState( |
+ InputEventAckState ack_old, |
+ InputEventAckState ack_new) { |
+ switch (ack_old) { |
+ case INPUT_EVENT_ACK_STATE_UNKNOWN: |
+ return ack_new; |
+ |
+ case INPUT_EVENT_ACK_STATE_NOT_CONSUMED: |
+ return ack_new == INPUT_EVENT_ACK_STATE_CONSUMED ? ack_new : ack_old; |
+ |
+ case INPUT_EVENT_ACK_STATE_CONSUMED: |
+ case INPUT_EVENT_ACK_STATE_IGNORED: |
+ case INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS: |
+ default: |
+ return ack_old; |
+ }; |
+} |
+ |
+void GestureEventQueue::SendPacket(const GestureEventPacket& packet) { |
+ for (size_t i = 0; i < packet.gesture_count(); ++i) |
+ SendGesture(packet.gesture(i)); |
+} |
+ |
+void GestureEventQueue::SendGesture(const WebGestureEvent& event) { |
+ switch (event.type) { |
+ case WebInputEvent::GestureScrollBegin: |
+ case WebInputEvent::GestureLongTap: |
+ 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: |
+ CancelFlingIfNecessary(); |
+ outstanding_fling_ = true; |
+ break; |
+ case WebInputEvent::GestureFlingCancel: |
+ outstanding_fling_ = false; |
+ break; |
+ default: |
+ break; |
+ } |
+ client_->ForwardGestureEvent(event); |
+} |
+ |
+void GestureEventQueue::CancelTapIfNecessary() { |
+ if (!outstanding_tap_) |
+ return; |
+ |
+ SendGesture(CreateGesture(WebInputEvent::GestureTapCancel)); |
+ DCHECK(!outstanding_tap_); |
+} |
+ |
+void GestureEventQueue::CancelFlingIfNecessary() { |
+ if (!outstanding_fling_) |
+ return; |
+ |
+ SendGesture(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) { |
+ if (packet.gesture_source() == GestureEventPacket::TOUCH_TIMEOUT || |
+ packet.gesture_source() == GestureEventPacket::SYNTHETIC) { |
+ // Timeout-derived gestures should have been forwarded or dropped |
+ // immediately if the sequence was empty. |
+ DCHECK(!IsEmpty()); |
+ } |
+ sequence_.push_back(packet); |
+} |
+ |
+void GestureEventQueue::GestureSequence::OnTouchEventAck( |
+ InputEventAckState new_ack_state, |
+ const PacketSender& packet_sender) { |
+ DCHECK_NE(INPUT_EVENT_ACK_STATE_UNKNOWN, new_ack_state); |
+ |
+ InputEventAckState old_ack_state = ack_state_; |
+ ack_state_ = GetMostRestrictiveState(old_ack_state, new_ack_state); |
Rick Byers
2014/01/21 20:10:10
Couldn't this just be a single bit "is gesture all
jdduke (slow)
2014/01/21 20:52:38
It is awkward, and I'm not opposed to using a uniq
Rick Byers
2014/01/23 20:45:29
Cool, I like this better (has a nice symmetry with
|
+ |
+ bool packet_for_current_ack_handled = false; |
+ while (!IsEmpty()) { |
+ const GestureEventPacket& packet = sequence_.front(); |
+ |
+ switch (packet.gesture_source()) { |
+ case GestureEventPacket::TOUCH_BEGIN: |
+ case GestureEventPacket::TOUCH: |
+ // We should handle at most one packet corresponding to a given ack. |
+ if (packet_for_current_ack_handled) |
+ return; |
+ packet_for_current_ack_handled = true; |
+ |
+ if (IsGestureAllowed()) |
+ packet_sender.Run(packet); |
+ break; |
+ |
+ case GestureEventPacket::TOUCH_TIMEOUT: |
+ if (IsGestureAllowed()) |
+ packet_sender.Run(packet); |
+ break; |
+ |
+ case GestureEventPacket::SYNTHETIC: |
+ // Synthetic gestures should be dispatched regardless of the touch |
+ // sequence disposition. |
+ packet_sender.Run(packet); |
+ break; |
+ }; |
+ |
+ sequence_.pop_front(); |
+ } |
+ DCHECK(packet_for_current_ack_handled); |
+} |
+ |
+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 |