|  | // 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::Type::kTouchStart || | 
|  | event.GetType() == WebInputEvent::Type::kTouchMove) && | 
|  | event.dispatch_type == WebInputEvent::DispatchType::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::BindRepeating(&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, | 
|  | blink::mojom::InputEventResultState 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 == blink::mojom::InputEventResultState::kConsumed) | 
|  | 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( | 
|  | blink::mojom::InputEventResultState ack_result) const { | 
|  | DCHECK(HasTimeoutEvent()); | 
|  | if (ack_result != blink::mojom::InputEventResultState::kNoConsumerExists) | 
|  | 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 |