blob: 20443213c440bdb2df84676f0657a8fab374c4e0 [file] [log] [blame]
// 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