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..084023490ec87a56f96e1ee7590ef6ecafc8b72d |
--- /dev/null |
+++ b/content/browser/renderer_host/input/passthrough_touch_event_queue.cc |
@@ -0,0 +1,376 @@ |
+// 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" |
+#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" |
tdresser
2017/02/23 15:10:45
How thoroughly has this changed from legacy_touch_
dtapuska
2017/02/23 16:52:12
Will comment on the interesting bits.
|
+ |
+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, |
+ 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; |
+} |
+ |
+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); |
dtapuska
2017/02/23 16:52:12
This is interesting code here. The Filtered touch
|
+ client_->OnTouchEventAck(event, |
+ filter_result == ACK_WITH_NO_CONSUMER_EXISTS |
+ ? INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS |
+ : INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
+ 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()) { |
+ NOTREACHED(); |
+ 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; |
+ |
+ return (outstanding_touches_.begin()->event.type() == |
+ WebInputEvent::TouchStart); |
+} |
+ |
+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; |
dtapuska
2017/02/23 16:52:11
This is interesting
|
+ 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() { |
dtapuska
2017/02/23 16:52:11
Interesting
|
+ 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; |
dtapuska
2017/02/23 16:52:11
maybe_ has been added here and inverted.
|
+ 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 |
dtapuska
2017/02/23 16:52:12
This is an interesting block.
|
+ // 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 |