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 |