| OLD | NEW |
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. | 1 // Copyright 2013 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "content/browser/renderer_host/input/legacy_touch_event_queue.h" | 5 #include "content/browser/renderer_host/input/legacy_touch_event_queue.h" |
| 6 | 6 |
| 7 #include <utility> | 7 #include <utility> |
| 8 | 8 |
| 9 #include "base/auto_reset.h" | 9 #include "base/auto_reset.h" |
| 10 #include "base/macros.h" | 10 #include "base/macros.h" |
| 11 #include "base/memory/ptr_util.h" | 11 #include "base/memory/ptr_util.h" |
| 12 #include "base/metrics/histogram_macros.h" | 12 #include "base/metrics/histogram_macros.h" |
| 13 #include "base/trace_event/trace_event.h" | 13 #include "base/trace_event/trace_event.h" |
| 14 #include "content/browser/renderer_host/input/timeout_monitor.h" | 14 #include "content/browser/renderer_host/input/touch_timeout_handler.h" |
| 15 #include "content/common/input/web_touch_event_traits.h" | 15 #include "content/common/input/web_touch_event_traits.h" |
| 16 #include "ui/events/base_event_utils.h" | 16 #include "ui/events/base_event_utils.h" |
| 17 #include "ui/gfx/geometry/point_f.h" | 17 #include "ui/gfx/geometry/point_f.h" |
| 18 | 18 |
| 19 using blink::WebInputEvent; | 19 using blink::WebInputEvent; |
| 20 using blink::WebTouchEvent; | 20 using blink::WebTouchEvent; |
| 21 using blink::WebTouchPoint; | 21 using blink::WebTouchPoint; |
| 22 using ui::LatencyInfo; | 22 using ui::LatencyInfo; |
| 23 | 23 |
| 24 namespace content { | 24 namespace content { |
| 25 namespace { | 25 namespace { |
| 26 | 26 |
| 27 // Time interval at which touchmove events will be forwarded to the client while | 27 // Time interval at which touchmove events will be forwarded to the client while |
| 28 // scrolling is active and possible. | 28 // scrolling is active and possible. |
| 29 const double kAsyncTouchMoveIntervalSec = .2; | 29 const double kAsyncTouchMoveIntervalSec = .2; |
| 30 | 30 |
| 31 TouchEventWithLatencyInfo ObtainCancelEventForTouchEvent( | |
| 32 const TouchEventWithLatencyInfo& event_to_cancel) { | |
| 33 TouchEventWithLatencyInfo event = event_to_cancel; | |
| 34 WebTouchEventTraits::ResetTypeAndTouchStates( | |
| 35 WebInputEvent::TouchCancel, | |
| 36 // TODO(rbyers): Shouldn't we use a fresh timestamp? | |
| 37 event.event.timeStampSeconds(), &event.event); | |
| 38 return event; | |
| 39 } | |
| 40 | |
| 41 bool ShouldTouchTriggerTimeout(const WebTouchEvent& event) { | |
| 42 return (event.type() == WebInputEvent::TouchStart || | |
| 43 event.type() == WebInputEvent::TouchMove) && | |
| 44 event.dispatchType == WebInputEvent::Blocking; | |
| 45 } | |
| 46 | |
| 47 // Compare all properties of touch points to determine the state. | 31 // Compare all properties of touch points to determine the state. |
| 48 bool HasPointChanged(const WebTouchPoint& point_1, | 32 bool HasPointChanged(const WebTouchPoint& point_1, |
| 49 const WebTouchPoint& point_2) { | 33 const WebTouchPoint& point_2) { |
| 50 DCHECK_EQ(point_1.id, point_2.id); | 34 DCHECK_EQ(point_1.id, point_2.id); |
| 51 if (point_1.screenPosition != point_2.screenPosition || | 35 if (point_1.screenPosition != point_2.screenPosition || |
| 52 point_1.position != point_2.position || | 36 point_1.position != point_2.position || |
| 53 point_1.radiusX != point_2.radiusX || | 37 point_1.radiusX != point_2.radiusX || |
| 54 point_1.radiusY != point_2.radiusY || | 38 point_1.radiusY != point_2.radiusY || |
| 55 point_1.rotationAngle != point_2.rotationAngle || | 39 point_1.rotationAngle != point_2.rotationAngle || |
| 56 point_1.force != point_2.force || point_1.tiltX != point_2.tiltX || | 40 point_1.force != point_2.force || point_1.tiltX != point_2.tiltX || |
| 57 point_1.tiltY != point_2.tiltY) { | 41 point_1.tiltY != point_2.tiltY) { |
| 58 return true; | 42 return true; |
| 59 } | 43 } |
| 60 return false; | 44 return false; |
| 61 } | 45 } |
| 62 | 46 |
| 63 } // namespace | 47 } // namespace |
| 64 | 48 |
| 65 // Cancels a touch sequence if a touchstart or touchmove ack response is | |
| 66 // sufficiently delayed. | |
| 67 class LegacyTouchEventQueue::TouchTimeoutHandler { | |
| 68 public: | |
| 69 TouchTimeoutHandler(LegacyTouchEventQueue* touch_queue, | |
| 70 base::TimeDelta desktop_timeout_delay, | |
| 71 base::TimeDelta mobile_timeout_delay) | |
| 72 : touch_queue_(touch_queue), | |
| 73 desktop_timeout_delay_(desktop_timeout_delay), | |
| 74 mobile_timeout_delay_(mobile_timeout_delay), | |
| 75 use_mobile_timeout_(false), | |
| 76 pending_ack_state_(PENDING_ACK_NONE), | |
| 77 timeout_monitor_(base::Bind(&TouchTimeoutHandler::OnTimeOut, | |
| 78 base::Unretained(this))), | |
| 79 enabled_(true), | |
| 80 enabled_for_current_sequence_(false), | |
| 81 sequence_awaiting_uma_update_(false), | |
| 82 sequence_using_mobile_timeout_(false) { | |
| 83 SetUseMobileTimeout(false); | |
| 84 } | |
| 85 | |
| 86 ~TouchTimeoutHandler() { LogSequenceEndForUMAIfNecessary(false); } | |
| 87 | |
| 88 void StartIfNecessary(const TouchEventWithLatencyInfo& event) { | |
| 89 if (pending_ack_state_ != PENDING_ACK_NONE) | |
| 90 return; | |
| 91 | |
| 92 if (!enabled_) | |
| 93 return; | |
| 94 | |
| 95 const base::TimeDelta timeout_delay = GetTimeoutDelay(); | |
| 96 if (timeout_delay.is_zero()) | |
| 97 return; | |
| 98 | |
| 99 if (!ShouldTouchTriggerTimeout(event.event)) | |
| 100 return; | |
| 101 | |
| 102 if (WebTouchEventTraits::IsTouchSequenceStart(event.event)) { | |
| 103 LogSequenceStartForUMA(); | |
| 104 enabled_for_current_sequence_ = true; | |
| 105 } | |
| 106 | |
| 107 if (!enabled_for_current_sequence_) | |
| 108 return; | |
| 109 | |
| 110 timeout_event_ = event; | |
| 111 timeout_monitor_.Restart(timeout_delay); | |
| 112 } | |
| 113 | |
| 114 bool ConfirmTouchEvent(InputEventAckState ack_result) { | |
| 115 switch (pending_ack_state_) { | |
| 116 case PENDING_ACK_NONE: | |
| 117 if (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED) | |
| 118 enabled_for_current_sequence_ = false; | |
| 119 timeout_monitor_.Stop(); | |
| 120 return false; | |
| 121 case PENDING_ACK_ORIGINAL_EVENT: | |
| 122 if (AckedTimeoutEventRequiresCancel(ack_result)) { | |
| 123 SetPendingAckState(PENDING_ACK_CANCEL_EVENT); | |
| 124 TouchEventWithLatencyInfo cancel_event = | |
| 125 ObtainCancelEventForTouchEvent(timeout_event_); | |
| 126 touch_queue_->SendTouchEventImmediately(&cancel_event); | |
| 127 } else { | |
| 128 SetPendingAckState(PENDING_ACK_NONE); | |
| 129 touch_queue_->UpdateTouchConsumerStates(timeout_event_.event, | |
| 130 ack_result); | |
| 131 } | |
| 132 return true; | |
| 133 case PENDING_ACK_CANCEL_EVENT: | |
| 134 SetPendingAckState(PENDING_ACK_NONE); | |
| 135 return true; | |
| 136 } | |
| 137 return false; | |
| 138 } | |
| 139 | |
| 140 bool FilterEvent(const WebTouchEvent& event) { | |
| 141 if (!HasTimeoutEvent()) | |
| 142 return false; | |
| 143 | |
| 144 if (WebTouchEventTraits::IsTouchSequenceStart(event)) { | |
| 145 // If a new sequence is observed while we're still waiting on the | |
| 146 // timed-out sequence response, also count the new sequence as timed-out. | |
| 147 LogSequenceStartForUMA(); | |
| 148 LogSequenceEndForUMAIfNecessary(true); | |
| 149 } | |
| 150 | |
| 151 return true; | |
| 152 } | |
| 153 | |
| 154 void SetEnabled(bool enabled) { | |
| 155 if (enabled_ == enabled) | |
| 156 return; | |
| 157 | |
| 158 enabled_ = enabled; | |
| 159 | |
| 160 if (enabled_) | |
| 161 return; | |
| 162 | |
| 163 enabled_for_current_sequence_ = false; | |
| 164 // Only reset the |timeout_handler_| if the timer is running and has not | |
| 165 // yet timed out. This ensures that an already timed out sequence is | |
| 166 // properly flushed by the handler. | |
| 167 if (IsTimeoutTimerRunning()) { | |
| 168 pending_ack_state_ = PENDING_ACK_NONE; | |
| 169 timeout_monitor_.Stop(); | |
| 170 } | |
| 171 } | |
| 172 | |
| 173 void SetUseMobileTimeout(bool use_mobile_timeout) { | |
| 174 use_mobile_timeout_ = use_mobile_timeout; | |
| 175 } | |
| 176 | |
| 177 bool IsTimeoutTimerRunning() const { return timeout_monitor_.IsRunning(); } | |
| 178 | |
| 179 bool IsEnabled() const { return enabled_ && !GetTimeoutDelay().is_zero(); } | |
| 180 | |
| 181 private: | |
| 182 enum PendingAckState { | |
| 183 PENDING_ACK_NONE, | |
| 184 PENDING_ACK_ORIGINAL_EVENT, | |
| 185 PENDING_ACK_CANCEL_EVENT, | |
| 186 }; | |
| 187 | |
| 188 void OnTimeOut() { | |
| 189 LogSequenceEndForUMAIfNecessary(true); | |
| 190 SetPendingAckState(PENDING_ACK_ORIGINAL_EVENT); | |
| 191 touch_queue_->FlushQueue(); | |
| 192 } | |
| 193 | |
| 194 // Skip a cancel event if the timed-out event had no consumer and was the | |
| 195 // initial event in the gesture. | |
| 196 bool AckedTimeoutEventRequiresCancel(InputEventAckState ack_result) const { | |
| 197 DCHECK(HasTimeoutEvent()); | |
| 198 if (ack_result != INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS) | |
| 199 return true; | |
| 200 return !WebTouchEventTraits::IsTouchSequenceStart(timeout_event_.event); | |
| 201 } | |
| 202 | |
| 203 void SetPendingAckState(PendingAckState new_pending_ack_state) { | |
| 204 DCHECK_NE(pending_ack_state_, new_pending_ack_state); | |
| 205 switch (new_pending_ack_state) { | |
| 206 case PENDING_ACK_ORIGINAL_EVENT: | |
| 207 DCHECK_EQ(pending_ack_state_, PENDING_ACK_NONE); | |
| 208 TRACE_EVENT_ASYNC_BEGIN0("input", "TouchEventTimeout", this); | |
| 209 break; | |
| 210 case PENDING_ACK_CANCEL_EVENT: | |
| 211 DCHECK_EQ(pending_ack_state_, PENDING_ACK_ORIGINAL_EVENT); | |
| 212 DCHECK(!timeout_monitor_.IsRunning()); | |
| 213 DCHECK(touch_queue_->empty()); | |
| 214 TRACE_EVENT_ASYNC_STEP_INTO0("input", "TouchEventTimeout", this, | |
| 215 "CancelEvent"); | |
| 216 break; | |
| 217 case PENDING_ACK_NONE: | |
| 218 DCHECK(!timeout_monitor_.IsRunning()); | |
| 219 DCHECK(touch_queue_->empty()); | |
| 220 TRACE_EVENT_ASYNC_END0("input", "TouchEventTimeout", this); | |
| 221 break; | |
| 222 } | |
| 223 pending_ack_state_ = new_pending_ack_state; | |
| 224 } | |
| 225 | |
| 226 void LogSequenceStartForUMA() { | |
| 227 // Always flush any unlogged entries before starting a new one. | |
| 228 LogSequenceEndForUMAIfNecessary(false); | |
| 229 sequence_awaiting_uma_update_ = true; | |
| 230 sequence_using_mobile_timeout_ = use_mobile_timeout_; | |
| 231 } | |
| 232 | |
| 233 void LogSequenceEndForUMAIfNecessary(bool timed_out) { | |
| 234 if (!sequence_awaiting_uma_update_) | |
| 235 return; | |
| 236 | |
| 237 sequence_awaiting_uma_update_ = false; | |
| 238 | |
| 239 if (sequence_using_mobile_timeout_) { | |
| 240 UMA_HISTOGRAM_BOOLEAN("Event.Touch.TimedOutOnMobileSite", timed_out); | |
| 241 } else { | |
| 242 UMA_HISTOGRAM_BOOLEAN("Event.Touch.TimedOutOnDesktopSite", timed_out); | |
| 243 } | |
| 244 } | |
| 245 | |
| 246 base::TimeDelta GetTimeoutDelay() const { | |
| 247 return use_mobile_timeout_ ? mobile_timeout_delay_ : desktop_timeout_delay_; | |
| 248 } | |
| 249 | |
| 250 bool HasTimeoutEvent() const { | |
| 251 return pending_ack_state_ != PENDING_ACK_NONE; | |
| 252 } | |
| 253 | |
| 254 LegacyTouchEventQueue* touch_queue_; | |
| 255 | |
| 256 // How long to wait on a touch ack before cancelling the touch sequence. | |
| 257 const base::TimeDelta desktop_timeout_delay_; | |
| 258 const base::TimeDelta mobile_timeout_delay_; | |
| 259 bool use_mobile_timeout_; | |
| 260 | |
| 261 // The touch event source for which we expect the next ack. | |
| 262 PendingAckState pending_ack_state_; | |
| 263 | |
| 264 // The event for which the ack timeout is triggered. | |
| 265 TouchEventWithLatencyInfo timeout_event_; | |
| 266 | |
| 267 // Provides timeout-based callback behavior. | |
| 268 TimeoutMonitor timeout_monitor_; | |
| 269 | |
| 270 bool enabled_; | |
| 271 bool enabled_for_current_sequence_; | |
| 272 | |
| 273 // Bookkeeping to classify and log whether a touch sequence times out. | |
| 274 bool sequence_awaiting_uma_update_; | |
| 275 bool sequence_using_mobile_timeout_; | |
| 276 }; | |
| 277 | |
| 278 // This class represents a single coalesced touch event. However, it also keeps | 49 // This class represents a single coalesced touch event. However, it also keeps |
| 279 // track of all the original touch-events that were coalesced into a single | 50 // track of all the original touch-events that were coalesced into a single |
| 280 // event. The coalesced event is forwarded to the renderer, while the original | 51 // event. The coalesced event is forwarded to the renderer, while the original |
| 281 // touch-events are sent to the Client (on ACK for the coalesced event) so that | 52 // touch-events are sent to the Client (on ACK for the coalesced event) so that |
| 282 // the Client receives the event with their original timestamp. | 53 // the Client receives the event with their original timestamp. |
| 283 class CoalescedWebTouchEvent { | 54 class CoalescedWebTouchEvent { |
| 284 public: | 55 public: |
| 285 CoalescedWebTouchEvent(const TouchEventWithLatencyInfo& event, | 56 CoalescedWebTouchEvent(const TouchEventWithLatencyInfo& event, |
| 286 bool suppress_client_ack) | 57 bool suppress_client_ack) |
| 287 : coalesced_event_(event), suppress_client_ack_(suppress_client_ack) { | 58 : coalesced_event_(event), suppress_client_ack_(suppress_client_ack) { |
| (...skipping 177 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 465 last_sent_touch_timestamp_sec_ + kAsyncTouchMoveIntervalSec) { | 236 last_sent_touch_timestamp_sec_ + kAsyncTouchMoveIntervalSec) { |
| 466 FlushPendingAsyncTouchmove(); | 237 FlushPendingAsyncTouchmove(); |
| 467 } | 238 } |
| 468 } | 239 } |
| 469 return; | 240 return; |
| 470 } | 241 } |
| 471 | 242 |
| 472 DCHECK(!dispatching_touch_ack_); | 243 DCHECK(!dispatching_touch_ack_); |
| 473 dispatching_touch_ = false; | 244 dispatching_touch_ = false; |
| 474 | 245 |
| 475 if (timeout_handler_ && timeout_handler_->ConfirmTouchEvent(ack_result)) | 246 if (timeout_handler_ && |
| 247 timeout_handler_->ConfirmTouchEvent(unique_touch_event_id, ack_result)) |
| 476 return; | 248 return; |
| 477 | 249 |
| 478 if (touch_queue_.empty()) | 250 if (touch_queue_.empty()) |
| 479 return; | 251 return; |
| 480 | 252 |
| 481 // We don't care about the ordering of the acks vs the ordering of the | 253 // We don't care about the ordering of the acks vs the ordering of the |
| 482 // dispatched events because we can receive the ack for event B before the ack | 254 // dispatched events because we can receive the ack for event B before the ack |
| 483 // for event A even though A was sent before B. This seems to be happening | 255 // for event A even though A was sent before B. This seems to be happening |
| 484 // when, for example, A is acked from renderer but B isn't, so the ack for B | 256 // when, for example, A is acked from renderer but B isn't, so the ack for B |
| 485 // is synthesized "locally" in InputRouter. | 257 // is synthesized "locally" in InputRouter. |
| (...skipping 226 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 712 // Skip ack for TouchScrollStarted since it was synthesized within the queue. | 484 // Skip ack for TouchScrollStarted since it was synthesized within the queue. |
| 713 if (acked_event->coalesced_event().event.type() != | 485 if (acked_event->coalesced_event().event.type() != |
| 714 WebInputEvent::TouchScrollStarted) { | 486 WebInputEvent::TouchScrollStarted) { |
| 715 acked_event->DispatchAckToClient(ack_result, optional_latency_info, | 487 acked_event->DispatchAckToClient(ack_result, optional_latency_info, |
| 716 client_); | 488 client_); |
| 717 } | 489 } |
| 718 | 490 |
| 719 touch_queue_.pop_front(); | 491 touch_queue_.pop_front(); |
| 720 } | 492 } |
| 721 | 493 |
| 494 void LegacyTouchEventQueue::SendTouchCancelEventForTouchEvent( |
| 495 const TouchEventWithLatencyInfo& event_to_cancel) { |
| 496 TouchEventWithLatencyInfo event = event_to_cancel; |
| 497 WebTouchEventTraits::ResetTypeAndTouchStates( |
| 498 WebInputEvent::TouchCancel, |
| 499 // TODO(rbyers): Shouldn't we use a fresh timestamp? |
| 500 event.event.timeStampSeconds(), &event.event); |
| 501 SendTouchEventImmediately(&event); |
| 502 } |
| 503 |
| 722 void LegacyTouchEventQueue::SendTouchEventImmediately( | 504 void LegacyTouchEventQueue::SendTouchEventImmediately( |
| 723 TouchEventWithLatencyInfo* touch) { | 505 TouchEventWithLatencyInfo* touch) { |
| 724 // TODO(crbug.com/600773): Hack to avoid cyclic reentry to this method. | 506 // TODO(crbug.com/600773): Hack to avoid cyclic reentry to this method. |
| 725 if (dispatching_touch_) | 507 if (dispatching_touch_) |
| 726 return; | 508 return; |
| 727 | 509 |
| 728 if (touch->event.type() == WebInputEvent::TouchStart) | 510 if (touch->event.type() == WebInputEvent::TouchStart) |
| 729 touch->event.touchStartOrFirstTouchMove = true; | 511 touch->event.touchStartOrFirstTouchMove = true; |
| 730 | 512 |
| 731 // For touchmove events, compare touch points position from current event | 513 // For touchmove events, compare touch points position from current event |
| (...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 787 LegacyTouchEventQueue::FilterBeforeForwarding(const WebTouchEvent& event) { | 569 LegacyTouchEventQueue::FilterBeforeForwarding(const WebTouchEvent& event) { |
| 788 if (event.type() == WebInputEvent::TouchScrollStarted) | 570 if (event.type() == WebInputEvent::TouchScrollStarted) |
| 789 return FORWARD_TO_RENDERER; | 571 return FORWARD_TO_RENDERER; |
| 790 | 572 |
| 791 if (WebTouchEventTraits::IsTouchSequenceStart(event)) { | 573 if (WebTouchEventTraits::IsTouchSequenceStart(event)) { |
| 792 has_handler_for_current_sequence_ = false; | 574 has_handler_for_current_sequence_ = false; |
| 793 send_touch_events_async_ = false; | 575 send_touch_events_async_ = false; |
| 794 pending_async_touchmove_.reset(); | 576 pending_async_touchmove_.reset(); |
| 795 last_sent_touchevent_.reset(); | 577 last_sent_touchevent_.reset(); |
| 796 | 578 |
| 797 touch_sequence_start_position_ = gfx::PointF(event.touches[0].position); | |
| 798 drop_remaining_touches_in_sequence_ = false; | 579 drop_remaining_touches_in_sequence_ = false; |
| 799 if (!has_handlers_) { | 580 if (!has_handlers_) { |
| 800 drop_remaining_touches_in_sequence_ = true; | 581 drop_remaining_touches_in_sequence_ = true; |
| 801 return ACK_WITH_NO_CONSUMER_EXISTS; | 582 return ACK_WITH_NO_CONSUMER_EXISTS; |
| 802 } | 583 } |
| 803 } | 584 } |
| 804 | 585 |
| 805 if (timeout_handler_ && timeout_handler_->FilterEvent(event)) | 586 if (timeout_handler_ && timeout_handler_->FilterEvent(event)) |
| 806 return ACK_WITH_NO_CONSUMER_EXISTS; | 587 return ACK_WITH_NO_CONSUMER_EXISTS; |
| 807 | 588 |
| (...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 860 if (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED) | 641 if (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED) |
| 861 send_touch_events_async_ = false; | 642 send_touch_events_async_ = false; |
| 862 has_handler_for_current_sequence_ |= | 643 has_handler_for_current_sequence_ |= |
| 863 ack_result != INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS; | 644 ack_result != INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS; |
| 864 } else if (WebTouchEventTraits::IsTouchSequenceEnd(event)) { | 645 } else if (WebTouchEventTraits::IsTouchSequenceEnd(event)) { |
| 865 has_handler_for_current_sequence_ = false; | 646 has_handler_for_current_sequence_ = false; |
| 866 } | 647 } |
| 867 } | 648 } |
| 868 | 649 |
| 869 } // namespace content | 650 } // namespace content |
| OLD | NEW |