This source file includes following definitions.
- ObtainCancelEventForTouchEvent
- ShouldTouchTypeTriggerTimeout
- timeout_monitor_
- TouchTimeoutHandler
- Start
- ConfirmTouchEvent
- FilterEvent
- IsTimeoutTimerRunning
- Reset
- set_timeout_delay
- OnTimeOut
- AckedTimeoutEventRequiresCancel
- SetPendingAckState
- HasTimeoutEvent
- suppressing_touch_moves_
- FilterEvent
- ConfirmTouchEvent
- ignore_ack_
- CoalesceEventIfPossible
- coalesced_event
- begin
- end
- ignore_ack
- touch_scrolling_mode_
- QueueEvent
- ProcessTouchAck
- TryForwardNextEventToRenderer
- ForwardToRenderer
- OnGestureScrollEvent
- OnGestureEventAck
- OnHasTouchEventHandlers
- IsPendingAckTouchStart
- SetAckTimeoutEnabled
- IsTimeoutRunningForTesting
- GetLatestEventForTesting
- FlushQueue
- PopTouchEventToClient
- FilterBeforeForwarding
- UpdateTouchAckStates
#include "content/browser/renderer_host/input/touch_event_queue.h"
#include "base/auto_reset.h"
#include "base/command_line.h"
#include "base/debug/trace_event.h"
#include "base/stl_util.h"
#include "content/browser/renderer_host/input/timeout_monitor.h"
#include "content/browser/renderer_host/input/web_touch_event_traits.h"
#include "content/public/common/content_switches.h"
#include "ui/gfx/geometry/point_f.h"
using blink::WebInputEvent;
using blink::WebTouchEvent;
using blink::WebTouchPoint;
using ui::LatencyInfo;
namespace content {
namespace {
typedef std::vector<TouchEventWithLatencyInfo> WebTouchEventWithLatencyList;
TouchEventWithLatencyInfo ObtainCancelEventForTouchEvent(
const TouchEventWithLatencyInfo& event_to_cancel) {
TouchEventWithLatencyInfo event = event_to_cancel;
event.event.type = WebInputEvent::TouchCancel;
for (size_t i = 0; i < event.event.touchesLength; i++)
event.event.touches[i].state = WebTouchPoint::StateCancelled;
return event;
}
bool ShouldTouchTypeTriggerTimeout(WebInputEvent::Type type) {
return type == WebInputEvent::TouchStart ||
type == WebInputEvent::TouchMove;
}
}
class TouchEventQueue::TouchTimeoutHandler {
public:
TouchTimeoutHandler(TouchEventQueue* touch_queue,
base::TimeDelta timeout_delay)
: touch_queue_(touch_queue),
timeout_delay_(timeout_delay),
pending_ack_state_(PENDING_ACK_NONE),
timeout_monitor_(base::Bind(&TouchTimeoutHandler::OnTimeOut,
base::Unretained(this))) {}
~TouchTimeoutHandler() {}
void Start(const TouchEventWithLatencyInfo& event) {
DCHECK_EQ(pending_ack_state_, PENDING_ACK_NONE);
DCHECK(ShouldTouchTypeTriggerTimeout(event.event.type));
timeout_event_ = event;
timeout_monitor_.Restart(timeout_delay_);
}
bool ConfirmTouchEvent(InputEventAckState ack_result) {
switch (pending_ack_state_) {
case PENDING_ACK_NONE:
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_->client_->SendTouchEventImmediately(cancel_event);
} else {
SetPendingAckState(PENDING_ACK_NONE);
touch_queue_->UpdateTouchAckStates(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) {
return HasTimeoutEvent();
}
bool IsTimeoutTimerRunning() const {
return timeout_monitor_.IsRunning();
}
void Reset() {
pending_ack_state_ = PENDING_ACK_NONE;
timeout_monitor_.Stop();
}
void set_timeout_delay(base::TimeDelta timeout_delay) {
timeout_delay_ = timeout_delay;
}
private:
enum PendingAckState {
PENDING_ACK_NONE,
PENDING_ACK_ORIGINAL_EVENT,
PENDING_ACK_CANCEL_EVENT,
};
void OnTimeOut() {
SetPendingAckState(PENDING_ACK_ORIGINAL_EVENT);
touch_queue_->FlushQueue();
}
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;
}
bool HasTimeoutEvent() const {
return pending_ack_state_ != PENDING_ACK_NONE;
}
TouchEventQueue* touch_queue_;
base::TimeDelta timeout_delay_;
PendingAckState pending_ack_state_;
TouchEventWithLatencyInfo timeout_event_;
TimeoutMonitor timeout_monitor_;
};
class TouchEventQueue::TouchMoveSlopSuppressor {
public:
TouchMoveSlopSuppressor(double slop_suppression_length_dips)
: slop_suppression_length_dips_squared_(slop_suppression_length_dips *
slop_suppression_length_dips),
suppressing_touch_moves_(false) {}
bool FilterEvent(const WebTouchEvent& event) {
if (WebTouchEventTraits::IsTouchSequenceStart(event)) {
touch_sequence_start_position_ =
gfx::PointF(event.touches[0].position);
suppressing_touch_moves_ = slop_suppression_length_dips_squared_ != 0;
}
if (event.type != WebInputEvent::TouchMove)
return false;
if (suppressing_touch_moves_) {
if (event.touchesLength > 1) {
suppressing_touch_moves_ = false;
} else if (event.touchesLength == 1) {
gfx::PointF position(event.touches[0].position);
if ((position - touch_sequence_start_position_).LengthSquared() >
slop_suppression_length_dips_squared_)
suppressing_touch_moves_ = false;
}
}
return suppressing_touch_moves_;
}
void ConfirmTouchEvent(InputEventAckState ack_result) {
if (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED)
suppressing_touch_moves_ = false;
}
private:
double slop_suppression_length_dips_squared_;
gfx::PointF touch_sequence_start_position_;
bool suppressing_touch_moves_;
DISALLOW_COPY_AND_ASSIGN(TouchMoveSlopSuppressor);
};
class CoalescedWebTouchEvent {
public:
CoalescedWebTouchEvent(const TouchEventWithLatencyInfo& event,
bool ignore_ack)
: coalesced_event_(event),
ignore_ack_(ignore_ack) {
events_.push_back(event);
TRACE_EVENT_ASYNC_BEGIN0(
"input", "TouchEventQueue::QueueEvent", this);
}
~CoalescedWebTouchEvent() {
TRACE_EVENT_ASYNC_END0(
"input", "TouchEventQueue::QueueEvent", this);
}
bool CoalesceEventIfPossible(
const TouchEventWithLatencyInfo& event_with_latency) {
if (ignore_ack_)
return false;
if (!coalesced_event_.CanCoalesceWith(event_with_latency))
return false;
TRACE_EVENT_INSTANT0(
"input", "TouchEventQueue::MoveCoalesced", TRACE_EVENT_SCOPE_THREAD);
coalesced_event_.CoalesceWith(event_with_latency);
events_.push_back(event_with_latency);
return true;
}
const TouchEventWithLatencyInfo& coalesced_event() const {
return coalesced_event_;
}
WebTouchEventWithLatencyList::iterator begin() {
return events_.begin();
}
WebTouchEventWithLatencyList::iterator end() {
return events_.end();
}
size_t size() const { return events_.size(); }
bool ignore_ack() const { return ignore_ack_; }
private:
TouchEventWithLatencyInfo coalesced_event_;
WebTouchEventWithLatencyList events_;
bool ignore_ack_;
DISALLOW_COPY_AND_ASSIGN(CoalescedWebTouchEvent);
};
TouchEventQueue::TouchEventQueue(TouchEventQueueClient* client,
TouchScrollingMode mode,
double touchmove_suppression_length_dips)
: client_(client),
dispatching_touch_ack_(NULL),
dispatching_touch_(false),
touch_filtering_state_(TOUCH_FILTERING_STATE_DEFAULT),
ack_timeout_enabled_(false),
touchmove_slop_suppressor_(
new TouchMoveSlopSuppressor(touchmove_suppression_length_dips)),
absorbing_touch_moves_(false),
touch_scrolling_mode_(mode) {
DCHECK(client);
}
TouchEventQueue::~TouchEventQueue() {
if (!touch_queue_.empty())
STLDeleteElements(&touch_queue_);
}
void TouchEventQueue::QueueEvent(const TouchEventWithLatencyInfo& event) {
TRACE_EVENT0("input", "TouchEventQueue::QueueEvent");
if (touch_queue_.empty() && !dispatching_touch_ack_) {
if (touch_filtering_state_ == DROP_ALL_TOUCHES ||
(touch_filtering_state_ == DROP_TOUCHES_IN_SEQUENCE &&
!WebTouchEventTraits::IsTouchSequenceStart(event.event))) {
client_->OnTouchEventAck(event, INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
return;
}
touch_queue_.push_back(new CoalescedWebTouchEvent(event, false));
TryForwardNextEventToRenderer();
return;
}
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) {
TRACE_EVENT0("input", "TouchEventQueue::ProcessTouchAck");
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;
const WebTouchEvent& acked_event =
touch_queue_.front()->coalesced_event().event;
if (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED &&
touch_filtering_state_ == FORWARD_TOUCHES_UNTIL_TIMEOUT) {
touch_filtering_state_ = FORWARD_ALL_TOUCHES;
}
if (ack_result == INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS &&
touch_filtering_state_ != DROP_ALL_TOUCHES &&
WebTouchEventTraits::IsTouchSequenceStart(acked_event)) {
touch_filtering_state_ = DROP_TOUCHES_IN_SEQUENCE;
}
UpdateTouchAckStates(acked_event, ack_result);
PopTouchEventToClient(ack_result, latency_info);
TryForwardNextEventToRenderer();
}
void TouchEventQueue::TryForwardNextEventToRenderer() {
DCHECK(!dispatching_touch_ack_);
while (!touch_queue_.empty()) {
const TouchEventWithLatencyInfo& touch =
touch_queue_.front()->coalesced_event();
PreFilterResult result = FilterBeforeForwarding(touch.event);
switch (result) {
case ACK_WITH_NO_CONSUMER_EXISTS:
PopTouchEventToClient(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS,
LatencyInfo());
break;
case ACK_WITH_NOT_CONSUMED:
PopTouchEventToClient(INPUT_EVENT_ACK_STATE_NOT_CONSUMED,
LatencyInfo());
break;
case FORWARD_TO_RENDERER:
ForwardToRenderer(touch);
return;
}
}
}
void TouchEventQueue::ForwardToRenderer(
const TouchEventWithLatencyInfo& touch) {
TRACE_EVENT0("input", "TouchEventQueue::ForwardToRenderer");
DCHECK(!dispatching_touch_);
DCHECK_NE(touch_filtering_state_, DROP_ALL_TOUCHES);
if (WebTouchEventTraits::IsTouchSequenceStart(touch.event)) {
touch_filtering_state_ =
ack_timeout_enabled_ ? FORWARD_TOUCHES_UNTIL_TIMEOUT
: FORWARD_ALL_TOUCHES;
touch_ack_states_.clear();
absorbing_touch_moves_ = false;
}
base::AutoReset<bool> dispatching_touch(&dispatching_touch_, true);
client_->SendTouchEventImmediately(touch);
if (dispatching_touch_ &&
touch_filtering_state_ == FORWARD_TOUCHES_UNTIL_TIMEOUT &&
ShouldTouchTypeTriggerTimeout(touch.event.type)) {
DCHECK(timeout_handler_);
timeout_handler_->Start(touch);
}
}
void TouchEventQueue::OnGestureScrollEvent(
const GestureEventWithLatencyInfo& gesture_event) {
if (gesture_event.event.type != blink::WebInputEvent::GestureScrollBegin)
return;
if (touch_scrolling_mode_ == TOUCH_SCROLLING_MODE_ABSORB_TOUCHMOVE)
absorbing_touch_moves_ = true;
if (touch_scrolling_mode_ != TOUCH_SCROLLING_MODE_TOUCHCANCEL)
return;
if (!dispatching_touch_ack_)
return;
if (touch_filtering_state_ == DROP_TOUCHES_IN_SEQUENCE)
return;
touch_filtering_state_ = DROP_TOUCHES_IN_SEQUENCE;
touch_queue_.push_front(new CoalescedWebTouchEvent(
ObtainCancelEventForTouchEvent(
dispatching_touch_ack_->coalesced_event()), true));
}
void TouchEventQueue::OnGestureEventAck(
const GestureEventWithLatencyInfo& event,
InputEventAckState ack_result) {
if (touch_scrolling_mode_ != TOUCH_SCROLLING_MODE_ABSORB_TOUCHMOVE)
return;
if (event.event.type != blink::WebInputEvent::GestureScrollUpdate)
return;
absorbing_touch_moves_ = (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED);
}
void TouchEventQueue::OnHasTouchEventHandlers(bool has_handlers) {
DCHECK(!dispatching_touch_ack_);
DCHECK(!dispatching_touch_);
if (has_handlers) {
if (touch_filtering_state_ == DROP_ALL_TOUCHES) {
DCHECK(touch_queue_.empty());
touch_filtering_state_ = DROP_TOUCHES_IN_SEQUENCE;
}
} else {
touch_filtering_state_ = DROP_ALL_TOUCHES;
if (timeout_handler_)
timeout_handler_->Reset();
if (!touch_queue_.empty())
ProcessTouchAck(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS, LatencyInfo());
DCHECK(touch_queue_.empty());
}
}
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,
base::TimeDelta ack_timeout_delay) {
if (!enabled) {
ack_timeout_enabled_ = false;
if (touch_filtering_state_ == FORWARD_TOUCHES_UNTIL_TIMEOUT)
touch_filtering_state_ = FORWARD_ALL_TOUCHES;
if (timeout_handler_ && timeout_handler_->IsTimeoutTimerRunning())
timeout_handler_->Reset();
return;
}
ack_timeout_enabled_ = true;
if (!timeout_handler_)
timeout_handler_.reset(new TouchTimeoutHandler(this, ack_timeout_delay));
else
timeout_handler_->set_timeout_delay(ack_timeout_delay);
}
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_);
if (touch_filtering_state_ != DROP_ALL_TOUCHES)
touch_filtering_state_ = DROP_TOUCHES_IN_SEQUENCE;
while (!touch_queue_.empty()) {
PopTouchEventToClient(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS,
LatencyInfo());
}
}
void TouchEventQueue::PopTouchEventToClient(
InputEventAckState ack_result,
const LatencyInfo& renderer_latency_info) {
DCHECK(!dispatching_touch_ack_);
if (touch_queue_.empty())
return;
scoped_ptr<CoalescedWebTouchEvent> acked_event(touch_queue_.front());
touch_queue_.pop_front();
if (acked_event->ignore_ack())
return;
base::AutoReset<CoalescedWebTouchEvent*>
dispatching_touch_ack(&dispatching_touch_ack_, acked_event.get());
for (WebTouchEventWithLatencyList::iterator iter = acked_event->begin(),
end = acked_event->end();
iter != end; ++iter) {
iter->latency.AddNewLatencyFrom(renderer_latency_info);
client_->OnTouchEventAck((*iter), ack_result);
}
}
TouchEventQueue::PreFilterResult
TouchEventQueue::FilterBeforeForwarding(const WebTouchEvent& event) {
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 (touch_filtering_state_ == DROP_ALL_TOUCHES)
return ACK_WITH_NO_CONSUMER_EXISTS;
if (touch_filtering_state_ == DROP_TOUCHES_IN_SEQUENCE &&
event.type != WebInputEvent::TouchCancel) {
if (WebTouchEventTraits::IsTouchSequenceStart(event))
return FORWARD_TO_RENDERER;
return ACK_WITH_NOT_CONSUMED;
}
if (absorbing_touch_moves_ && event.type == WebInputEvent::TouchMove)
return ACK_WITH_NOT_CONSUMED;
if (event.type == WebInputEvent::TouchStart)
return FORWARD_TO_RENDERER;
for (unsigned int i = 0; i < event.touchesLength; ++i) {
const WebTouchPoint& point = event.touches[i];
if (point.state == WebTouchPoint::StateStationary)
continue;
if (touch_ack_states_.count(point.id) > 0) {
if (touch_ack_states_.find(point.id)->second !=
INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS)
return FORWARD_TO_RENDERER;
} else {
return FORWARD_TO_RENDERER;
}
}
return ACK_WITH_NO_CONSUMER_EXISTS;
}
void TouchEventQueue::UpdateTouchAckStates(const WebTouchEvent& event,
InputEventAckState ack_result) {
if (event.type == WebInputEvent::TouchEnd ||
event.type == WebInputEvent::TouchCancel) {
for (unsigned i = 0; i < event.touchesLength; ++i) {
const WebTouchPoint& point = event.touches[i];
if (point.state == WebTouchPoint::StateReleased ||
point.state == WebTouchPoint::StateCancelled)
touch_ack_states_.erase(point.id);
}
} else if (event.type == WebInputEvent::TouchStart) {
for (unsigned i = 0; i < event.touchesLength; ++i) {
const WebTouchPoint& point = event.touches[i];
if (point.state == WebTouchPoint::StatePressed)
touch_ack_states_[point.id] = ack_result;
}
}
}
}