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..0f8ed03c1f7550ce66f6cb06fc28b289d5abc283 |
| --- /dev/null |
| +++ b/content/browser/renderer_host/input/gesture_event_queue.cc |
| @@ -0,0 +1,230 @@ |
| +// 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" |
| + |
| +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::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(GestureSequence()); |
| + break; |
| + |
| + case GestureEventPacket::TOUCH: |
| + break; |
| + |
| + case GestureEventPacket::TOUCH_TIMEOUT: |
| + // Handle the timeout packet immediately if the packet preceding the |
| + // timeout has already been dispatched. |
| + if (Tail().IsEmpty()) { |
| + if (!Tail().IsGesturePrevented()) |
| + SendPacket(packet); |
| + return; |
| + } |
| + break; |
| + |
| + case GestureEventPacket::SYNTHETIC: |
| + // Handle the synthetic packet immediately if no other packets pending. |
|
aelias_OOO_until_Jul13
2014/01/24 05:35:54
Looks like this was already discussed to some degr
jdduke (slow)
2014/01/24 18:17:35
Yeah, it was a mistake to include synthetic gestur
|
| + 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) { |
| + DCHECK(!sequences_.empty()); |
| + if (Head().IsEmpty()) { |
| + CancelTapIfNecessary(); |
| + CancelFlingIfNecessary(); |
| + sequences_.pop(); |
| + } |
| + |
| + Head().OnTouchEventAck(ack_state, packet_sender_); |
| + |
| + // Immediately cancel a TapDown if TouchStart went unconsumed, but a |
| + // subsequent TouchMove is consumed. |
| + if (Head().IsGesturePrevented()) |
| + CancelTapIfNecessary(); |
| +} |
| + |
| +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::GestureLongTap: |
| + CancelTapIfNecessary(); |
| + CancelFlingIfNecessary(); |
| + 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::GestureScrollBegin: |
| + CancelTapIfNecessary(); |
| + CancelFlingIfNecessary(); |
| + 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 |
| + |
| +GestureEventQueue::GestureSequence::GestureSequence() |
| + : state_(PENDING) {} |
| + |
| +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(packet); |
| +} |
| + |
| +void GestureEventQueue::GestureSequence::OnTouchEventAck( |
| + InputEventAckState ack_state, |
| + const PacketSender& packet_sender) { |
| + DCHECK_NE(INPUT_EVENT_ACK_STATE_UNKNOWN, ack_state); |
| + |
| + if (state_ == PENDING || state_ == ALLOWED) { |
| + // |NO_CONSUMER| should only be effective when the *first* touch is ack'ed. |
| + if (state_ == PENDING && |
| + ack_state == INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS) |
| + state_ = ALWAYS_ALLOWED; |
| + else if (ack_state == INPUT_EVENT_ACK_STATE_CONSUMED) |
| + state_ = ALWAYS_PREVENTED; |
| + else |
| + state_ = ALLOWED; |
| + } |
| + |
| + 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 (!IsGesturePrevented()) |
| + packet_sender.Run(packet); |
| + break; |
| + |
| + case GestureEventPacket::TOUCH_TIMEOUT: |
| + if (!IsGesturePrevented()) |
| + 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(); |
| + } |
| + DCHECK(packet_for_current_ack_handled); |
| +} |
| + |
| +bool GestureEventQueue::GestureSequence::IsGesturePrevented() const { |
| + return state_ == ALWAYS_PREVENTED; |
| +} |
| + |
| +bool GestureEventQueue::GestureSequence::IsEmpty() const { |
| + return sequence_.empty(); |
| +} |
| + |
| +} // namespace content |