OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2017 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "content/browser/renderer_host/input/passthrough_touch_event_queue.h" | |
6 | |
7 #include <utility> | |
8 | |
9 #include "base/auto_reset.h" | |
tdresser
2017/02/24 20:04:28
Are we using all these includes?
| |
10 #include "base/macros.h" | |
11 #include "base/memory/ptr_util.h" | |
12 #include "base/metrics/histogram_macros.h" | |
13 #include "base/trace_event/trace_event.h" | |
14 #include "content/browser/renderer_host/input/touch_timeout_handler.h" | |
15 #include "content/common/input/web_touch_event_traits.h" | |
16 #include "ui/events/base_event_utils.h" | |
17 #include "ui/gfx/geometry/point_f.h" | |
18 | |
19 using blink::WebInputEvent; | |
20 using blink::WebTouchEvent; | |
21 using blink::WebTouchPoint; | |
22 using ui::LatencyInfo; | |
23 | |
24 namespace content { | |
25 namespace { | |
26 | |
27 // Compare all properties of touch points to determine the state. | |
28 bool HasPointChanged(const WebTouchPoint& point_1, | |
tdresser
2017/02/24 20:04:28
Comment doesn't really add anything.
The name imp
| |
29 const WebTouchPoint& point_2) { | |
30 DCHECK_EQ(point_1.id, point_2.id); | |
31 if (point_1.screenPosition != point_2.screenPosition || | |
32 point_1.position != point_2.position || | |
33 point_1.radiusX != point_2.radiusX || | |
34 point_1.radiusY != point_2.radiusY || | |
35 point_1.rotationAngle != point_2.rotationAngle || | |
36 point_1.force != point_2.force || point_1.tiltX != point_2.tiltX || | |
37 point_1.tiltY != point_2.tiltY) { | |
38 return true; | |
39 } | |
40 return false; | |
41 } | |
42 | |
43 } // namespace | |
44 | |
45 PassthroughTouchEventQueue::TouchEventWithLatencyInfoAndAckState:: | |
46 TouchEventWithLatencyInfoAndAckState(const TouchEventWithLatencyInfo& event) | |
47 : TouchEventWithLatencyInfo(event), | |
48 ack_state_(INPUT_EVENT_ACK_STATE_UNKNOWN) {} | |
49 | |
50 bool PassthroughTouchEventQueue::TouchEventWithLatencyInfoAndAckState:: | |
51 operator<(const TouchEventWithLatencyInfoAndAckState& other) const { | |
52 return event.uniqueTouchEventId < other.event.uniqueTouchEventId; | |
tdresser
2017/02/24 20:04:28
Every 2 years or so of use, we'll wrap around the
| |
53 } | |
54 | |
55 PassthroughTouchEventQueue::PassthroughTouchEventQueue( | |
56 TouchEventQueueClient* client, | |
57 const Config& config) | |
58 : client_(client), | |
59 has_handlers_(true), | |
60 maybe_has_handler_for_current_sequence_(false), | |
61 drop_remaining_touches_in_sequence_(false), | |
62 send_touch_events_async_(false) { | |
63 if (config.touch_ack_timeout_supported) { | |
64 timeout_handler_.reset( | |
65 new TouchTimeoutHandler(this, config.desktop_touch_ack_timeout_delay, | |
66 config.mobile_touch_ack_timeout_delay)); | |
67 } | |
68 } | |
69 | |
70 PassthroughTouchEventQueue::~PassthroughTouchEventQueue() {} | |
71 | |
72 void PassthroughTouchEventQueue::SendTouchCancelEventForTouchEvent( | |
73 const TouchEventWithLatencyInfo& event_to_cancel) { | |
74 TouchEventWithLatencyInfo event = event_to_cancel; | |
75 WebTouchEventTraits::ResetTypeAndTouchStates( | |
76 WebInputEvent::TouchCancel, | |
77 // TODO(rbyers): Shouldn't we use a fresh timestamp? | |
78 event.event.timeStampSeconds(), &event.event); | |
79 SendTouchEventImmediately(&event, false); | |
80 } | |
81 | |
82 void PassthroughTouchEventQueue::QueueEvent( | |
83 const TouchEventWithLatencyInfo& event) { | |
84 TRACE_EVENT0("input", "PassthroughTouchEventQueue::QueueEvent"); | |
85 PreFilterResult filter_result = FilterBeforeForwarding(event.event); | |
86 if (filter_result != FORWARD_TO_RENDERER) { | |
87 client_->OnFilteringTouchEvent(event.event); | |
88 | |
89 InputEventAckState ack_state = | |
90 filter_result == ACK_WITH_NO_CONSUMER_EXISTS | |
91 ? INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS | |
92 : INPUT_EVENT_ACK_STATE_NOT_CONSUMED; | |
93 TouchEventWithLatencyInfoAndAckState event_with_ack_state = event; | |
94 event_with_ack_state.set_ack_state(ack_state); | |
95 outstanding_touches_.insert(event_with_ack_state); | |
96 AckCompletedEvents(); | |
97 return; | |
98 } | |
99 TouchEventWithLatencyInfo cloned_event(event); | |
100 SendTouchEventImmediately(&cloned_event, true); | |
101 } | |
102 | |
103 void PassthroughTouchEventQueue::PrependTouchScrollNotification() { | |
104 TRACE_EVENT0("input", | |
105 "PassthroughTouchEventQueue::PrependTouchScrollNotification"); | |
106 | |
107 TouchEventWithLatencyInfo touch( | |
108 WebInputEvent::TouchScrollStarted, WebInputEvent::NoModifiers, | |
109 ui::EventTimeStampToSeconds(ui::EventTimeForNow()), LatencyInfo()); | |
110 touch.event.dispatchType = WebInputEvent::EventNonBlocking; | |
111 SendTouchEventImmediately(&touch, true); | |
112 } | |
113 | |
114 void PassthroughTouchEventQueue::ProcessTouchAck( | |
115 InputEventAckState ack_result, | |
116 const LatencyInfo& latency_info, | |
117 const uint32_t unique_touch_event_id) { | |
118 TRACE_EVENT0("input", "PassthroughTouchEventQueue::ProcessTouchAck"); | |
119 if (timeout_handler_ && | |
120 timeout_handler_->ConfirmTouchEvent(unique_touch_event_id, ack_result)) | |
121 return; | |
122 | |
123 auto touch_event_iter = outstanding_touches_.begin(); | |
124 while (touch_event_iter != outstanding_touches_.end()) { | |
125 if (unique_touch_event_id == touch_event_iter->event.uniqueTouchEventId) | |
126 break; | |
127 ++touch_event_iter; | |
128 } | |
129 | |
130 if (touch_event_iter == outstanding_touches_.end()) | |
131 return; | |
132 | |
133 TouchEventWithLatencyInfoAndAckState event = *touch_event_iter; | |
134 touch_event_iter = outstanding_touches_.erase(touch_event_iter); | |
135 event.latency.AddNewLatencyFrom(latency_info); | |
136 event.set_ack_state(ack_result); | |
137 outstanding_touches_.insert(touch_event_iter, event); | |
138 | |
139 AckCompletedEvents(); | |
140 } | |
141 | |
142 void PassthroughTouchEventQueue::OnGestureScrollEvent( | |
143 const GestureEventWithLatencyInfo& gesture_event) { | |
144 if (gesture_event.event.type() == blink::WebInputEvent::GestureScrollUpdate && | |
145 gesture_event.event.resendingPluginId == -1) { | |
146 send_touch_events_async_ = true; | |
147 } | |
148 } | |
149 | |
150 void PassthroughTouchEventQueue::OnGestureEventAck( | |
151 const GestureEventWithLatencyInfo& event, | |
152 InputEventAckState ack_result) { | |
153 // Turn events sent during gesture scrolls to be async. | |
154 if (event.event.type() == blink::WebInputEvent::GestureScrollUpdate && | |
155 event.event.resendingPluginId == -1) { | |
156 send_touch_events_async_ = (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED); | |
157 } | |
158 } | |
159 | |
160 void PassthroughTouchEventQueue::OnHasTouchEventHandlers(bool has_handlers) { | |
161 has_handlers_ = has_handlers; | |
162 } | |
163 | |
164 bool PassthroughTouchEventQueue::IsPendingAckTouchStart() const { | |
165 if (outstanding_touches_.empty()) | |
166 return false; | |
167 | |
168 for (auto& iter : outstanding_touches_) { | |
169 if (iter.event.type() == WebInputEvent::TouchStart) | |
170 return true; | |
171 } | |
172 return false; | |
173 } | |
174 | |
175 void PassthroughTouchEventQueue::SetAckTimeoutEnabled(bool enabled) { | |
176 if (timeout_handler_) | |
177 timeout_handler_->SetEnabled(enabled); | |
178 } | |
179 | |
180 void PassthroughTouchEventQueue::SetIsMobileOptimizedSite( | |
181 bool mobile_optimized_site) { | |
182 if (timeout_handler_) | |
183 timeout_handler_->SetUseMobileTimeout(mobile_optimized_site); | |
184 } | |
185 | |
186 bool PassthroughTouchEventQueue::IsAckTimeoutEnabled() const { | |
187 return timeout_handler_ && timeout_handler_->IsEnabled(); | |
188 } | |
189 | |
190 bool PassthroughTouchEventQueue::Empty() const { | |
191 return outstanding_touches_.empty(); | |
192 } | |
193 | |
194 void PassthroughTouchEventQueue::FlushQueue() { | |
195 drop_remaining_touches_in_sequence_ = true; | |
196 while (!outstanding_touches_.empty()) { | |
197 auto iter = outstanding_touches_.begin(); | |
198 TouchEventWithLatencyInfoAndAckState event = *iter; | |
199 outstanding_touches_.erase(iter); | |
200 if (event.ack_state() == INPUT_EVENT_ACK_STATE_UNKNOWN) | |
201 event.set_ack_state(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS); | |
202 AckTouchEventToClient(event, event.ack_state()); | |
203 } | |
204 } | |
205 | |
206 void PassthroughTouchEventQueue::AckCompletedEvents() { | |
207 while (!outstanding_touches_.empty()) { | |
208 auto iter = outstanding_touches_.begin(); | |
209 if (iter->ack_state() == INPUT_EVENT_ACK_STATE_UNKNOWN) | |
210 break; | |
211 TouchEventWithLatencyInfoAndAckState event = *iter; | |
212 outstanding_touches_.erase(iter); | |
213 AckTouchEventToClient(event, event.ack_state()); | |
214 } | |
215 } | |
216 | |
217 void PassthroughTouchEventQueue::AckTouchEventToClient( | |
218 const TouchEventWithLatencyInfo& acked_event, | |
219 InputEventAckState ack_result) { | |
220 UpdateTouchConsumerStates(acked_event.event, ack_result); | |
221 | |
222 // Skip ack for TouchScrollStarted since it was synthesized within the queue. | |
223 if (acked_event.event.type() != WebInputEvent::TouchScrollStarted) { | |
224 client_->OnTouchEventAck(acked_event, ack_result); | |
225 } | |
226 } | |
227 | |
228 void PassthroughTouchEventQueue::SendTouchEventImmediately( | |
229 TouchEventWithLatencyInfo* touch, | |
230 bool wait_for_ack) { | |
231 // Note: Touchstart events are marked cancelable to allow transitions between | |
232 // platform scrolling and JS pinching. Touchend events, however, remain | |
233 // uncancelable, mitigating the risk of jank when transitioning to a fling. | |
234 if (send_touch_events_async_ && | |
235 touch->event.type() != WebInputEvent::TouchStart) | |
236 touch->event.dispatchType = WebInputEvent::EventNonBlocking; | |
237 | |
238 if (touch->event.type() == WebInputEvent::TouchStart) | |
239 touch->event.touchStartOrFirstTouchMove = true; | |
240 | |
241 // For touchmove events, compare touch points position from current event | |
242 // to last sent event and update touch points state. | |
243 if (touch->event.type() == WebInputEvent::TouchMove) { | |
244 CHECK(last_sent_touchevent_); | |
245 if (last_sent_touchevent_->type() == WebInputEvent::TouchStart) | |
246 touch->event.touchStartOrFirstTouchMove = true; | |
247 for (unsigned int i = 0; i < last_sent_touchevent_->touchesLength; ++i) { | |
248 const WebTouchPoint& last_touch_point = last_sent_touchevent_->touches[i]; | |
249 // Touches with same id may not have same index in Touches array. | |
250 for (unsigned int j = 0; j < touch->event.touchesLength; ++j) { | |
251 const WebTouchPoint& current_touchmove_point = touch->event.touches[j]; | |
252 if (current_touchmove_point.id != last_touch_point.id) | |
253 continue; | |
254 | |
255 if (!HasPointChanged(last_touch_point, current_touchmove_point)) | |
256 touch->event.touches[j].state = WebTouchPoint::StateStationary; | |
257 | |
258 break; | |
259 } | |
260 } | |
261 } | |
262 | |
263 if (touch->event.type() != WebInputEvent::TouchScrollStarted) { | |
264 if (last_sent_touchevent_) | |
265 *last_sent_touchevent_ = touch->event; | |
266 else | |
267 last_sent_touchevent_.reset(new WebTouchEvent(touch->event)); | |
268 } | |
269 | |
270 if (timeout_handler_) | |
271 timeout_handler_->StartIfNecessary(*touch); | |
272 if (wait_for_ack) | |
273 outstanding_touches_.insert(*touch); | |
274 client_->SendTouchEventImmediately(*touch); | |
275 } | |
276 | |
277 PassthroughTouchEventQueue::PreFilterResult | |
278 PassthroughTouchEventQueue::FilterBeforeForwarding(const WebTouchEvent& event) { | |
279 if (event.type() == WebInputEvent::TouchScrollStarted) | |
280 return FORWARD_TO_RENDERER; | |
281 | |
282 if (WebTouchEventTraits::IsTouchSequenceStart(event)) { | |
283 // We don't know if we have a handler until we get the ACK back so | |
284 // assume it is true. | |
285 maybe_has_handler_for_current_sequence_ = true; | |
286 send_touch_events_async_ = false; | |
287 last_sent_touchevent_.reset(); | |
288 | |
289 drop_remaining_touches_in_sequence_ = false; | |
290 if (!has_handlers_) { | |
291 drop_remaining_touches_in_sequence_ = true; | |
292 return ACK_WITH_NO_CONSUMER_EXISTS; | |
293 } | |
294 } | |
295 | |
296 if (timeout_handler_ && timeout_handler_->FilterEvent(event)) | |
297 return ACK_WITH_NO_CONSUMER_EXISTS; | |
298 | |
299 if (drop_remaining_touches_in_sequence_ && | |
300 event.type() != WebInputEvent::TouchCancel) { | |
301 return ACK_WITH_NO_CONSUMER_EXISTS; | |
302 } | |
303 | |
304 if (event.type() == WebInputEvent::TouchStart) { | |
305 return (has_handlers_ || maybe_has_handler_for_current_sequence_) | |
306 ? FORWARD_TO_RENDERER | |
307 : ACK_WITH_NO_CONSUMER_EXISTS; | |
308 } | |
309 | |
310 if (maybe_has_handler_for_current_sequence_) { | |
311 // Only forward a touch if it has a non-stationary pointer that is active | |
312 // in the current touch sequence. | |
313 for (size_t i = 0; i < event.touchesLength; ++i) { | |
314 const WebTouchPoint& point = event.touches[i]; | |
315 if (point.state == WebTouchPoint::StateStationary) | |
316 continue; | |
317 | |
318 // |last_sent_touchevent_| will be non-null as long as there is an | |
319 // active touch sequence being forwarded to the renderer. | |
320 if (!last_sent_touchevent_) | |
321 continue; | |
322 | |
323 for (size_t j = 0; j < last_sent_touchevent_->touchesLength; ++j) { | |
324 if (point.id != last_sent_touchevent_->touches[j].id) | |
325 continue; | |
326 | |
327 if (event.type() != WebInputEvent::TouchMove) | |
328 return FORWARD_TO_RENDERER; | |
329 | |
330 // All pointers in TouchMove events may have state as StateMoved, | |
331 // even though none of the pointers have not changed in real. | |
332 // Forward these events when at least one pointer has changed. | |
333 if (HasPointChanged(last_sent_touchevent_->touches[j], point)) | |
334 return FORWARD_TO_RENDERER; | |
335 | |
336 // This is a TouchMove event for which we have yet to find a | |
337 // non-stationary pointer. Continue checking the next pointers | |
338 // in the |event|. | |
339 break; | |
340 } | |
341 } | |
342 } | |
343 | |
344 return ACK_WITH_NO_CONSUMER_EXISTS; | |
345 } | |
346 | |
347 void PassthroughTouchEventQueue::UpdateTouchConsumerStates( | |
348 const WebTouchEvent& event, | |
349 InputEventAckState ack_result) { | |
350 if (event.type() == WebInputEvent::TouchStart) { | |
351 if (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED) | |
352 send_touch_events_async_ = false; | |
353 | |
354 // Once we have the ack back for the sequence we know if there | |
355 // is a handler or not. Other touch-starts sent can upgrade | |
356 // whether we have a handler or not as well. | |
357 if (WebTouchEventTraits::IsTouchSequenceStart(event)) { | |
358 maybe_has_handler_for_current_sequence_ = | |
359 ack_result != INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS; | |
360 } else { | |
361 maybe_has_handler_for_current_sequence_ |= | |
362 ack_result != INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS; | |
363 } | |
364 } else if (WebTouchEventTraits::IsTouchSequenceEnd(event)) { | |
365 maybe_has_handler_for_current_sequence_ = false; | |
366 } | |
367 } | |
368 | |
369 size_t PassthroughTouchEventQueue::SizeForTesting() const { | |
370 return outstanding_touches_.size(); | |
371 } | |
372 | |
373 bool PassthroughTouchEventQueue::IsTimeoutRunningForTesting() const { | |
374 return timeout_handler_ && timeout_handler_->IsTimeoutTimerRunning(); | |
375 } | |
376 | |
377 const TouchEventWithLatencyInfo& | |
378 PassthroughTouchEventQueue::GetLatestEventForTesting() const { | |
379 return *outstanding_touches_.rbegin(); | |
380 } | |
381 | |
382 } // namespace content | |
OLD | NEW |