blob: 66023b1e9cbdb980d194f2a7d20814d2154bea27 [file] [log] [blame]
// Copyright 2013 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/touch_event_queue.h"
#include <utility>
#include "base/auto_reset.h"
#include "base/macros.h"
#include "base/metrics/histogram_macros.h"
#include "base/stl_util.h"
#include "base/trace_event/trace_event.h"
#include "content/browser/renderer_host/input/timeout_monitor.h"
#include "content/common/input/web_touch_event_traits.h"
#include "ui/gfx/geometry/point_f.h"
using blink::WebInputEvent;
using blink::WebTouchEvent;
using blink::WebTouchPoint;
using ui::LatencyInfo;
namespace content {
namespace {
// Time interval at which touchmove events will be forwarded to the client while
// scrolling is active and possible.
const double kAsyncTouchMoveIntervalSec = .2;
// A sanity check on touches received to ensure that touch movement outside
// the platform slop region will cause scrolling, as indicated by the event's
// |causesScrollingIfUncanceled| bit.
const double kMaxConceivablePlatformSlopRegionLengthDipsSquared = 60. * 60.;
TouchEventWithLatencyInfo ObtainCancelEventForTouchEvent(
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);
return event;
}
bool ShouldTouchTriggerTimeout(const WebTouchEvent& event) {
return (event.type == WebInputEvent::TouchStart ||
event.type == WebInputEvent::TouchMove) &&
WebInputEventTraits::ShouldBlockEventStream(event) && event.cancelable;
}
// 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
// Cancels a touch sequence if a touchstart or touchmove ack response is
// sufficiently delayed.
class TouchEventQueue::TouchTimeoutHandler {
public:
TouchTimeoutHandler(TouchEventQueue* touch_queue,
base::TimeDelta desktop_timeout_delay,
base::TimeDelta mobile_timeout_delay)
: touch_queue_(touch_queue),
desktop_timeout_delay_(desktop_timeout_delay),
mobile_timeout_delay_(mobile_timeout_delay),
use_mobile_timeout_(false),
pending_ack_state_(PENDING_ACK_NONE),
timeout_monitor_(base::Bind(&TouchTimeoutHandler::OnTimeOut,
base::Unretained(this))),
enabled_(true),
enabled_for_current_sequence_(false),
sequence_awaiting_uma_update_(false),
sequence_using_mobile_timeout_(false) {
SetUseMobileTimeout(false);
}
~TouchTimeoutHandler() {
LogSequenceEndForUMAIfNecessary(false);
}
void StartIfNecessary(const TouchEventWithLatencyInfo& event) {
if (pending_ack_state_ != PENDING_ACK_NONE)
return;
if (!enabled_)
return;
const base::TimeDelta timeout_delay = GetTimeoutDelay();
if (timeout_delay.is_zero())
return;
if (!ShouldTouchTriggerTimeout(event.event))
return;
if (WebTouchEventTraits::IsTouchSequenceStart(event.event)) {
LogSequenceStartForUMA();
enabled_for_current_sequence_ = true;
}
if (!enabled_for_current_sequence_)
return;
timeout_event_ = event;
timeout_monitor_.Restart(timeout_delay);
}
bool ConfirmTouchEvent(InputEventAckState ack_result) {
switch (pending_ack_state_) {
case PENDING_ACK_NONE:
if (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED)
enabled_for_current_sequence_ = false;
timeout_monitor_.Stop();
return false;
case PENDING_ACK_ORIGINAL_EVENT:
if (AckedTimeoutEventRequiresCancel(ack_result)) {
SetPendingAckState(PENDING_ACK_CANCEL_EVENT);
TouchEventWithLatencyInfo cancel_event =
ObtainCancelEventForTouchEvent(timeout_event_);
touch_queue_->SendTouchEventImmediately(&cancel_event);
} else {
SetPendingAckState(PENDING_ACK_NONE);
touch_queue_->UpdateTouchConsumerStates(timeout_event_.event,
ack_result);
}
return true;
case PENDING_ACK_CANCEL_EVENT:
SetPendingAckState(PENDING_ACK_NONE);
return true;
}
return false;
}
bool FilterEvent(const WebTouchEvent& event) {
if (!HasTimeoutEvent())
return false;
if (WebTouchEventTraits::IsTouchSequenceStart(event)) {
// If a new sequence is observed while we're still waiting on the
// timed-out sequence response, also count the new sequence as timed-out.
LogSequenceStartForUMA();
LogSequenceEndForUMAIfNecessary(true);
}
return true;
}
void SetEnabled(bool enabled) {
if (enabled_ == enabled)
return;
enabled_ = enabled;
if (enabled_)
return;
enabled_for_current_sequence_ = false;
// Only reset the |timeout_handler_| if the timer is running and has not
// yet timed out. This ensures that an already timed out sequence is
// properly flushed by the handler.
if (IsTimeoutTimerRunning()) {
pending_ack_state_ = PENDING_ACK_NONE;
timeout_monitor_.Stop();
}
}
void SetUseMobileTimeout(bool use_mobile_timeout) {
use_mobile_timeout_ = use_mobile_timeout;
}
bool IsTimeoutTimerRunning() const { return timeout_monitor_.IsRunning(); }
bool IsEnabled() const {
return enabled_ && !GetTimeoutDelay().is_zero();
}
private:
enum PendingAckState {
PENDING_ACK_NONE,
PENDING_ACK_ORIGINAL_EVENT,
PENDING_ACK_CANCEL_EVENT,
};
void OnTimeOut() {
LogSequenceEndForUMAIfNecessary(true);
SetPendingAckState(PENDING_ACK_ORIGINAL_EVENT);
touch_queue_->FlushQueue();
}
// Skip a cancel event if the timed-out event had no consumer and was the
// initial event in the gesture.
bool AckedTimeoutEventRequiresCancel(InputEventAckState ack_result) const {
DCHECK(HasTimeoutEvent());
if (ack_result != INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS)
return true;
return !WebTouchEventTraits::IsTouchSequenceStart(timeout_event_.event);
}
void SetPendingAckState(PendingAckState new_pending_ack_state) {
DCHECK_NE(pending_ack_state_, new_pending_ack_state);
switch (new_pending_ack_state) {
case PENDING_ACK_ORIGINAL_EVENT:
DCHECK_EQ(pending_ack_state_, PENDING_ACK_NONE);
TRACE_EVENT_ASYNC_BEGIN0("input", "TouchEventTimeout", this);
break;
case PENDING_ACK_CANCEL_EVENT:
DCHECK_EQ(pending_ack_state_, PENDING_ACK_ORIGINAL_EVENT);
DCHECK(!timeout_monitor_.IsRunning());
DCHECK(touch_queue_->empty());
TRACE_EVENT_ASYNC_STEP_INTO0(
"input", "TouchEventTimeout", this, "CancelEvent");
break;
case PENDING_ACK_NONE:
DCHECK(!timeout_monitor_.IsRunning());
DCHECK(touch_queue_->empty());
TRACE_EVENT_ASYNC_END0("input", "TouchEventTimeout", this);
break;
}
pending_ack_state_ = new_pending_ack_state;
}
void LogSequenceStartForUMA() {
// Always flush any unlogged entries before starting a new one.
LogSequenceEndForUMAIfNecessary(false);
sequence_awaiting_uma_update_ = true;
sequence_using_mobile_timeout_ = use_mobile_timeout_;
}
void LogSequenceEndForUMAIfNecessary(bool timed_out) {
if (!sequence_awaiting_uma_update_)
return;
sequence_awaiting_uma_update_ = false;
if (sequence_using_mobile_timeout_) {
UMA_HISTOGRAM_BOOLEAN("Event.Touch.TimedOutOnMobileSite", timed_out);
} else {
UMA_HISTOGRAM_BOOLEAN("Event.Touch.TimedOutOnDesktopSite", timed_out);
}
}
base::TimeDelta GetTimeoutDelay() const {
return use_mobile_timeout_ ? mobile_timeout_delay_ : desktop_timeout_delay_;
}
bool HasTimeoutEvent() const {
return pending_ack_state_ != PENDING_ACK_NONE;
}
TouchEventQueue* touch_queue_;
// How long to wait on a touch ack before cancelling the touch sequence.
const base::TimeDelta desktop_timeout_delay_;
const base::TimeDelta mobile_timeout_delay_;
bool use_mobile_timeout_;
// The touch event source for which we expect the next ack.
PendingAckState pending_ack_state_;
// The event for which the ack timeout is triggered.
TouchEventWithLatencyInfo timeout_event_;
// Provides timeout-based callback behavior.
TimeoutMonitor timeout_monitor_;
bool enabled_;
bool enabled_for_current_sequence_;
// Bookkeeping to classify and log whether a touch sequence times out.
bool sequence_awaiting_uma_update_;
bool sequence_using_mobile_timeout_;
};
// Provides touchmove slop suppression for a touch sequence until a
// (unprevented) touch will trigger immediate scrolling.
class TouchEventQueue::TouchMoveSlopSuppressor {
public:
TouchMoveSlopSuppressor() : suppressing_touchmoves_(false) {}
bool FilterEvent(const WebTouchEvent& event) {
if (WebTouchEventTraits::IsTouchSequenceStart(event)) {
suppressing_touchmoves_ = true;
touch_start_location_ = gfx::PointF(event.touches[0].position);
}
if (event.type == WebInputEvent::TouchEnd ||
event.type == WebInputEvent::TouchCancel)
suppressing_touchmoves_ = false;
if (event.type != WebInputEvent::TouchMove)
return false;
if (suppressing_touchmoves_) {
if (event.touchesLength > 1) {
suppressing_touchmoves_ = false;
} else if (event.movedBeyondSlopRegion) {
suppressing_touchmoves_ = false;
} else {
// No sane slop region should be larger than 60 DIPs.
DCHECK_LT((gfx::PointF(event.touches[0].position) -
touch_start_location_).LengthSquared(),
kMaxConceivablePlatformSlopRegionLengthDipsSquared);
}
}
return suppressing_touchmoves_;
}
void ConfirmTouchEvent(InputEventAckState ack_result) {
if (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED)
suppressing_touchmoves_ = false;
}
bool suppressing_touchmoves() const { return suppressing_touchmoves_; }
private:
bool suppressing_touchmoves_;
// Sanity check that the upstream touch provider is properly reporting whether
// the touch sequence will cause scrolling.
gfx::PointF touch_start_location_;
DISALLOW_COPY_AND_ASSIGN(TouchMoveSlopSuppressor);
};
// This class represents a single coalesced touch event. However, it also keeps
// track of all the original touch-events that were coalesced into a single
// event. The coalesced event is forwarded to the renderer, while the original
// touch-events are sent to the Client (on ACK for the coalesced event) so that
// the Client receives the event with their original timestamp.
class CoalescedWebTouchEvent {
public:
CoalescedWebTouchEvent(const TouchEventWithLatencyInfo& event,
bool suppress_client_ack)
: coalesced_event_(event), suppress_client_ack_(suppress_client_ack) {
TRACE_EVENT_ASYNC_BEGIN0("input", "TouchEventQueue::QueueEvent", this);
}
~CoalescedWebTouchEvent() {
TRACE_EVENT_ASYNC_END0("input", "TouchEventQueue::QueueEvent", this);
}
// Coalesces the event with the existing event if possible. Returns whether
// the event was coalesced.
bool CoalesceEventIfPossible(
const TouchEventWithLatencyInfo& event_with_latency) {
if (suppress_client_ack_)
return false;
if (!coalesced_event_.CanCoalesceWith(event_with_latency))
return false;
// Addition of the first event to |uncoaleseced_events_to_ack_| is deferred
// until the first coalesced event, optimizing the (common) case where the
// event is not coalesced at all.
if (uncoaleseced_events_to_ack_.empty())
uncoaleseced_events_to_ack_.push_back(coalesced_event_);
TRACE_EVENT_INSTANT0(
"input", "TouchEventQueue::MoveCoalesced", TRACE_EVENT_SCOPE_THREAD);
coalesced_event_.CoalesceWith(event_with_latency);
uncoaleseced_events_to_ack_.push_back(event_with_latency);
DCHECK_GE(uncoaleseced_events_to_ack_.size(), 2U);
return true;
}
void DispatchAckToClient(InputEventAckState ack_result,
const ui::LatencyInfo* optional_latency_info,
TouchEventQueueClient* client) {
DCHECK(client);
if (suppress_client_ack_)
return;
if (uncoaleseced_events_to_ack_.empty()) {
if (optional_latency_info)
coalesced_event_.latency.AddNewLatencyFrom(*optional_latency_info);
client->OnTouchEventAck(coalesced_event_, ack_result);
return;
}
DCHECK_GE(uncoaleseced_events_to_ack_.size(), 2U);
for (WebTouchEventWithLatencyList::iterator
iter = uncoaleseced_events_to_ack_.begin(),
end = uncoaleseced_events_to_ack_.end();
iter != end;
++iter) {
if (optional_latency_info)
iter->latency.AddNewLatencyFrom(*optional_latency_info);
client->OnTouchEventAck(*iter, ack_result);
}
}
const TouchEventWithLatencyInfo& coalesced_event() const {
return coalesced_event_;
}
private:
// This is the event that is forwarded to the renderer.
TouchEventWithLatencyInfo coalesced_event_;
// This is the list of the original events that were coalesced, each requiring
// future ack dispatch to the client.
// Note that this will be empty if no coalescing has occurred.
typedef std::vector<TouchEventWithLatencyInfo> WebTouchEventWithLatencyList;
WebTouchEventWithLatencyList uncoaleseced_events_to_ack_;
bool suppress_client_ack_;
DISALLOW_COPY_AND_ASSIGN(CoalescedWebTouchEvent);
};
TouchEventQueue::Config::Config()
: desktop_touch_ack_timeout_delay(base::TimeDelta::FromMilliseconds(200)),
mobile_touch_ack_timeout_delay(base::TimeDelta::FromMilliseconds(1000)),
touch_ack_timeout_supported(false) {
}
TouchEventQueue::TouchEventQueue(TouchEventQueueClient* client,
const Config& config)
: client_(client),
dispatching_touch_ack_(false),
dispatching_touch_(false),
has_handlers_(true),
has_handler_for_current_sequence_(false),
drop_remaining_touches_in_sequence_(false),
touchmove_slop_suppressor_(new TouchMoveSlopSuppressor),
send_touch_events_async_(false),
last_sent_touch_timestamp_sec_(0) {
DCHECK(client);
if (config.touch_ack_timeout_supported) {
timeout_handler_.reset(
new TouchTimeoutHandler(this,
config.desktop_touch_ack_timeout_delay,
config.mobile_touch_ack_timeout_delay));
}
}
TouchEventQueue::~TouchEventQueue() {
if (!touch_queue_.empty())
STLDeleteElements(&touch_queue_);
}
void TouchEventQueue::QueueEvent(const TouchEventWithLatencyInfo& event) {
TRACE_EVENT0("input", "TouchEventQueue::QueueEvent");
// If the queueing of |event| was triggered by an ack dispatch, defer
// processing the event until the dispatch has finished.
if (touch_queue_.empty() && !dispatching_touch_ack_) {
// Optimization of the case without touch handlers. Removing this path
// yields identical results, but this avoids unnecessary allocations.
PreFilterResult filter_result = FilterBeforeForwarding(event.event);
if (filter_result != FORWARD_TO_RENDERER) {
client_->OnFilteringTouchEvent(event.event);
client_->OnTouchEventAck(event,
filter_result == ACK_WITH_NO_CONSUMER_EXISTS
? INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS
: INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
return;
}
// There is no touch event in the queue. Forward it to the renderer
// immediately.
touch_queue_.push_back(new CoalescedWebTouchEvent(event, false));
ForwardNextEventToRenderer();
return;
}
// If the last queued touch-event was a touch-move, and the current event is
// also a touch-move, then the events can be coalesced into a single event.
if (touch_queue_.size() > 1) {
CoalescedWebTouchEvent* last_event = touch_queue_.back();
if (last_event->CoalesceEventIfPossible(event))
return;
}
touch_queue_.push_back(new CoalescedWebTouchEvent(event, false));
}
void TouchEventQueue::ProcessTouchAck(InputEventAckState ack_result,
const LatencyInfo& latency_info,
const uint32_t unique_touch_event_id) {
TRACE_EVENT0("input", "TouchEventQueue::ProcessTouchAck");
// We receive an ack for async touchmove from render.
if (!ack_pending_async_touchmove_ids_.empty() &&
ack_pending_async_touchmove_ids_.front() == unique_touch_event_id) {
// Remove the first touchmove from the ack_pending_async_touchmove queue.
ack_pending_async_touchmove_ids_.pop_front();
// Send the next pending async touch move once we receive all acks back.
if (pending_async_touchmove_ && ack_pending_async_touchmove_ids_.empty()) {
DCHECK(touch_queue_.empty());
// Dispatch the next pending async touch move when time expires.
if (pending_async_touchmove_->event.timeStampSeconds >=
last_sent_touch_timestamp_sec_ + kAsyncTouchMoveIntervalSec) {
FlushPendingAsyncTouchmove();
}
}
return;
}
DCHECK(!dispatching_touch_ack_);
dispatching_touch_ = false;
if (timeout_handler_ && timeout_handler_->ConfirmTouchEvent(ack_result))
return;
touchmove_slop_suppressor_->ConfirmTouchEvent(ack_result);
if (touch_queue_.empty())
return;
DCHECK_EQ(touch_queue_.front()->coalesced_event().event.uniqueTouchEventId,
unique_touch_event_id);
PopTouchEventToClient(ack_result, latency_info);
TryForwardNextEventToRenderer();
}
void TouchEventQueue::TryForwardNextEventToRenderer() {
DCHECK(!dispatching_touch_ack_);
// If there are queued touch events, then try to forward them to the renderer
// immediately, or ACK the events back to the client if appropriate.
while (!touch_queue_.empty()) {
const WebTouchEvent& event = touch_queue_.front()->coalesced_event().event;
PreFilterResult filter_result = FilterBeforeForwarding(event);
if (filter_result != FORWARD_TO_RENDERER)
client_->OnFilteringTouchEvent(event);
switch (filter_result) {
case ACK_WITH_NO_CONSUMER_EXISTS:
PopTouchEventToClient(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
break;
case ACK_WITH_NOT_CONSUMED:
PopTouchEventToClient(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
break;
case FORWARD_TO_RENDERER:
ForwardNextEventToRenderer();
return;
}
}
}
void TouchEventQueue::ForwardNextEventToRenderer() {
TRACE_EVENT0("input", "TouchEventQueue::ForwardNextEventToRenderer");
DCHECK(!empty());
DCHECK(!dispatching_touch_);
TouchEventWithLatencyInfo touch = touch_queue_.front()->coalesced_event();
if (send_touch_events_async_ &&
touch.event.type == WebInputEvent::TouchMove) {
// Throttling touchmove's in a continuous touchmove stream while scrolling
// reduces the risk of jank. However, it's still important that the web
// application be sent touches at key points in the gesture stream,
// e.g., when the application slop region is exceeded or touchmove
// coalescing fails because of different modifiers.
bool send_touchmove_now = size() > 1;
send_touchmove_now |= pending_async_touchmove_ &&
!pending_async_touchmove_->CanCoalesceWith(touch);
send_touchmove_now |=
ack_pending_async_touchmove_ids_.empty() &&
(touch.event.timeStampSeconds >=
last_sent_touch_timestamp_sec_ + kAsyncTouchMoveIntervalSec);
if (!send_touchmove_now) {
if (!pending_async_touchmove_) {
pending_async_touchmove_.reset(new TouchEventWithLatencyInfo(touch));
} else {
DCHECK(pending_async_touchmove_->CanCoalesceWith(touch));
pending_async_touchmove_->CoalesceWith(touch);
}
DCHECK_EQ(1U, size());
PopTouchEventToClient(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
// It's possible (though unlikely) that ack'ing the current touch will
// trigger the queueing of another touch event (e.g., a touchcancel). As
// forwarding of the queued event will be deferred while the ack is being
// dispatched (see |OnTouchEvent()|), try forwarding it now.
TryForwardNextEventToRenderer();
return;
}
}
last_sent_touch_timestamp_sec_ = touch.event.timeStampSeconds;
// Flush any pending async touch move. If it can be combined with the current
// (touchmove) event, great, otherwise send it immediately but separately. Its
// ack will trigger forwarding of the original |touch| event.
if (pending_async_touchmove_) {
if (pending_async_touchmove_->CanCoalesceWith(touch)) {
pending_async_touchmove_->CoalesceWith(touch);
pending_async_touchmove_->event.cancelable = !send_touch_events_async_;
touch = *pending_async_touchmove_;
pending_async_touchmove_.reset();
} else {
FlushPendingAsyncTouchmove();
return;
}
}
// 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.cancelable = false;
SendTouchEventImmediately(&touch);
}
void TouchEventQueue::FlushPendingAsyncTouchmove() {
DCHECK(!dispatching_touch_);
scoped_ptr<TouchEventWithLatencyInfo> touch =
std::move(pending_async_touchmove_);
touch->event.cancelable = false;
touch_queue_.push_front(new CoalescedWebTouchEvent(*touch, true));
SendTouchEventImmediately(touch.get());
}
void TouchEventQueue::OnGestureScrollEvent(
const GestureEventWithLatencyInfo& gesture_event) {
if (gesture_event.event.type == blink::WebInputEvent::GestureScrollBegin) {
if (has_handler_for_current_sequence_ &&
!drop_remaining_touches_in_sequence_) {
DCHECK(!touchmove_slop_suppressor_->suppressing_touchmoves())
<< "A touch handler should be offered a touchmove before scrolling.";
}
pending_async_touchmove_.reset();
return;
}
if (gesture_event.event.type == blink::WebInputEvent::GestureScrollUpdate &&
gesture_event.event.resendingPluginId == -1) {
send_touch_events_async_ = true;
}
}
void TouchEventQueue::OnGestureEventAck(
const GestureEventWithLatencyInfo& event,
InputEventAckState ack_result) {
// Throttle sending touchmove events as long as the scroll events are handled.
// Note that there's no guarantee that this ACK is for the most recent
// gesture event (or even part of the current sequence). Worst case, the
// delay in updating the absorption state will result in minor UI glitches.
// A valid |pending_async_touchmove_| will be flushed when the next event is
// forwarded. Scroll updates that are being resent from a GuestView are
// ignored.
if (event.event.type == blink::WebInputEvent::GestureScrollUpdate &&
event.event.resendingPluginId == -1) {
send_touch_events_async_ = (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED);
}
}
void TouchEventQueue::OnHasTouchEventHandlers(bool has_handlers) {
DCHECK(!dispatching_touch_ack_);
DCHECK(!dispatching_touch_);
has_handlers_ = has_handlers;
}
bool TouchEventQueue::IsPendingAckTouchStart() const {
DCHECK(!dispatching_touch_ack_);
if (touch_queue_.empty())
return false;
const blink::WebTouchEvent& event =
touch_queue_.front()->coalesced_event().event;
return (event.type == WebInputEvent::TouchStart);
}
void TouchEventQueue::SetAckTimeoutEnabled(bool enabled) {
if (timeout_handler_)
timeout_handler_->SetEnabled(enabled);
}
void TouchEventQueue::SetIsMobileOptimizedSite(bool mobile_optimized_site) {
if (timeout_handler_)
timeout_handler_->SetUseMobileTimeout(mobile_optimized_site);
}
bool TouchEventQueue::IsAckTimeoutEnabled() const {
return timeout_handler_ && timeout_handler_->IsEnabled();
}
bool TouchEventQueue::HasPendingAsyncTouchMoveForTesting() const {
return !!pending_async_touchmove_;
}
bool TouchEventQueue::IsTimeoutRunningForTesting() const {
return timeout_handler_ && timeout_handler_->IsTimeoutTimerRunning();
}
const TouchEventWithLatencyInfo&
TouchEventQueue::GetLatestEventForTesting() const {
return touch_queue_.back()->coalesced_event();
}
void TouchEventQueue::FlushQueue() {
DCHECK(!dispatching_touch_ack_);
DCHECK(!dispatching_touch_);
pending_async_touchmove_.reset();
drop_remaining_touches_in_sequence_ = true;
while (!touch_queue_.empty())
PopTouchEventToClient(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
}
void TouchEventQueue::PopTouchEventToClient(InputEventAckState ack_result) {
AckTouchEventToClient(ack_result, PopTouchEvent(), nullptr);
}
void TouchEventQueue::PopTouchEventToClient(
InputEventAckState ack_result,
const LatencyInfo& renderer_latency_info) {
AckTouchEventToClient(ack_result, PopTouchEvent(), &renderer_latency_info);
}
void TouchEventQueue::AckTouchEventToClient(
InputEventAckState ack_result,
scoped_ptr<CoalescedWebTouchEvent> acked_event,
const ui::LatencyInfo* optional_latency_info) {
DCHECK(acked_event);
DCHECK(!dispatching_touch_ack_);
UpdateTouchConsumerStates(acked_event->coalesced_event().event, ack_result);
// Note that acking the touch-event may result in multiple gestures being sent
// to the renderer, or touch-events being queued.
base::AutoReset<bool> dispatching_touch_ack(&dispatching_touch_ack_, true);
acked_event->DispatchAckToClient(ack_result, optional_latency_info, client_);
}
scoped_ptr<CoalescedWebTouchEvent> TouchEventQueue::PopTouchEvent() {
DCHECK(!touch_queue_.empty());
scoped_ptr<CoalescedWebTouchEvent> event(touch_queue_.front());
touch_queue_.pop_front();
return event;
}
void TouchEventQueue::SendTouchEventImmediately(
TouchEventWithLatencyInfo* touch) {
// 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_);
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 (last_sent_touchevent_)
*last_sent_touchevent_ = touch->event;
else
last_sent_touchevent_.reset(new WebTouchEvent(touch->event));
base::AutoReset<bool> dispatching_touch(&dispatching_touch_, true);
client_->SendTouchEventImmediately(*touch);
// A synchronous ack will reset |dispatching_touch_|, in which case the touch
// timeout should not be started and the count also should not be increased.
if (dispatching_touch_) {
if (touch->event.type == WebInputEvent::TouchMove &&
!touch->event.cancelable) {
// When we send out a uncancelable touch move, we increase the count and
// we do not process input event ack any more, we will just ack to client
// and wait for the ack from render. Also we will remove it from the front
// of the queue.
ack_pending_async_touchmove_ids_.push_back(
touch->event.uniqueTouchEventId);
dispatching_touch_ = false;
PopTouchEventToClient(INPUT_EVENT_ACK_STATE_IGNORED);
TryForwardNextEventToRenderer();
return;
}
if (timeout_handler_)
timeout_handler_->StartIfNecessary(*touch);
}
}
TouchEventQueue::PreFilterResult
TouchEventQueue::FilterBeforeForwarding(const WebTouchEvent& event) {
if (WebTouchEventTraits::IsTouchSequenceStart(event)) {
has_handler_for_current_sequence_ = false;
send_touch_events_async_ = false;
pending_async_touchmove_.reset();
last_sent_touchevent_.reset();
touch_sequence_start_position_ = gfx::PointF(event.touches[0].position);
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 (touchmove_slop_suppressor_->FilterEvent(event))
return ACK_WITH_NOT_CONSUMED;
if (drop_remaining_touches_in_sequence_ &&
event.type != WebInputEvent::TouchCancel) {
return ACK_WITH_NO_CONSUMER_EXISTS;
}
if (event.type == WebInputEvent::TouchStart) {
return (has_handlers_ || has_handler_for_current_sequence_)
? FORWARD_TO_RENDERER
: ACK_WITH_NO_CONSUMER_EXISTS;
}
if (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 TouchEventQueue::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;
has_handler_for_current_sequence_ |=
ack_result != INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS;
} else if (WebTouchEventTraits::IsTouchSequenceEnd(event)) {
has_handler_for_current_sequence_ = false;
}
}
} // namespace content