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