blob: 7cfc0e9f4fd78169d28827c72236571bcaf8b280 [file] [log] [blame]
// 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/touch_timeout_handler.h"
#include <utility>
#include "base/auto_reset.h"
#include "base/bind.h"
#include "base/macros.h"
#include "base/metrics/histogram_macros.h"
#include "base/trace_event/trace_event.h"
#include "content/browser/renderer_host/input/passthrough_touch_event_queue.h"
#include "content/common/input/web_touch_event_traits.h"
#include "ui/events/base_event_utils.h"
#include "ui/gfx/geometry/point_f.h"
using blink::WebInputEvent;
using blink::WebTouchEvent;
using blink::WebTouchPoint;
using ui::LatencyInfo;
namespace content {
namespace {
bool ShouldTouchTriggerTimeout(const WebTouchEvent& event) {
return (event.GetType() == WebInputEvent::kTouchStart ||
event.GetType() == WebInputEvent::kTouchMove) &&
event.dispatch_type == WebInputEvent::kBlocking;
}
} // namespace
TouchTimeoutHandler::TouchTimeoutHandler(
PassthroughTouchEventQueue* 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::~TouchTimeoutHandler() {
LogSequenceEndForUMAIfNecessary(false);
}
void TouchTimeoutHandler::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 TouchTimeoutHandler::ConfirmTouchEvent(uint32_t unique_touch_event_id,
InputEventAckState ack_result,
bool should_stop_timeout_monitor) {
if (timeout_event_.event.unique_touch_event_id != unique_touch_event_id)
return false;
switch (pending_ack_state_) {
case PENDING_ACK_NONE:
if (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED)
enabled_for_current_sequence_ = false;
if (should_stop_timeout_monitor)
timeout_monitor_.Stop();
return false;
case PENDING_ACK_ORIGINAL_EVENT:
if (AckedTimeoutEventRequiresCancel(ack_result)) {
SetPendingAckState(PENDING_ACK_CANCEL_EVENT);
touch_queue_->SendTouchCancelEventForTouchEvent(timeout_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 TouchTimeoutHandler::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 TouchTimeoutHandler::StopTimeoutMonitor() {
timeout_monitor_.Stop();
}
void TouchTimeoutHandler::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 TouchTimeoutHandler::SetUseMobileTimeout(bool use_mobile_timeout) {
use_mobile_timeout_ = use_mobile_timeout;
}
void TouchTimeoutHandler::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 TouchTimeoutHandler::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 TouchTimeoutHandler::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 TouchTimeoutHandler::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 TouchTimeoutHandler::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 TouchTimeoutHandler::GetTimeoutDelay() const {
return use_mobile_timeout_ ? mobile_timeout_delay_ : desktop_timeout_delay_;
}
bool TouchTimeoutHandler::HasTimeoutEvent() const {
return pending_ack_state_ != PENDING_ACK_NONE;
}
} // namespace content