summaryrefslogtreecommitdiffstats
path: root/chromium/ui/events/gesture_detection/touch_disposition_gesture_filter.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/ui/events/gesture_detection/touch_disposition_gesture_filter.cc')
-rw-r--r--chromium/ui/events/gesture_detection/touch_disposition_gesture_filter.cc400
1 files changed, 400 insertions, 0 deletions
diff --git a/chromium/ui/events/gesture_detection/touch_disposition_gesture_filter.cc b/chromium/ui/events/gesture_detection/touch_disposition_gesture_filter.cc
new file mode 100644
index 00000000000..0e775dd9c19
--- /dev/null
+++ b/chromium/ui/events/gesture_detection/touch_disposition_gesture_filter.cc
@@ -0,0 +1,400 @@
+// Copyright 2014 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 "ui/events/gesture_detection/touch_disposition_gesture_filter.h"
+
+#include "base/auto_reset.h"
+#include "base/logging.h"
+#include "ui/events/gesture_event_details.h"
+
+namespace ui {
+namespace {
+
+// A BitSet32 is used for tracking dropped gesture types.
+COMPILE_ASSERT(ET_GESTURE_TYPE_END - ET_GESTURE_TYPE_START < 32,
+ gesture_type_count_too_large);
+
+GestureEventData CreateGesture(EventType type,
+ int motion_event_id,
+ const GestureEventDataPacket& packet) {
+ return GestureEventData(GestureEventDetails(type, 0, 0),
+ motion_event_id,
+ packet.timestamp(),
+ packet.touch_location().x(),
+ packet.touch_location().y(),
+ packet.raw_touch_location().x(),
+ packet.raw_touch_location().y(),
+ 1,
+ gfx::RectF(packet.touch_location(), gfx::SizeF()));
+}
+
+enum RequiredTouches {
+ RT_NONE = 0,
+ RT_START = 1 << 0,
+ RT_CURRENT = 1 << 1,
+};
+
+struct DispositionHandlingInfo {
+ // A bitwise-OR of |RequiredTouches|.
+ int required_touches;
+ EventType antecedent_event_type;
+
+ explicit DispositionHandlingInfo(int required_touches)
+ : required_touches(required_touches), antecedent_event_type(ET_UNKNOWN) {}
+
+ DispositionHandlingInfo(int required_touches,
+ EventType antecedent_event_type)
+ : required_touches(required_touches),
+ antecedent_event_type(antecedent_event_type) {}
+};
+
+DispositionHandlingInfo Info(int required_touches) {
+ return DispositionHandlingInfo(required_touches);
+}
+
+DispositionHandlingInfo Info(int required_touches,
+ EventType antecedent_event_type) {
+ return DispositionHandlingInfo(required_touches, antecedent_event_type);
+}
+
+// This approach to disposition handling is described at http://goo.gl/5G8PWJ.
+DispositionHandlingInfo GetDispositionHandlingInfo(EventType type) {
+ switch (type) {
+ case ET_GESTURE_TAP_DOWN:
+ return Info(RT_START);
+ case ET_GESTURE_TAP_CANCEL:
+ return Info(RT_START);
+ case ET_GESTURE_SHOW_PRESS:
+ return Info(RT_START);
+ case ET_GESTURE_LONG_PRESS:
+ return Info(RT_START);
+ case ET_GESTURE_LONG_TAP:
+ return Info(RT_START | RT_CURRENT);
+ case ET_GESTURE_TAP:
+ return Info(RT_START | RT_CURRENT, ET_GESTURE_TAP_UNCONFIRMED);
+ case ET_GESTURE_TAP_UNCONFIRMED:
+ return Info(RT_START | RT_CURRENT);
+ case ET_GESTURE_DOUBLE_TAP:
+ return Info(RT_START | RT_CURRENT, ET_GESTURE_TAP_UNCONFIRMED);
+ case ET_GESTURE_SCROLL_BEGIN:
+ return Info(RT_START | RT_CURRENT);
+ case ET_GESTURE_SCROLL_UPDATE:
+ return Info(RT_CURRENT, ET_GESTURE_SCROLL_BEGIN);
+ case ET_GESTURE_SCROLL_END:
+ return Info(RT_NONE, ET_GESTURE_SCROLL_BEGIN);
+ case ET_SCROLL_FLING_START:
+ // We rely on |EndScrollGestureIfNecessary| to end the scroll if the fling
+ // start is prevented.
+ return Info(RT_NONE, ET_GESTURE_SCROLL_UPDATE);
+ case ET_SCROLL_FLING_CANCEL:
+ return Info(RT_NONE, ET_SCROLL_FLING_START);
+ case ET_GESTURE_PINCH_BEGIN:
+ return Info(RT_START, ET_GESTURE_SCROLL_BEGIN);
+ case ET_GESTURE_PINCH_UPDATE:
+ return Info(RT_CURRENT, ET_GESTURE_PINCH_BEGIN);
+ case ET_GESTURE_PINCH_END:
+ return Info(RT_NONE, ET_GESTURE_PINCH_BEGIN);
+ case ET_GESTURE_BEGIN:
+ return Info(RT_START);
+ case ET_GESTURE_END:
+ return Info(RT_NONE, ET_GESTURE_BEGIN);
+ case ET_GESTURE_SWIPE:
+ return Info(RT_START, ET_GESTURE_SCROLL_BEGIN);
+ case ET_GESTURE_TWO_FINGER_TAP:
+ return Info(RT_START);
+ default:
+ break;
+ }
+ NOTREACHED();
+ return Info(RT_NONE);
+}
+
+int GetGestureTypeIndex(EventType type) {
+ DCHECK_GE(type, ET_GESTURE_TYPE_START);
+ DCHECK_LE(type, ET_GESTURE_TYPE_END);
+ return type - ET_GESTURE_TYPE_START;
+}
+
+bool IsTouchStartEvent(GestureEventDataPacket::GestureSource gesture_source) {
+ return gesture_source == GestureEventDataPacket::TOUCH_SEQUENCE_START ||
+ gesture_source == GestureEventDataPacket::TOUCH_START;
+}
+
+} // namespace
+
+// TouchDispositionGestureFilter
+
+TouchDispositionGestureFilter::TouchDispositionGestureFilter(
+ TouchDispositionGestureFilterClient* client)
+ : client_(client),
+ needs_tap_ending_event_(false),
+ needs_show_press_event_(false),
+ needs_fling_ending_event_(false),
+ needs_scroll_ending_event_(false) {
+ DCHECK(client_);
+}
+
+TouchDispositionGestureFilter::~TouchDispositionGestureFilter() {
+}
+
+TouchDispositionGestureFilter::PacketResult
+TouchDispositionGestureFilter::OnGesturePacket(
+ const GestureEventDataPacket& packet) {
+ if (packet.gesture_source() == GestureEventDataPacket::UNDEFINED ||
+ packet.gesture_source() == GestureEventDataPacket::INVALID)
+ return INVALID_PACKET_TYPE;
+
+ if (packet.gesture_source() == GestureEventDataPacket::TOUCH_SEQUENCE_START)
+ sequences_.push(GestureSequence());
+
+ if (IsEmpty())
+ return INVALID_PACKET_ORDER;
+
+ if (packet.gesture_source() == GestureEventDataPacket::TOUCH_TIMEOUT &&
+ Tail().empty()) {
+ // Handle the timeout packet immediately if the packet preceding the timeout
+ // has already been dispatched.
+ FilterAndSendPacket(packet);
+ return SUCCESS;
+ }
+
+ Tail().push(packet);
+ return SUCCESS;
+}
+
+void TouchDispositionGestureFilter::OnTouchEventAck(bool event_consumed) {
+ // Spurious touch acks from the renderer should not trigger a crash.
+ if (IsEmpty() || (Head().empty() && sequences_.size() == 1))
+ return;
+
+ if (Head().empty())
+ PopGestureSequence();
+
+ GestureSequence& sequence = Head();
+
+ // Dispatch the packet corresponding to the ack'ed touch, as well as any
+ // additional timeout-based packets queued before the ack was received.
+ bool touch_packet_for_current_ack_handled = false;
+ while (!sequence.empty()) {
+ DCHECK_NE(sequence.front().gesture_source(),
+ GestureEventDataPacket::UNDEFINED);
+ DCHECK_NE(sequence.front().gesture_source(),
+ GestureEventDataPacket::INVALID);
+
+ GestureEventDataPacket::GestureSource source =
+ sequence.front().gesture_source();
+ if (source != GestureEventDataPacket::TOUCH_TIMEOUT) {
+ // We should handle at most one non-timeout based packet.
+ if (touch_packet_for_current_ack_handled)
+ break;
+ state_.OnTouchEventAck(event_consumed, IsTouchStartEvent(source));
+ touch_packet_for_current_ack_handled = true;
+ }
+ // We need to pop the current sequence before sending the packet, because
+ // sending the packet could result in this method being re-entered (e.g. on
+ // Aura, we could trigger a touch-cancel). As popping the sequence destroys
+ // the packet, we copy the packet before popping it.
+ const GestureEventDataPacket packet = sequence.front();
+ sequence.pop();
+ FilterAndSendPacket(packet);
+ }
+ DCHECK(touch_packet_for_current_ack_handled);
+}
+
+bool TouchDispositionGestureFilter::IsEmpty() const {
+ return sequences_.empty();
+}
+
+void TouchDispositionGestureFilter::FilterAndSendPacket(
+ const GestureEventDataPacket& packet) {
+ if (packet.gesture_source() == GestureEventDataPacket::TOUCH_SEQUENCE_START) {
+ CancelTapIfNecessary(packet);
+ EndScrollIfNecessary(packet);
+ CancelFlingIfNecessary(packet);
+ } else if (packet.gesture_source() == GestureEventDataPacket::TOUCH_START) {
+ CancelTapIfNecessary(packet);
+ }
+
+ for (size_t i = 0; i < packet.gesture_count(); ++i) {
+ const GestureEventData& gesture = packet.gesture(i);
+ DCHECK_GE(gesture.details.type(), ET_GESTURE_TYPE_START);
+ DCHECK_LE(gesture.details.type(), ET_GESTURE_TYPE_END);
+ if (state_.Filter(gesture.details.type())) {
+ CancelTapIfNecessary(packet);
+ continue;
+ }
+ if (packet.gesture_source() == GestureEventDataPacket::TOUCH_TIMEOUT) {
+ // Sending a timed gesture could delete |this|, so we need to return
+ // directly after the |SendGesture| call.
+ SendGesture(gesture, packet);
+ return;
+ }
+
+ SendGesture(gesture, packet);
+ }
+
+ if (packet.gesture_source() ==
+ GestureEventDataPacket::TOUCH_SEQUENCE_CANCEL) {
+ EndScrollIfNecessary(packet);
+ CancelTapIfNecessary(packet);
+ } else if (packet.gesture_source() ==
+ GestureEventDataPacket::TOUCH_SEQUENCE_END) {
+ EndScrollIfNecessary(packet);
+ }
+}
+
+void TouchDispositionGestureFilter::SendGesture(
+ const GestureEventData& event,
+ const GestureEventDataPacket& packet_being_sent) {
+ // TODO(jdduke): Factor out gesture stream reparation code into a standalone
+ // utility class.
+ switch (event.type()) {
+ case ET_GESTURE_LONG_TAP:
+ if (!needs_tap_ending_event_)
+ return;
+ CancelTapIfNecessary(packet_being_sent);
+ CancelFlingIfNecessary(packet_being_sent);
+ break;
+ case ET_GESTURE_TAP_DOWN:
+ DCHECK(!needs_tap_ending_event_);
+ ending_event_motion_event_id_ = event.motion_event_id;
+ needs_show_press_event_ = true;
+ needs_tap_ending_event_ = true;
+ break;
+ case ET_GESTURE_SHOW_PRESS:
+ if (!needs_show_press_event_)
+ return;
+ needs_show_press_event_ = false;
+ break;
+ case ET_GESTURE_DOUBLE_TAP:
+ CancelTapIfNecessary(packet_being_sent);
+ needs_show_press_event_ = false;
+ break;
+ case ET_GESTURE_TAP:
+ DCHECK(needs_tap_ending_event_);
+ if (needs_show_press_event_) {
+ SendGesture(GestureEventData(ET_GESTURE_SHOW_PRESS, event),
+ packet_being_sent);
+ DCHECK(!needs_show_press_event_);
+ }
+ needs_tap_ending_event_ = false;
+ break;
+ case ET_GESTURE_TAP_CANCEL:
+ needs_show_press_event_ = false;
+ needs_tap_ending_event_ = false;
+ break;
+ case ET_GESTURE_SCROLL_BEGIN:
+ CancelTapIfNecessary(packet_being_sent);
+ CancelFlingIfNecessary(packet_being_sent);
+ EndScrollIfNecessary(packet_being_sent);
+ ending_event_motion_event_id_ = event.motion_event_id;
+ needs_scroll_ending_event_ = true;
+ break;
+ case ET_GESTURE_SCROLL_END:
+ needs_scroll_ending_event_ = false;
+ break;
+ case ET_SCROLL_FLING_START:
+ CancelFlingIfNecessary(packet_being_sent);
+ ending_event_motion_event_id_ = event.motion_event_id;
+ needs_fling_ending_event_ = true;
+ needs_scroll_ending_event_ = false;
+ break;
+ case ET_SCROLL_FLING_CANCEL:
+ needs_fling_ending_event_ = false;
+ break;
+ default:
+ break;
+ }
+ client_->ForwardGestureEvent(event);
+}
+
+void TouchDispositionGestureFilter::CancelTapIfNecessary(
+ const GestureEventDataPacket& packet_being_sent) {
+ if (!needs_tap_ending_event_)
+ return;
+
+ SendGesture(CreateGesture(ET_GESTURE_TAP_CANCEL,
+ ending_event_motion_event_id_,
+ packet_being_sent),
+ packet_being_sent);
+ DCHECK(!needs_tap_ending_event_);
+}
+
+void TouchDispositionGestureFilter::CancelFlingIfNecessary(
+ const GestureEventDataPacket& packet_being_sent) {
+ if (!needs_fling_ending_event_)
+ return;
+
+ SendGesture(CreateGesture(ET_SCROLL_FLING_CANCEL,
+ ending_event_motion_event_id_,
+ packet_being_sent),
+ packet_being_sent);
+ DCHECK(!needs_fling_ending_event_);
+}
+
+void TouchDispositionGestureFilter::EndScrollIfNecessary(
+ const GestureEventDataPacket& packet_being_sent) {
+ if (!needs_scroll_ending_event_)
+ return;
+
+ SendGesture(CreateGesture(ET_GESTURE_SCROLL_END,
+ ending_event_motion_event_id_,
+ packet_being_sent),
+ packet_being_sent);
+ DCHECK(!needs_scroll_ending_event_);
+}
+
+void TouchDispositionGestureFilter::PopGestureSequence() {
+ DCHECK(Head().empty());
+ state_ = GestureHandlingState();
+ sequences_.pop();
+}
+
+TouchDispositionGestureFilter::GestureSequence&
+TouchDispositionGestureFilter::Head() {
+ DCHECK(!sequences_.empty());
+ return sequences_.front();
+}
+
+TouchDispositionGestureFilter::GestureSequence&
+TouchDispositionGestureFilter::Tail() {
+ DCHECK(!sequences_.empty());
+ return sequences_.back();
+}
+
+// TouchDispositionGestureFilter::GestureHandlingState
+
+TouchDispositionGestureFilter::GestureHandlingState::GestureHandlingState()
+ : start_touch_consumed_(false),
+ current_touch_consumed_(false) {}
+
+void TouchDispositionGestureFilter::GestureHandlingState::OnTouchEventAck(
+ bool event_consumed,
+ bool is_touch_start_event) {
+ current_touch_consumed_ = event_consumed;
+ if (event_consumed && is_touch_start_event)
+ start_touch_consumed_ = true;
+}
+
+bool TouchDispositionGestureFilter::GestureHandlingState::Filter(
+ EventType gesture_type) {
+ DispositionHandlingInfo disposition_handling_info =
+ GetDispositionHandlingInfo(gesture_type);
+
+ int required_touches = disposition_handling_info.required_touches;
+ EventType antecedent_event_type =
+ disposition_handling_info.antecedent_event_type;
+ if ((required_touches & RT_START && start_touch_consumed_) ||
+ (required_touches & RT_CURRENT && current_touch_consumed_) ||
+ (antecedent_event_type != ET_UNKNOWN &&
+ last_gesture_of_type_dropped_.has_bit(
+ GetGestureTypeIndex(antecedent_event_type)))) {
+ last_gesture_of_type_dropped_.mark_bit(GetGestureTypeIndex(gesture_type));
+ return true;
+ }
+ last_gesture_of_type_dropped_.clear_bit(GetGestureTypeIndex(gesture_type));
+ return false;
+}
+
+} // namespace content