summaryrefslogtreecommitdiffstats
path: root/chromium/content/browser/renderer_host/input/gesture_event_queue.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/content/browser/renderer_host/input/gesture_event_queue.cc')
-rw-r--r--chromium/content/browser/renderer_host/input/gesture_event_queue.cc378
1 files changed, 378 insertions, 0 deletions
diff --git a/chromium/content/browser/renderer_host/input/gesture_event_queue.cc b/chromium/content/browser/renderer_host/input/gesture_event_queue.cc
new file mode 100644
index 00000000000..b16923be17f
--- /dev/null
+++ b/chromium/content/browser/renderer_host/input/gesture_event_queue.cc
@@ -0,0 +1,378 @@
+// 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 "content/browser/renderer_host/input/gesture_event_queue.h"
+
+#include "base/debug/trace_event.h"
+#include "base/strings/string_number_conversions.h"
+#include "content/browser/renderer_host/input/input_router.h"
+#include "content/browser/renderer_host/input/touchpad_tap_suppression_controller.h"
+#include "content/browser/renderer_host/input/touchscreen_tap_suppression_controller.h"
+#include "content/public/common/content_switches.h"
+
+using blink::WebGestureEvent;
+using blink::WebInputEvent;
+
+namespace content {
+
+GestureEventQueue::Config::Config() {
+}
+
+GestureEventQueue::GestureEventQueue(
+ GestureEventQueueClient* client,
+ TouchpadTapSuppressionControllerClient* touchpad_client,
+ const Config& config)
+ : client_(client),
+ fling_in_progress_(false),
+ scrolling_in_progress_(false),
+ ignore_next_ack_(false),
+ touchpad_tap_suppression_controller_(
+ touchpad_client,
+ config.touchpad_tap_suppression_config),
+ touchscreen_tap_suppression_controller_(
+ this,
+ config.touchscreen_tap_suppression_config),
+ debounce_interval_(config.debounce_interval) {
+ DCHECK(client);
+ DCHECK(touchpad_client);
+}
+
+GestureEventQueue::~GestureEventQueue() { }
+
+bool GestureEventQueue::ShouldDiscardFlingCancelEvent(
+ const GestureEventWithLatencyInfo& gesture_event) const {
+ if (coalesced_gesture_events_.empty() && fling_in_progress_)
+ return false;
+ GestureQueue::const_reverse_iterator it =
+ coalesced_gesture_events_.rbegin();
+ while (it != coalesced_gesture_events_.rend()) {
+ if (it->event.type == WebInputEvent::GestureFlingStart)
+ return false;
+ if (it->event.type == WebInputEvent::GestureFlingCancel)
+ return true;
+ it++;
+ }
+ return true;
+}
+
+bool GestureEventQueue::ShouldForwardForBounceReduction(
+ const GestureEventWithLatencyInfo& gesture_event) {
+ if (debounce_interval_ <= base::TimeDelta())
+ return true;
+ switch (gesture_event.event.type) {
+ case WebInputEvent::GestureScrollUpdate:
+ if (!scrolling_in_progress_) {
+ debounce_deferring_timer_.Start(
+ FROM_HERE,
+ debounce_interval_,
+ this,
+ &GestureEventQueue::SendScrollEndingEventsNow);
+ } else {
+ // Extend the bounce interval.
+ debounce_deferring_timer_.Reset();
+ }
+ scrolling_in_progress_ = true;
+ debouncing_deferral_queue_.clear();
+ return true;
+ case WebInputEvent::GesturePinchBegin:
+ case WebInputEvent::GesturePinchEnd:
+ case WebInputEvent::GesturePinchUpdate:
+ // TODO(rjkroege): Debounce pinch (http://crbug.com/147647)
+ return true;
+ default:
+ if (scrolling_in_progress_) {
+ debouncing_deferral_queue_.push_back(gesture_event);
+ return false;
+ }
+ return true;
+ }
+}
+
+// NOTE: The filters are applied successively. This simplifies the change.
+bool GestureEventQueue::ShouldForward(
+ const GestureEventWithLatencyInfo& gesture_event) {
+ TRACE_EVENT0("input", "GestureEventQueue::ShouldForward");
+ return ShouldForwardForBounceReduction(gesture_event) &&
+ ShouldForwardForGFCFiltering(gesture_event) &&
+ ShouldForwardForTapSuppression(gesture_event) &&
+ ShouldForwardForCoalescing(gesture_event);
+}
+
+bool GestureEventQueue::ShouldForwardForGFCFiltering(
+ const GestureEventWithLatencyInfo& gesture_event) const {
+ return gesture_event.event.type != WebInputEvent::GestureFlingCancel ||
+ !ShouldDiscardFlingCancelEvent(gesture_event);
+}
+
+bool GestureEventQueue::ShouldForwardForTapSuppression(
+ const GestureEventWithLatencyInfo& gesture_event) {
+ switch (gesture_event.event.type) {
+ case WebInputEvent::GestureFlingCancel:
+ if (gesture_event.event.sourceDevice ==
+ blink::WebGestureDeviceTouchscreen)
+ touchscreen_tap_suppression_controller_.GestureFlingCancel();
+ else
+ touchpad_tap_suppression_controller_.GestureFlingCancel();
+ return true;
+ case WebInputEvent::GestureTapDown:
+ case WebInputEvent::GestureShowPress:
+ case WebInputEvent::GestureTapUnconfirmed:
+ case WebInputEvent::GestureTapCancel:
+ case WebInputEvent::GestureTap:
+ case WebInputEvent::GestureDoubleTap:
+ if (gesture_event.event.sourceDevice ==
+ blink::WebGestureDeviceTouchscreen) {
+ return !touchscreen_tap_suppression_controller_.FilterTapEvent(
+ gesture_event);
+ }
+ return true;
+ default:
+ return true;
+ }
+}
+
+bool GestureEventQueue::ShouldForwardForCoalescing(
+ const GestureEventWithLatencyInfo& gesture_event) {
+ switch (gesture_event.event.type) {
+ case WebInputEvent::GestureFlingCancel:
+ fling_in_progress_ = false;
+ break;
+ case WebInputEvent::GestureFlingStart:
+ fling_in_progress_ = true;
+ break;
+ case WebInputEvent::GesturePinchUpdate:
+ case WebInputEvent::GestureScrollUpdate:
+ MergeOrInsertScrollAndPinchEvent(gesture_event);
+ return ShouldHandleEventNow();
+ default:
+ break;
+ }
+ coalesced_gesture_events_.push_back(gesture_event);
+ return ShouldHandleEventNow();
+}
+
+void GestureEventQueue::ProcessGestureAck(InputEventAckState ack_result,
+ WebInputEvent::Type type,
+ const ui::LatencyInfo& latency) {
+ TRACE_EVENT0("input", "GestureEventQueue::ProcessGestureAck");
+
+ if (coalesced_gesture_events_.empty()) {
+ DLOG(ERROR) << "Received unexpected ACK for event type " << type;
+ return;
+ }
+
+ // It's possible that the ack for the second event in an in-flight, coalesced
+ // Gesture{Scroll,Pinch}Update pair is received prior to the first event ack.
+ // TODO(jdduke): Unify GSU/GPU pairs into a single event, crbug.com/359115.
+ size_t event_index = 0;
+ if (ignore_next_ack_ &&
+ coalesced_gesture_events_.size() > 1 &&
+ coalesced_gesture_events_[0].event.type != type &&
+ coalesced_gesture_events_[1].event.type == type) {
+ event_index = 1;
+ }
+ GestureEventWithLatencyInfo event_with_latency =
+ coalesced_gesture_events_[event_index];
+ DCHECK_EQ(event_with_latency.event.type, type);
+ event_with_latency.latency.AddNewLatencyFrom(latency);
+
+ // Ack'ing an event may enqueue additional gesture events. By ack'ing the
+ // event before the forwarding of queued events below, such additional events
+ // can be coalesced with existing queued events prior to dispatch.
+ client_->OnGestureEventAck(event_with_latency, ack_result);
+
+ const bool processed = (INPUT_EVENT_ACK_STATE_CONSUMED == ack_result);
+ if (type == WebInputEvent::GestureFlingCancel) {
+ if (event_with_latency.event.sourceDevice ==
+ blink::WebGestureDeviceTouchscreen)
+ touchscreen_tap_suppression_controller_.GestureFlingCancelAck(processed);
+ else
+ touchpad_tap_suppression_controller_.GestureFlingCancelAck(processed);
+ }
+ DCHECK_LT(event_index, coalesced_gesture_events_.size());
+ coalesced_gesture_events_.erase(coalesced_gesture_events_.begin() +
+ event_index);
+
+ if (ignore_next_ack_) {
+ ignore_next_ack_ = false;
+ return;
+ }
+
+ if (coalesced_gesture_events_.empty())
+ return;
+
+ const GestureEventWithLatencyInfo& first_gesture_event =
+ coalesced_gesture_events_.front();
+
+ // TODO(jdduke): Unify GSU/GPU pairs into a single event, crbug.com/359115.
+ // Check for the coupled GesturePinchUpdate before sending either event,
+ // handling the case where the first GestureScrollUpdate ack is synchronous.
+ GestureEventWithLatencyInfo second_gesture_event;
+ if (first_gesture_event.event.type == WebInputEvent::GestureScrollUpdate &&
+ coalesced_gesture_events_.size() > 1 &&
+ coalesced_gesture_events_[1].event.type ==
+ WebInputEvent::GesturePinchUpdate) {
+ second_gesture_event = coalesced_gesture_events_[1];
+ ignore_next_ack_ = true;
+ }
+
+ client_->SendGestureEventImmediately(first_gesture_event);
+ if (second_gesture_event.event.type != WebInputEvent::Undefined)
+ client_->SendGestureEventImmediately(second_gesture_event);
+}
+
+TouchpadTapSuppressionController*
+ GestureEventQueue::GetTouchpadTapSuppressionController() {
+ return &touchpad_tap_suppression_controller_;
+}
+
+bool GestureEventQueue::ExpectingGestureAck() const {
+ return !coalesced_gesture_events_.empty();
+}
+
+void GestureEventQueue::FlingHasBeenHalted() {
+ fling_in_progress_ = false;
+}
+
+bool GestureEventQueue::ShouldHandleEventNow() const {
+ return coalesced_gesture_events_.size() == 1;
+}
+
+void GestureEventQueue::ForwardGestureEvent(
+ const GestureEventWithLatencyInfo& gesture_event) {
+ if (ShouldForwardForCoalescing(gesture_event))
+ client_->SendGestureEventImmediately(gesture_event);
+}
+
+void GestureEventQueue::SendScrollEndingEventsNow() {
+ scrolling_in_progress_ = false;
+ if (debouncing_deferral_queue_.empty())
+ return;
+ GestureQueue debouncing_deferral_queue;
+ debouncing_deferral_queue.swap(debouncing_deferral_queue_);
+ for (GestureQueue::const_iterator it = debouncing_deferral_queue.begin();
+ it != debouncing_deferral_queue.end(); it++) {
+ if (ShouldForwardForGFCFiltering(*it) &&
+ ShouldForwardForTapSuppression(*it) &&
+ ShouldForwardForCoalescing(*it)) {
+ client_->SendGestureEventImmediately(*it);
+ }
+ }
+}
+
+void GestureEventQueue::MergeOrInsertScrollAndPinchEvent(
+ const GestureEventWithLatencyInfo& gesture_event) {
+ const size_t unsent_events_count =
+ coalesced_gesture_events_.size() - EventsInFlightCount();
+ if (!unsent_events_count) {
+ coalesced_gesture_events_.push_back(gesture_event);
+ return;
+ }
+
+ GestureEventWithLatencyInfo* last_event = &coalesced_gesture_events_.back();
+ if (last_event->CanCoalesceWith(gesture_event)) {
+ last_event->CoalesceWith(gesture_event);
+ return;
+ }
+
+ if (!ShouldTryMerging(gesture_event, *last_event)) {
+ coalesced_gesture_events_.push_back(gesture_event);
+ return;
+ }
+
+ GestureEventWithLatencyInfo scroll_event;
+ GestureEventWithLatencyInfo pinch_event;
+ scroll_event.event.modifiers |= gesture_event.event.modifiers;
+ scroll_event.event.sourceDevice = gesture_event.event.sourceDevice;
+ scroll_event.event.timeStampSeconds = gesture_event.event.timeStampSeconds;
+ // Keep the oldest LatencyInfo.
+ DCHECK_LE(last_event->latency.trace_id, gesture_event.latency.trace_id);
+ scroll_event.latency = last_event->latency;
+ pinch_event = scroll_event;
+ scroll_event.event.type = WebInputEvent::GestureScrollUpdate;
+ pinch_event.event.type = WebInputEvent::GesturePinchUpdate;
+ pinch_event.event.x = gesture_event.event.type ==
+ WebInputEvent::GesturePinchUpdate ?
+ gesture_event.event.x : last_event->event.x;
+ pinch_event.event.y = gesture_event.event.type ==
+ WebInputEvent::GesturePinchUpdate ?
+ gesture_event.event.y : last_event->event.y;
+
+ gfx::Transform combined_scroll_pinch = GetTransformForEvent(*last_event);
+ // Only include the second-to-last event in the coalesced pair if it exists
+ // and can be combined with the new event.
+ if (unsent_events_count > 1) {
+ const GestureEventWithLatencyInfo& second_last_event =
+ coalesced_gesture_events_[coalesced_gesture_events_.size() - 2];
+ if (ShouldTryMerging(gesture_event, second_last_event)) {
+ // Keep the oldest LatencyInfo.
+ DCHECK_LE(second_last_event.latency.trace_id,
+ scroll_event.latency.trace_id);
+ scroll_event.latency = second_last_event.latency;
+ pinch_event.latency = second_last_event.latency;
+ combined_scroll_pinch.PreconcatTransform(
+ GetTransformForEvent(second_last_event));
+ coalesced_gesture_events_.pop_back();
+ }
+ }
+ combined_scroll_pinch.ConcatTransform(GetTransformForEvent(gesture_event));
+ coalesced_gesture_events_.pop_back();
+
+ float combined_scale =
+ SkMScalarToFloat(combined_scroll_pinch.matrix().get(0, 0));
+ float combined_scroll_pinch_x =
+ SkMScalarToFloat(combined_scroll_pinch.matrix().get(0, 3));
+ float combined_scroll_pinch_y =
+ SkMScalarToFloat(combined_scroll_pinch.matrix().get(1, 3));
+ scroll_event.event.data.scrollUpdate.deltaX =
+ (combined_scroll_pinch_x + pinch_event.event.x) / combined_scale -
+ pinch_event.event.x;
+ scroll_event.event.data.scrollUpdate.deltaY =
+ (combined_scroll_pinch_y + pinch_event.event.y) / combined_scale -
+ pinch_event.event.y;
+ coalesced_gesture_events_.push_back(scroll_event);
+ pinch_event.event.data.pinchUpdate.scale = combined_scale;
+ coalesced_gesture_events_.push_back(pinch_event);
+}
+
+bool GestureEventQueue::ShouldTryMerging(
+ const GestureEventWithLatencyInfo& new_event,
+ const GestureEventWithLatencyInfo& event_in_queue) const {
+ DLOG_IF(WARNING,
+ new_event.event.timeStampSeconds <
+ event_in_queue.event.timeStampSeconds)
+ << "Event time not monotonic?\n";
+ return (event_in_queue.event.type == WebInputEvent::GestureScrollUpdate ||
+ event_in_queue.event.type == WebInputEvent::GesturePinchUpdate) &&
+ event_in_queue.event.modifiers == new_event.event.modifiers &&
+ event_in_queue.event.sourceDevice == new_event.event.sourceDevice;
+}
+
+gfx::Transform GestureEventQueue::GetTransformForEvent(
+ const GestureEventWithLatencyInfo& gesture_event) const {
+ gfx::Transform gesture_transform;
+ if (gesture_event.event.type == WebInputEvent::GestureScrollUpdate) {
+ gesture_transform.Translate(gesture_event.event.data.scrollUpdate.deltaX,
+ gesture_event.event.data.scrollUpdate.deltaY);
+ } else if (gesture_event.event.type == WebInputEvent::GesturePinchUpdate) {
+ float scale = gesture_event.event.data.pinchUpdate.scale;
+ gesture_transform.Translate(-gesture_event.event.x, -gesture_event.event.y);
+ gesture_transform.Scale(scale,scale);
+ gesture_transform.Translate(gesture_event.event.x, gesture_event.event.y);
+ }
+ return gesture_transform;
+}
+
+size_t GestureEventQueue::EventsInFlightCount() const {
+ if (coalesced_gesture_events_.empty())
+ return 0;
+
+ if (!ignore_next_ack_)
+ return 1;
+
+ DCHECK_GT(coalesced_gesture_events_.size(), 1U);
+ return 2;
+}
+
+} // namespace content