| // 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/auto_reset.h" |
| #include "base/trace_event/trace_event.h" |
| #include "content/browser/renderer_host/input/touchpad_tap_suppression_controller.h" |
| #include "content/browser/renderer_host/input/touchscreen_tap_suppression_controller.h" |
| #include "ui/events/blink/blink_features.h" |
| #include "ui/events/blink/web_input_event_traits.h" |
| |
| using blink::WebGestureEvent; |
| using blink::WebInputEvent; |
| |
| namespace content { |
| |
| GestureEventQueue::GestureEventWithLatencyInfoAndAckState:: |
| GestureEventWithLatencyInfoAndAckState( |
| const GestureEventWithLatencyInfo& event) |
| : GestureEventWithLatencyInfo(event) {} |
| |
| 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), |
| allow_multiple_inflight_events_( |
| base::FeatureList::IsEnabled(features::kVsyncAlignedInputEvents)), |
| debounce_interval_(config.debounce_interval), |
| fling_controller_(this, touchpad_client, config.fling_config) { |
| DCHECK(client); |
| DCHECK(touchpad_client); |
| } |
| |
| GestureEventQueue::~GestureEventQueue() { } |
| |
| void GestureEventQueue::QueueEvent( |
| const GestureEventWithLatencyInfo& gesture_event) { |
| TRACE_EVENT0("input", "GestureEventQueue::QueueEvent"); |
| if (!ShouldForwardForBounceReduction(gesture_event) || |
| fling_controller_.FilterGestureEvent(gesture_event)) { |
| return; |
| } |
| |
| QueueAndForwardIfNecessary(gesture_event); |
| } |
| |
| bool GestureEventQueue::ShouldDiscardFlingCancelEvent( |
| const GestureEventWithLatencyInfo& gesture_event) const { |
| if (coalesced_gesture_events_.empty() && fling_in_progress_) |
| return false; |
| GestureQueueWithAckState::const_reverse_iterator it = |
| coalesced_gesture_events_.rbegin(); |
| while (it != coalesced_gesture_events_.rend()) { |
| if (it->event.GetType() == WebInputEvent::kGestureFlingStart) |
| return false; |
| if (it->event.GetType() == WebInputEvent::kGestureFlingCancel) |
| return true; |
| it++; |
| } |
| return true; |
| } |
| |
| bool GestureEventQueue::ShouldForwardForBounceReduction( |
| const GestureEventWithLatencyInfo& gesture_event) { |
| if (debounce_interval_ <= base::TimeDelta()) |
| return true; |
| switch (gesture_event.event.GetType()) { |
| case WebInputEvent::kGestureScrollUpdate: |
| if (fling_in_progress_) |
| return false; |
| 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::kGestureFlingStart: |
| // GestureFlingStart shouldn't be debounced because it is a discrete event |
| // signalling the end of a scroll |
| debounce_deferring_timer_.Stop(); |
| GestureEventQueue::SendScrollEndingEventsNow(); |
| return true; |
| |
| case WebInputEvent::kGesturePinchBegin: |
| case WebInputEvent::kGesturePinchEnd: |
| case WebInputEvent::kGesturePinchUpdate: |
| // 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; |
| } |
| } |
| |
| void GestureEventQueue::QueueAndForwardIfNecessary( |
| const GestureEventWithLatencyInfo& gesture_event) { |
| if (allow_multiple_inflight_events_) { |
| // Event coalescing should be handled in compositor thread event queue. |
| if (gesture_event.event.GetType() == WebInputEvent::kGestureFlingCancel) |
| fling_in_progress_ = false; |
| else if (gesture_event.event.GetType() == WebInputEvent::kGestureFlingStart) |
| fling_in_progress_ = true; |
| coalesced_gesture_events_.push_back(gesture_event); |
| client_->SendGestureEventImmediately(gesture_event); |
| return; |
| } |
| |
| switch (gesture_event.event.GetType()) { |
| case WebInputEvent::kGestureFlingCancel: |
| fling_in_progress_ = false; |
| break; |
| case WebInputEvent::kGestureFlingStart: |
| fling_in_progress_ = true; |
| break; |
| case WebInputEvent::kGesturePinchUpdate: |
| case WebInputEvent::kGestureScrollUpdate: |
| QueueScrollOrPinchAndForwardIfNecessary(gesture_event); |
| return; |
| case WebInputEvent::kGestureScrollBegin: |
| if (OnScrollBegin(gesture_event)) |
| return; |
| default: |
| break; |
| } |
| |
| coalesced_gesture_events_.push_back(gesture_event); |
| if (coalesced_gesture_events_.size() == 1) |
| client_->SendGestureEventImmediately(gesture_event); |
| } |
| |
| bool GestureEventQueue::OnScrollBegin( |
| const GestureEventWithLatencyInfo& gesture_event) { |
| // If a synthetic scroll begin is encountered, it can cancel out a previous |
| // synthetic scroll end. This allows a later gesture scroll update to coalesce |
| // with the previous one. crbug.com/607340. |
| bool synthetic = gesture_event.event.data.scroll_begin.synthetic; |
| bool have_unsent_events = |
| EventsInFlightCount() < coalesced_gesture_events_.size(); |
| if (synthetic && have_unsent_events) { |
| GestureEventWithLatencyInfo* last_event = &coalesced_gesture_events_.back(); |
| if (last_event->event.GetType() == WebInputEvent::kGestureScrollEnd && |
| last_event->event.data.scroll_end.synthetic) { |
| coalesced_gesture_events_.pop_back(); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| void GestureEventQueue::ProcessGestureAck(InputEventAckSource ack_source, |
| 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; |
| } |
| |
| if (!allow_multiple_inflight_events_) { |
| LegacyProcessGestureAck(ack_source, ack_result, type, latency); |
| return; |
| } |
| |
| // ACKs could come back out of order. We want to cache them to restore the |
| // original order. |
| for (auto& outstanding_event : coalesced_gesture_events_) { |
| if (outstanding_event.ack_state() != INPUT_EVENT_ACK_STATE_UNKNOWN) |
| continue; |
| if (outstanding_event.event.GetType() == type) { |
| outstanding_event.latency.AddNewLatencyFrom(latency); |
| outstanding_event.set_ack_info(ack_source, ack_result); |
| break; |
| } |
| } |
| |
| AckCompletedEvents(); |
| } |
| |
| void GestureEventQueue::AckCompletedEvents() { |
| // Don't allow re-entrancy into this method otherwise |
| // the ordering of acks won't be preserved. |
| if (processing_acks_) |
| return; |
| base::AutoReset<bool> process_acks(&processing_acks_, true); |
| while (!coalesced_gesture_events_.empty()) { |
| auto iter = coalesced_gesture_events_.begin(); |
| if (iter->ack_state() == INPUT_EVENT_ACK_STATE_UNKNOWN) |
| break; |
| GestureEventWithLatencyInfoAndAckState event = *iter; |
| coalesced_gesture_events_.erase(iter); |
| AckGestureEventToClient(event, event.ack_source(), event.ack_state()); |
| } |
| } |
| |
| void GestureEventQueue::AckGestureEventToClient( |
| const GestureEventWithLatencyInfo& event_with_latency, |
| InputEventAckSource ack_source, |
| InputEventAckState ack_result) { |
| DCHECK(allow_multiple_inflight_events_); |
| |
| // 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_source, ack_result); |
| |
| const bool processed = (INPUT_EVENT_ACK_STATE_CONSUMED == ack_result); |
| if (event_with_latency.event.GetType() == |
| WebInputEvent::kGestureFlingCancel) { |
| fling_controller_.GestureFlingCancelAck( |
| event_with_latency.event.source_device, processed); |
| } |
| } |
| |
| void GestureEventQueue::LegacyProcessGestureAck( |
| InputEventAckSource ack_source, |
| InputEventAckState ack_result, |
| WebInputEvent::Type type, |
| const ui::LatencyInfo& latency) { |
| DCHECK(!allow_multiple_inflight_events_); |
| |
| // Events are forwarded one-by-one. |
| // 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. |
| size_t event_index = 0; |
| if (ignore_next_ack_ && coalesced_gesture_events_.size() > 1 && |
| coalesced_gesture_events_[0].event.GetType() != type && |
| coalesced_gesture_events_[1].event.GetType() == type) { |
| event_index = 1; |
| } |
| |
| GestureEventWithLatencyInfo event_with_latency = |
| coalesced_gesture_events_[event_index]; |
| DCHECK_EQ(event_with_latency.event.GetType(), 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_source, ack_result); |
| |
| const bool processed = (INPUT_EVENT_ACK_STATE_CONSUMED == ack_result); |
| if (type == WebInputEvent::kGestureFlingCancel) { |
| fling_controller_.GestureFlingCancelAck( |
| event_with_latency.event.source_device, 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(); |
| |
| // 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.GetType() == |
| WebInputEvent::kGestureScrollUpdate && |
| coalesced_gesture_events_.size() > 1 && |
| coalesced_gesture_events_[1].event.GetType() == |
| WebInputEvent::kGesturePinchUpdate) { |
| second_gesture_event = coalesced_gesture_events_[1]; |
| ignore_next_ack_ = true; |
| } |
| |
| client_->SendGestureEventImmediately(first_gesture_event); |
| if (second_gesture_event.event.GetType() != WebInputEvent::kUndefined) |
| client_->SendGestureEventImmediately(second_gesture_event); |
| } |
| |
| TouchpadTapSuppressionController* |
| GestureEventQueue::GetTouchpadTapSuppressionController() { |
| return fling_controller_.GetTouchpadTapSuppressionController(); |
| } |
| |
| void GestureEventQueue::FlingHasBeenHalted() { |
| fling_in_progress_ = false; |
| } |
| |
| void GestureEventQueue::ForwardGestureEvent( |
| const GestureEventWithLatencyInfo& gesture_event) { |
| QueueAndForwardIfNecessary(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 (!fling_controller_.FilterGestureEvent(*it)) { |
| QueueAndForwardIfNecessary(*it); |
| } |
| } |
| } |
| |
| void GestureEventQueue::QueueScrollOrPinchAndForwardIfNecessary( |
| const GestureEventWithLatencyInfo& gesture_event) { |
| DCHECK_GE(coalesced_gesture_events_.size(), EventsInFlightCount()); |
| const size_t unsent_events_count = |
| coalesced_gesture_events_.size() - EventsInFlightCount(); |
| if (!unsent_events_count) { |
| coalesced_gesture_events_.push_back(gesture_event); |
| if (coalesced_gesture_events_.size() == 1) { |
| client_->SendGestureEventImmediately(gesture_event); |
| } else if (coalesced_gesture_events_.size() == 2) { |
| DCHECK(!ignore_next_ack_); |
| // If there is an in-flight scroll, the new pinch can be forwarded |
| // immediately, avoiding a potential frame delay between the two |
| // (similarly for an in-flight pinch with a new scroll). |
| const GestureEventWithLatencyInfo& first_event = |
| coalesced_gesture_events_.front(); |
| if (gesture_event.event.GetType() != first_event.event.GetType() && |
| ui::IsCompatibleScrollorPinch(gesture_event.event, |
| first_event.event)) { |
| ignore_next_ack_ = true; |
| client_->SendGestureEventImmediately(gesture_event); |
| } |
| } |
| return; |
| } |
| |
| GestureEventWithLatencyInfo* last_event = &coalesced_gesture_events_.back(); |
| if (last_event->CanCoalesceWith(gesture_event)) { |
| last_event->CoalesceWith(gesture_event); |
| return; |
| } |
| |
| if (!ui::IsCompatibleScrollorPinch(gesture_event.event, last_event->event)) { |
| coalesced_gesture_events_.push_back(gesture_event); |
| return; |
| } |
| |
| // Extract the last event in queue. |
| blink::WebGestureEvent last_gesture_event = |
| coalesced_gesture_events_.back().event; |
| DCHECK_LE(coalesced_gesture_events_.back().latency.trace_id(), |
| gesture_event.latency.trace_id()); |
| ui::LatencyInfo oldest_latency = coalesced_gesture_events_.back().latency; |
| oldest_latency.set_coalesced(); |
| coalesced_gesture_events_.pop_back(); |
| |
| // Extract the second last event in queue. |
| ui::WebScopedInputEvent second_last_gesture_event = nullptr; |
| if (unsent_events_count > 1 && |
| ui::IsCompatibleScrollorPinch(gesture_event.event, |
| coalesced_gesture_events_.back().event)) { |
| second_last_gesture_event = |
| ui::WebInputEventTraits::Clone(coalesced_gesture_events_.back().event); |
| DCHECK_LE(coalesced_gesture_events_.back().latency.trace_id(), |
| oldest_latency.trace_id()); |
| oldest_latency = coalesced_gesture_events_.back().latency; |
| oldest_latency.set_coalesced(); |
| coalesced_gesture_events_.pop_back(); |
| } |
| |
| std::pair<blink::WebGestureEvent, blink::WebGestureEvent> coalesced_events = |
| ui::CoalesceScrollAndPinch( |
| second_last_gesture_event |
| ? &ui::ToWebGestureEvent(*second_last_gesture_event) |
| : nullptr, |
| last_gesture_event, gesture_event.event); |
| |
| GestureEventWithLatencyInfo scroll_event; |
| scroll_event.event = coalesced_events.first; |
| scroll_event.latency = oldest_latency; |
| |
| GestureEventWithLatencyInfo pinch_event; |
| pinch_event.event = coalesced_events.second; |
| pinch_event.latency = oldest_latency; |
| |
| coalesced_gesture_events_.push_back(scroll_event); |
| coalesced_gesture_events_.push_back(pinch_event); |
| } |
| |
| size_t GestureEventQueue::EventsInFlightCount() const { |
| if (allow_multiple_inflight_events_) { |
| // Currently unused, can be removed if compositor event queue was enabled by |
| // default. |
| NOTREACHED(); |
| return coalesced_gesture_events_.size(); |
| } |
| |
| if (coalesced_gesture_events_.empty()) |
| return 0; |
| |
| if (!ignore_next_ack_) |
| return 1; |
| |
| DCHECK_GT(coalesced_gesture_events_.size(), 1U); |
| return 2; |
| } |
| |
| } // namespace content |