Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(730)

Unified Diff: content/browser/renderer_host/input/passthrough_touch_event_queue.cc

Issue 2715623002: Add a passthrough touch event queue. (Closed)
Patch Set: Rebase on throttling change Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: content/browser/renderer_host/input/passthrough_touch_event_queue.cc
diff --git a/content/browser/renderer_host/input/passthrough_touch_event_queue.cc b/content/browser/renderer_host/input/passthrough_touch_event_queue.cc
new file mode 100644
index 0000000000000000000000000000000000000000..fb5a0f42f70c46d9a81cfb321b98ba7ebbb73dcc
--- /dev/null
+++ b/content/browser/renderer_host/input/passthrough_touch_event_queue.cc
@@ -0,0 +1,382 @@
+// Copyright 2017 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/passthrough_touch_event_queue.h"
+
+#include <utility>
+
+#include "base/auto_reset.h"
tdresser 2017/02/24 20:04:28 Are we using all these includes?
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/trace_event/trace_event.h"
+#include "content/browser/renderer_host/input/touch_timeout_handler.h"
+#include "content/common/input/web_touch_event_traits.h"
+#include "ui/events/base_event_utils.h"
+#include "ui/gfx/geometry/point_f.h"
+
+using blink::WebInputEvent;
+using blink::WebTouchEvent;
+using blink::WebTouchPoint;
+using ui::LatencyInfo;
+
+namespace content {
+namespace {
+
+// Compare all properties of touch points to determine the state.
+bool HasPointChanged(const WebTouchPoint& point_1,
tdresser 2017/02/24 20:04:28 Comment doesn't really add anything. The name imp
+ const WebTouchPoint& point_2) {
+ DCHECK_EQ(point_1.id, point_2.id);
+ if (point_1.screenPosition != point_2.screenPosition ||
+ point_1.position != point_2.position ||
+ point_1.radiusX != point_2.radiusX ||
+ point_1.radiusY != point_2.radiusY ||
+ point_1.rotationAngle != point_2.rotationAngle ||
+ point_1.force != point_2.force || point_1.tiltX != point_2.tiltX ||
+ point_1.tiltY != point_2.tiltY) {
+ return true;
+ }
+ return false;
+}
+
+} // namespace
+
+PassthroughTouchEventQueue::TouchEventWithLatencyInfoAndAckState::
+ TouchEventWithLatencyInfoAndAckState(const TouchEventWithLatencyInfo& event)
+ : TouchEventWithLatencyInfo(event),
+ ack_state_(INPUT_EVENT_ACK_STATE_UNKNOWN) {}
+
+bool PassthroughTouchEventQueue::TouchEventWithLatencyInfoAndAckState::
+operator<(const TouchEventWithLatencyInfoAndAckState& other) const {
+ return event.uniqueTouchEventId < other.event.uniqueTouchEventId;
tdresser 2017/02/24 20:04:28 Every 2 years or so of use, we'll wrap around the
+}
+
+PassthroughTouchEventQueue::PassthroughTouchEventQueue(
+ TouchEventQueueClient* client,
+ const Config& config)
+ : client_(client),
+ has_handlers_(true),
+ maybe_has_handler_for_current_sequence_(false),
+ drop_remaining_touches_in_sequence_(false),
+ send_touch_events_async_(false) {
+ if (config.touch_ack_timeout_supported) {
+ timeout_handler_.reset(
+ new TouchTimeoutHandler(this, config.desktop_touch_ack_timeout_delay,
+ config.mobile_touch_ack_timeout_delay));
+ }
+}
+
+PassthroughTouchEventQueue::~PassthroughTouchEventQueue() {}
+
+void PassthroughTouchEventQueue::SendTouchCancelEventForTouchEvent(
+ const TouchEventWithLatencyInfo& event_to_cancel) {
+ TouchEventWithLatencyInfo event = event_to_cancel;
+ WebTouchEventTraits::ResetTypeAndTouchStates(
+ WebInputEvent::TouchCancel,
+ // TODO(rbyers): Shouldn't we use a fresh timestamp?
+ event.event.timeStampSeconds(), &event.event);
+ SendTouchEventImmediately(&event, false);
+}
+
+void PassthroughTouchEventQueue::QueueEvent(
+ const TouchEventWithLatencyInfo& event) {
+ TRACE_EVENT0("input", "PassthroughTouchEventQueue::QueueEvent");
+ PreFilterResult filter_result = FilterBeforeForwarding(event.event);
+ if (filter_result != FORWARD_TO_RENDERER) {
+ client_->OnFilteringTouchEvent(event.event);
+
+ InputEventAckState ack_state =
+ filter_result == ACK_WITH_NO_CONSUMER_EXISTS
+ ? INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS
+ : INPUT_EVENT_ACK_STATE_NOT_CONSUMED;
+ TouchEventWithLatencyInfoAndAckState event_with_ack_state = event;
+ event_with_ack_state.set_ack_state(ack_state);
+ outstanding_touches_.insert(event_with_ack_state);
+ AckCompletedEvents();
+ return;
+ }
+ TouchEventWithLatencyInfo cloned_event(event);
+ SendTouchEventImmediately(&cloned_event, true);
+}
+
+void PassthroughTouchEventQueue::PrependTouchScrollNotification() {
+ TRACE_EVENT0("input",
+ "PassthroughTouchEventQueue::PrependTouchScrollNotification");
+
+ TouchEventWithLatencyInfo touch(
+ WebInputEvent::TouchScrollStarted, WebInputEvent::NoModifiers,
+ ui::EventTimeStampToSeconds(ui::EventTimeForNow()), LatencyInfo());
+ touch.event.dispatchType = WebInputEvent::EventNonBlocking;
+ SendTouchEventImmediately(&touch, true);
+}
+
+void PassthroughTouchEventQueue::ProcessTouchAck(
+ InputEventAckState ack_result,
+ const LatencyInfo& latency_info,
+ const uint32_t unique_touch_event_id) {
+ TRACE_EVENT0("input", "PassthroughTouchEventQueue::ProcessTouchAck");
+ if (timeout_handler_ &&
+ timeout_handler_->ConfirmTouchEvent(unique_touch_event_id, ack_result))
+ return;
+
+ auto touch_event_iter = outstanding_touches_.begin();
+ while (touch_event_iter != outstanding_touches_.end()) {
+ if (unique_touch_event_id == touch_event_iter->event.uniqueTouchEventId)
+ break;
+ ++touch_event_iter;
+ }
+
+ if (touch_event_iter == outstanding_touches_.end())
+ return;
+
+ TouchEventWithLatencyInfoAndAckState event = *touch_event_iter;
+ touch_event_iter = outstanding_touches_.erase(touch_event_iter);
+ event.latency.AddNewLatencyFrom(latency_info);
+ event.set_ack_state(ack_result);
+ outstanding_touches_.insert(touch_event_iter, event);
+
+ AckCompletedEvents();
+}
+
+void PassthroughTouchEventQueue::OnGestureScrollEvent(
+ const GestureEventWithLatencyInfo& gesture_event) {
+ if (gesture_event.event.type() == blink::WebInputEvent::GestureScrollUpdate &&
+ gesture_event.event.resendingPluginId == -1) {
+ send_touch_events_async_ = true;
+ }
+}
+
+void PassthroughTouchEventQueue::OnGestureEventAck(
+ const GestureEventWithLatencyInfo& event,
+ InputEventAckState ack_result) {
+ // Turn events sent during gesture scrolls to be async.
+ if (event.event.type() == blink::WebInputEvent::GestureScrollUpdate &&
+ event.event.resendingPluginId == -1) {
+ send_touch_events_async_ = (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED);
+ }
+}
+
+void PassthroughTouchEventQueue::OnHasTouchEventHandlers(bool has_handlers) {
+ has_handlers_ = has_handlers;
+}
+
+bool PassthroughTouchEventQueue::IsPendingAckTouchStart() const {
+ if (outstanding_touches_.empty())
+ return false;
+
+ for (auto& iter : outstanding_touches_) {
+ if (iter.event.type() == WebInputEvent::TouchStart)
+ return true;
+ }
+ return false;
+}
+
+void PassthroughTouchEventQueue::SetAckTimeoutEnabled(bool enabled) {
+ if (timeout_handler_)
+ timeout_handler_->SetEnabled(enabled);
+}
+
+void PassthroughTouchEventQueue::SetIsMobileOptimizedSite(
+ bool mobile_optimized_site) {
+ if (timeout_handler_)
+ timeout_handler_->SetUseMobileTimeout(mobile_optimized_site);
+}
+
+bool PassthroughTouchEventQueue::IsAckTimeoutEnabled() const {
+ return timeout_handler_ && timeout_handler_->IsEnabled();
+}
+
+bool PassthroughTouchEventQueue::Empty() const {
+ return outstanding_touches_.empty();
+}
+
+void PassthroughTouchEventQueue::FlushQueue() {
+ drop_remaining_touches_in_sequence_ = true;
+ while (!outstanding_touches_.empty()) {
+ auto iter = outstanding_touches_.begin();
+ TouchEventWithLatencyInfoAndAckState event = *iter;
+ outstanding_touches_.erase(iter);
+ if (event.ack_state() == INPUT_EVENT_ACK_STATE_UNKNOWN)
+ event.set_ack_state(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
+ AckTouchEventToClient(event, event.ack_state());
+ }
+}
+
+void PassthroughTouchEventQueue::AckCompletedEvents() {
+ while (!outstanding_touches_.empty()) {
+ auto iter = outstanding_touches_.begin();
+ if (iter->ack_state() == INPUT_EVENT_ACK_STATE_UNKNOWN)
+ break;
+ TouchEventWithLatencyInfoAndAckState event = *iter;
+ outstanding_touches_.erase(iter);
+ AckTouchEventToClient(event, event.ack_state());
+ }
+}
+
+void PassthroughTouchEventQueue::AckTouchEventToClient(
+ const TouchEventWithLatencyInfo& acked_event,
+ InputEventAckState ack_result) {
+ UpdateTouchConsumerStates(acked_event.event, ack_result);
+
+ // Skip ack for TouchScrollStarted since it was synthesized within the queue.
+ if (acked_event.event.type() != WebInputEvent::TouchScrollStarted) {
+ client_->OnTouchEventAck(acked_event, ack_result);
+ }
+}
+
+void PassthroughTouchEventQueue::SendTouchEventImmediately(
+ TouchEventWithLatencyInfo* touch,
+ bool wait_for_ack) {
+ // Note: Touchstart events are marked cancelable to allow transitions between
+ // platform scrolling and JS pinching. Touchend events, however, remain
+ // uncancelable, mitigating the risk of jank when transitioning to a fling.
+ if (send_touch_events_async_ &&
+ touch->event.type() != WebInputEvent::TouchStart)
+ touch->event.dispatchType = WebInputEvent::EventNonBlocking;
+
+ if (touch->event.type() == WebInputEvent::TouchStart)
+ touch->event.touchStartOrFirstTouchMove = true;
+
+ // For touchmove events, compare touch points position from current event
+ // to last sent event and update touch points state.
+ if (touch->event.type() == WebInputEvent::TouchMove) {
+ CHECK(last_sent_touchevent_);
+ if (last_sent_touchevent_->type() == WebInputEvent::TouchStart)
+ touch->event.touchStartOrFirstTouchMove = true;
+ for (unsigned int i = 0; i < last_sent_touchevent_->touchesLength; ++i) {
+ const WebTouchPoint& last_touch_point = last_sent_touchevent_->touches[i];
+ // Touches with same id may not have same index in Touches array.
+ for (unsigned int j = 0; j < touch->event.touchesLength; ++j) {
+ const WebTouchPoint& current_touchmove_point = touch->event.touches[j];
+ if (current_touchmove_point.id != last_touch_point.id)
+ continue;
+
+ if (!HasPointChanged(last_touch_point, current_touchmove_point))
+ touch->event.touches[j].state = WebTouchPoint::StateStationary;
+
+ break;
+ }
+ }
+ }
+
+ if (touch->event.type() != WebInputEvent::TouchScrollStarted) {
+ if (last_sent_touchevent_)
+ *last_sent_touchevent_ = touch->event;
+ else
+ last_sent_touchevent_.reset(new WebTouchEvent(touch->event));
+ }
+
+ if (timeout_handler_)
+ timeout_handler_->StartIfNecessary(*touch);
+ if (wait_for_ack)
+ outstanding_touches_.insert(*touch);
+ client_->SendTouchEventImmediately(*touch);
+}
+
+PassthroughTouchEventQueue::PreFilterResult
+PassthroughTouchEventQueue::FilterBeforeForwarding(const WebTouchEvent& event) {
+ if (event.type() == WebInputEvent::TouchScrollStarted)
+ return FORWARD_TO_RENDERER;
+
+ if (WebTouchEventTraits::IsTouchSequenceStart(event)) {
+ // We don't know if we have a handler until we get the ACK back so
+ // assume it is true.
+ maybe_has_handler_for_current_sequence_ = true;
+ send_touch_events_async_ = false;
+ last_sent_touchevent_.reset();
+
+ drop_remaining_touches_in_sequence_ = false;
+ if (!has_handlers_) {
+ drop_remaining_touches_in_sequence_ = true;
+ return ACK_WITH_NO_CONSUMER_EXISTS;
+ }
+ }
+
+ if (timeout_handler_ && timeout_handler_->FilterEvent(event))
+ return ACK_WITH_NO_CONSUMER_EXISTS;
+
+ if (drop_remaining_touches_in_sequence_ &&
+ event.type() != WebInputEvent::TouchCancel) {
+ return ACK_WITH_NO_CONSUMER_EXISTS;
+ }
+
+ if (event.type() == WebInputEvent::TouchStart) {
+ return (has_handlers_ || maybe_has_handler_for_current_sequence_)
+ ? FORWARD_TO_RENDERER
+ : ACK_WITH_NO_CONSUMER_EXISTS;
+ }
+
+ if (maybe_has_handler_for_current_sequence_) {
+ // Only forward a touch if it has a non-stationary pointer that is active
+ // in the current touch sequence.
+ for (size_t i = 0; i < event.touchesLength; ++i) {
+ const WebTouchPoint& point = event.touches[i];
+ if (point.state == WebTouchPoint::StateStationary)
+ continue;
+
+ // |last_sent_touchevent_| will be non-null as long as there is an
+ // active touch sequence being forwarded to the renderer.
+ if (!last_sent_touchevent_)
+ continue;
+
+ for (size_t j = 0; j < last_sent_touchevent_->touchesLength; ++j) {
+ if (point.id != last_sent_touchevent_->touches[j].id)
+ continue;
+
+ if (event.type() != WebInputEvent::TouchMove)
+ return FORWARD_TO_RENDERER;
+
+ // All pointers in TouchMove events may have state as StateMoved,
+ // even though none of the pointers have not changed in real.
+ // Forward these events when at least one pointer has changed.
+ if (HasPointChanged(last_sent_touchevent_->touches[j], point))
+ return FORWARD_TO_RENDERER;
+
+ // This is a TouchMove event for which we have yet to find a
+ // non-stationary pointer. Continue checking the next pointers
+ // in the |event|.
+ break;
+ }
+ }
+ }
+
+ return ACK_WITH_NO_CONSUMER_EXISTS;
+}
+
+void PassthroughTouchEventQueue::UpdateTouchConsumerStates(
+ const WebTouchEvent& event,
+ InputEventAckState ack_result) {
+ if (event.type() == WebInputEvent::TouchStart) {
+ if (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED)
+ send_touch_events_async_ = false;
+
+ // Once we have the ack back for the sequence we know if there
+ // is a handler or not. Other touch-starts sent can upgrade
+ // whether we have a handler or not as well.
+ if (WebTouchEventTraits::IsTouchSequenceStart(event)) {
+ maybe_has_handler_for_current_sequence_ =
+ ack_result != INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS;
+ } else {
+ maybe_has_handler_for_current_sequence_ |=
+ ack_result != INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS;
+ }
+ } else if (WebTouchEventTraits::IsTouchSequenceEnd(event)) {
+ maybe_has_handler_for_current_sequence_ = false;
+ }
+}
+
+size_t PassthroughTouchEventQueue::SizeForTesting() const {
+ return outstanding_touches_.size();
+}
+
+bool PassthroughTouchEventQueue::IsTimeoutRunningForTesting() const {
+ return timeout_handler_ && timeout_handler_->IsTimeoutTimerRunning();
+}
+
+const TouchEventWithLatencyInfo&
+PassthroughTouchEventQueue::GetLatestEventForTesting() const {
+ return *outstanding_touches_.rbegin();
+}
+
+} // namespace content

Powered by Google App Engine
This is Rietveld 408576698