Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(65)

Side by Side Diff: content/browser/renderer_host/input/touch_event_queue.cc

Issue 2663493003: Move the TouchEventQueue to be completely virtual. (Closed)
Patch Set: Fix nits Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
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
3 // found in the LICENSE file.
4
5 #include "content/browser/renderer_host/input/touch_event_queue.h"
6
7 #include <utility>
8
9 #include "base/auto_reset.h"
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/timeout_monitor.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 // Time interval at which touchmove events will be forwarded to the client while
28 // scrolling is active and possible.
29 const double kAsyncTouchMoveIntervalSec = .2;
30
31 // A sanity check on touches received to ensure that touch movement outside
32 // the platform slop region will cause scrolling.
33 const double kMaxConceivablePlatformSlopRegionLengthDipsSquared = 60. * 60.;
34
35 TouchEventWithLatencyInfo ObtainCancelEventForTouchEvent(
36 const TouchEventWithLatencyInfo& event_to_cancel) {
37 TouchEventWithLatencyInfo event = event_to_cancel;
38 WebTouchEventTraits::ResetTypeAndTouchStates(
39 WebInputEvent::TouchCancel,
40 // TODO(rbyers): Shouldn't we use a fresh timestamp?
41 event.event.timeStampSeconds(), &event.event);
42 return event;
43 }
44
45 bool ShouldTouchTriggerTimeout(const WebTouchEvent& event) {
46 return (event.type() == WebInputEvent::TouchStart ||
47 event.type() == WebInputEvent::TouchMove) &&
48 event.dispatchType == WebInputEvent::Blocking;
49 }
50
51 // Compare all properties of touch points to determine the state.
52 bool HasPointChanged(const WebTouchPoint& point_1,
53 const WebTouchPoint& point_2) {
54 DCHECK_EQ(point_1.id, point_2.id);
55 if (point_1.screenPosition != point_2.screenPosition ||
56 point_1.position != point_2.position ||
57 point_1.radiusX != point_2.radiusX ||
58 point_1.radiusY != point_2.radiusY ||
59 point_1.rotationAngle != point_2.rotationAngle ||
60 point_1.force != point_2.force ||
61 point_1.tiltX != point_2.tiltX ||
62 point_1.tiltY != point_2.tiltY) {
63 return true;
64 }
65 return false;
66 }
67
68 } // namespace
69
70
71 // Cancels a touch sequence if a touchstart or touchmove ack response is
72 // sufficiently delayed.
73 class TouchEventQueue::TouchTimeoutHandler {
74 public:
75 TouchTimeoutHandler(TouchEventQueue* touch_queue,
76 base::TimeDelta desktop_timeout_delay,
77 base::TimeDelta mobile_timeout_delay)
78 : touch_queue_(touch_queue),
79 desktop_timeout_delay_(desktop_timeout_delay),
80 mobile_timeout_delay_(mobile_timeout_delay),
81 use_mobile_timeout_(false),
82 pending_ack_state_(PENDING_ACK_NONE),
83 timeout_monitor_(base::Bind(&TouchTimeoutHandler::OnTimeOut,
84 base::Unretained(this))),
85 enabled_(true),
86 enabled_for_current_sequence_(false),
87 sequence_awaiting_uma_update_(false),
88 sequence_using_mobile_timeout_(false) {
89 SetUseMobileTimeout(false);
90 }
91
92 ~TouchTimeoutHandler() {
93 LogSequenceEndForUMAIfNecessary(false);
94 }
95
96 void StartIfNecessary(const TouchEventWithLatencyInfo& event) {
97 if (pending_ack_state_ != PENDING_ACK_NONE)
98 return;
99
100 if (!enabled_)
101 return;
102
103 const base::TimeDelta timeout_delay = GetTimeoutDelay();
104 if (timeout_delay.is_zero())
105 return;
106
107 if (!ShouldTouchTriggerTimeout(event.event))
108 return;
109
110 if (WebTouchEventTraits::IsTouchSequenceStart(event.event)) {
111 LogSequenceStartForUMA();
112 enabled_for_current_sequence_ = true;
113 }
114
115 if (!enabled_for_current_sequence_)
116 return;
117
118 timeout_event_ = event;
119 timeout_monitor_.Restart(timeout_delay);
120 }
121
122 bool ConfirmTouchEvent(InputEventAckState ack_result) {
123 switch (pending_ack_state_) {
124 case PENDING_ACK_NONE:
125 if (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED)
126 enabled_for_current_sequence_ = false;
127 timeout_monitor_.Stop();
128 return false;
129 case PENDING_ACK_ORIGINAL_EVENT:
130 if (AckedTimeoutEventRequiresCancel(ack_result)) {
131 SetPendingAckState(PENDING_ACK_CANCEL_EVENT);
132 TouchEventWithLatencyInfo cancel_event =
133 ObtainCancelEventForTouchEvent(timeout_event_);
134 touch_queue_->SendTouchEventImmediately(&cancel_event);
135 } else {
136 SetPendingAckState(PENDING_ACK_NONE);
137 touch_queue_->UpdateTouchConsumerStates(timeout_event_.event,
138 ack_result);
139 }
140 return true;
141 case PENDING_ACK_CANCEL_EVENT:
142 SetPendingAckState(PENDING_ACK_NONE);
143 return true;
144 }
145 return false;
146 }
147
148 bool FilterEvent(const WebTouchEvent& event) {
149 if (!HasTimeoutEvent())
150 return false;
151
152 if (WebTouchEventTraits::IsTouchSequenceStart(event)) {
153 // If a new sequence is observed while we're still waiting on the
154 // timed-out sequence response, also count the new sequence as timed-out.
155 LogSequenceStartForUMA();
156 LogSequenceEndForUMAIfNecessary(true);
157 }
158
159 return true;
160 }
161
162 void SetEnabled(bool enabled) {
163 if (enabled_ == enabled)
164 return;
165
166 enabled_ = enabled;
167
168 if (enabled_)
169 return;
170
171 enabled_for_current_sequence_ = false;
172 // Only reset the |timeout_handler_| if the timer is running and has not
173 // yet timed out. This ensures that an already timed out sequence is
174 // properly flushed by the handler.
175 if (IsTimeoutTimerRunning()) {
176 pending_ack_state_ = PENDING_ACK_NONE;
177 timeout_monitor_.Stop();
178 }
179 }
180
181 void SetUseMobileTimeout(bool use_mobile_timeout) {
182 use_mobile_timeout_ = use_mobile_timeout;
183 }
184
185 bool IsTimeoutTimerRunning() const { return timeout_monitor_.IsRunning(); }
186
187 bool IsEnabled() const {
188 return enabled_ && !GetTimeoutDelay().is_zero();
189 }
190
191 private:
192 enum PendingAckState {
193 PENDING_ACK_NONE,
194 PENDING_ACK_ORIGINAL_EVENT,
195 PENDING_ACK_CANCEL_EVENT,
196 };
197
198 void OnTimeOut() {
199 LogSequenceEndForUMAIfNecessary(true);
200 SetPendingAckState(PENDING_ACK_ORIGINAL_EVENT);
201 touch_queue_->FlushQueue();
202 }
203
204 // Skip a cancel event if the timed-out event had no consumer and was the
205 // initial event in the gesture.
206 bool AckedTimeoutEventRequiresCancel(InputEventAckState ack_result) const {
207 DCHECK(HasTimeoutEvent());
208 if (ack_result != INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS)
209 return true;
210 return !WebTouchEventTraits::IsTouchSequenceStart(timeout_event_.event);
211 }
212
213 void SetPendingAckState(PendingAckState new_pending_ack_state) {
214 DCHECK_NE(pending_ack_state_, new_pending_ack_state);
215 switch (new_pending_ack_state) {
216 case PENDING_ACK_ORIGINAL_EVENT:
217 DCHECK_EQ(pending_ack_state_, PENDING_ACK_NONE);
218 TRACE_EVENT_ASYNC_BEGIN0("input", "TouchEventTimeout", this);
219 break;
220 case PENDING_ACK_CANCEL_EVENT:
221 DCHECK_EQ(pending_ack_state_, PENDING_ACK_ORIGINAL_EVENT);
222 DCHECK(!timeout_monitor_.IsRunning());
223 DCHECK(touch_queue_->empty());
224 TRACE_EVENT_ASYNC_STEP_INTO0(
225 "input", "TouchEventTimeout", this, "CancelEvent");
226 break;
227 case PENDING_ACK_NONE:
228 DCHECK(!timeout_monitor_.IsRunning());
229 DCHECK(touch_queue_->empty());
230 TRACE_EVENT_ASYNC_END0("input", "TouchEventTimeout", this);
231 break;
232 }
233 pending_ack_state_ = new_pending_ack_state;
234 }
235
236 void LogSequenceStartForUMA() {
237 // Always flush any unlogged entries before starting a new one.
238 LogSequenceEndForUMAIfNecessary(false);
239 sequence_awaiting_uma_update_ = true;
240 sequence_using_mobile_timeout_ = use_mobile_timeout_;
241 }
242
243 void LogSequenceEndForUMAIfNecessary(bool timed_out) {
244 if (!sequence_awaiting_uma_update_)
245 return;
246
247 sequence_awaiting_uma_update_ = false;
248
249 if (sequence_using_mobile_timeout_) {
250 UMA_HISTOGRAM_BOOLEAN("Event.Touch.TimedOutOnMobileSite", timed_out);
251 } else {
252 UMA_HISTOGRAM_BOOLEAN("Event.Touch.TimedOutOnDesktopSite", timed_out);
253 }
254 }
255
256 base::TimeDelta GetTimeoutDelay() const {
257 return use_mobile_timeout_ ? mobile_timeout_delay_ : desktop_timeout_delay_;
258 }
259
260 bool HasTimeoutEvent() const {
261 return pending_ack_state_ != PENDING_ACK_NONE;
262 }
263
264 TouchEventQueue* touch_queue_;
265
266 // How long to wait on a touch ack before cancelling the touch sequence.
267 const base::TimeDelta desktop_timeout_delay_;
268 const base::TimeDelta mobile_timeout_delay_;
269 bool use_mobile_timeout_;
270
271 // The touch event source for which we expect the next ack.
272 PendingAckState pending_ack_state_;
273
274 // The event for which the ack timeout is triggered.
275 TouchEventWithLatencyInfo timeout_event_;
276
277 // Provides timeout-based callback behavior.
278 TimeoutMonitor timeout_monitor_;
279
280 bool enabled_;
281 bool enabled_for_current_sequence_;
282
283 // Bookkeeping to classify and log whether a touch sequence times out.
284 bool sequence_awaiting_uma_update_;
285 bool sequence_using_mobile_timeout_;
286 };
287
288 // Provides touchmove slop suppression for a touch sequence until a
289 // (unprevented) touch will trigger immediate scrolling.
290 class TouchEventQueue::TouchMoveSlopSuppressor {
291 public:
292 TouchMoveSlopSuppressor() : suppressing_touchmoves_(false) {}
293
294 bool FilterEvent(const WebTouchEvent& event) {
295 if (WebTouchEventTraits::IsTouchSequenceStart(event)) {
296 suppressing_touchmoves_ = true;
297 touch_start_location_ = gfx::PointF(event.touches[0].position);
298 }
299
300 if (event.type() == WebInputEvent::TouchEnd ||
301 event.type() == WebInputEvent::TouchCancel)
302 suppressing_touchmoves_ = false;
303
304 if (event.type() != WebInputEvent::TouchMove)
305 return false;
306
307 if (suppressing_touchmoves_) {
308 if (event.touchesLength > 1) {
309 suppressing_touchmoves_ = false;
310 } else if (event.movedBeyondSlopRegion) {
311 suppressing_touchmoves_ = false;
312 } else {
313 // No sane slop region should be larger than 60 DIPs.
314 DCHECK_LT((gfx::PointF(event.touches[0].position) -
315 touch_start_location_).LengthSquared(),
316 kMaxConceivablePlatformSlopRegionLengthDipsSquared);
317 }
318 }
319
320 return suppressing_touchmoves_;
321 }
322
323 void ConfirmTouchEvent(InputEventAckState ack_result) {
324 if (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED)
325 suppressing_touchmoves_ = false;
326 }
327
328 bool suppressing_touchmoves() const { return suppressing_touchmoves_; }
329
330 private:
331 bool suppressing_touchmoves_;
332
333 // Sanity check that the upstream touch provider is properly reporting whether
334 // the touch sequence will cause scrolling.
335 gfx::PointF touch_start_location_;
336
337 DISALLOW_COPY_AND_ASSIGN(TouchMoveSlopSuppressor);
338 };
339
340 // This class represents a single coalesced touch event. However, it also keeps
341 // track of all the original touch-events that were coalesced into a single
342 // event. The coalesced event is forwarded to the renderer, while the original
343 // touch-events are sent to the Client (on ACK for the coalesced event) so that
344 // the Client receives the event with their original timestamp.
345 class CoalescedWebTouchEvent {
346 public:
347 CoalescedWebTouchEvent(const TouchEventWithLatencyInfo& event,
348 bool suppress_client_ack)
349 : coalesced_event_(event), suppress_client_ack_(suppress_client_ack) {
350 TRACE_EVENT_ASYNC_BEGIN0("input", "TouchEventQueue::QueueEvent", this);
351 }
352
353 ~CoalescedWebTouchEvent() {
354 TRACE_EVENT_ASYNC_END0("input", "TouchEventQueue::QueueEvent", this);
355 }
356
357 // Coalesces the event with the existing event if possible. Returns whether
358 // the event was coalesced.
359 bool CoalesceEventIfPossible(
360 const TouchEventWithLatencyInfo& event_with_latency) {
361 if (suppress_client_ack_)
362 return false;
363
364 if (!coalesced_event_.CanCoalesceWith(event_with_latency))
365 return false;
366
367 // Addition of the first event to |uncoaleseced_events_to_ack_| is deferred
368 // until the first coalesced event, optimizing the (common) case where the
369 // event is not coalesced at all.
370 if (uncoaleseced_events_to_ack_.empty())
371 uncoaleseced_events_to_ack_.push_back(coalesced_event_);
372
373 TRACE_EVENT_INSTANT0(
374 "input", "TouchEventQueue::MoveCoalesced", TRACE_EVENT_SCOPE_THREAD);
375 coalesced_event_.CoalesceWith(event_with_latency);
376 uncoaleseced_events_to_ack_.push_back(event_with_latency);
377 DCHECK_GE(uncoaleseced_events_to_ack_.size(), 2U);
378 return true;
379 }
380
381 void DispatchAckToClient(InputEventAckState ack_result,
382 const ui::LatencyInfo* optional_latency_info,
383 TouchEventQueueClient* client) {
384 DCHECK(client);
385 if (suppress_client_ack_)
386 return;
387
388 if (uncoaleseced_events_to_ack_.empty()) {
389 if (optional_latency_info)
390 coalesced_event_.latency.AddNewLatencyFrom(*optional_latency_info);
391 client->OnTouchEventAck(coalesced_event_, ack_result);
392 return;
393 }
394
395 DCHECK_GE(uncoaleseced_events_to_ack_.size(), 2U);
396 for (WebTouchEventWithLatencyList::iterator
397 iter = uncoaleseced_events_to_ack_.begin(),
398 end = uncoaleseced_events_to_ack_.end();
399 iter != end;
400 ++iter) {
401 if (optional_latency_info)
402 iter->latency.AddNewLatencyFrom(*optional_latency_info);
403 client->OnTouchEventAck(*iter, ack_result);
404 }
405 }
406
407 const TouchEventWithLatencyInfo& coalesced_event() const {
408 return coalesced_event_;
409 }
410
411 private:
412 // This is the event that is forwarded to the renderer.
413 TouchEventWithLatencyInfo coalesced_event_;
414
415 // This is the list of the original events that were coalesced, each requiring
416 // future ack dispatch to the client.
417 // Note that this will be empty if no coalescing has occurred.
418 typedef std::vector<TouchEventWithLatencyInfo> WebTouchEventWithLatencyList;
419 WebTouchEventWithLatencyList uncoaleseced_events_to_ack_;
420
421 bool suppress_client_ack_;
422
423 DISALLOW_COPY_AND_ASSIGN(CoalescedWebTouchEvent);
424 };
425
426 TouchEventQueue::Config::Config()
427 : desktop_touch_ack_timeout_delay(base::TimeDelta::FromMilliseconds(200)),
428 mobile_touch_ack_timeout_delay(base::TimeDelta::FromMilliseconds(1000)),
429 touch_ack_timeout_supported(false) {
430 }
431
432 TouchEventQueue::TouchEventQueue(TouchEventQueueClient* client,
433 const Config& config)
434 : client_(client),
435 dispatching_touch_ack_(false),
436 dispatching_touch_(false),
437 has_handlers_(true),
438 has_handler_for_current_sequence_(false),
439 drop_remaining_touches_in_sequence_(false),
440 touchmove_slop_suppressor_(new TouchMoveSlopSuppressor),
441 send_touch_events_async_(false),
442 last_sent_touch_timestamp_sec_(0) {
443 DCHECK(client);
444 if (config.touch_ack_timeout_supported) {
445 timeout_handler_.reset(
446 new TouchTimeoutHandler(this,
447 config.desktop_touch_ack_timeout_delay,
448 config.mobile_touch_ack_timeout_delay));
449 }
450 }
451
452 TouchEventQueue::~TouchEventQueue() {
453 }
454
455 void TouchEventQueue::QueueEvent(const TouchEventWithLatencyInfo& event) {
456 TRACE_EVENT0("input", "TouchEventQueue::QueueEvent");
457
458 // If the queueing of |event| was triggered by an ack dispatch, defer
459 // processing the event until the dispatch has finished.
460 if (touch_queue_.empty() && !dispatching_touch_ack_) {
461 // Optimization of the case without touch handlers. Removing this path
462 // yields identical results, but this avoids unnecessary allocations.
463 PreFilterResult filter_result = FilterBeforeForwarding(event.event);
464 if (filter_result != FORWARD_TO_RENDERER) {
465 client_->OnFilteringTouchEvent(event.event);
466 client_->OnTouchEventAck(event,
467 filter_result == ACK_WITH_NO_CONSUMER_EXISTS
468 ? INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS
469 : INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
470 return;
471 }
472
473 // There is no touch event in the queue. Forward it to the renderer
474 // immediately.
475 touch_queue_.push_back(
476 base::MakeUnique<CoalescedWebTouchEvent>(event, false));
477 ForwardNextEventToRenderer();
478 return;
479 }
480
481 // If the last queued touch-event was a touch-move, and the current event is
482 // also a touch-move, then the events can be coalesced into a single event.
483 if (touch_queue_.size() > 1) {
484 CoalescedWebTouchEvent* last_event = touch_queue_.back().get();
485 if (last_event->CoalesceEventIfPossible(event))
486 return;
487 }
488 touch_queue_.push_back(
489 base::MakeUnique<CoalescedWebTouchEvent>(event, false));
490 }
491
492 void TouchEventQueue::PrependTouchScrollNotification() {
493 TRACE_EVENT0("input", "TouchEventQueue::PrependTouchScrollNotification");
494
495 // The queue should have an in-flight event when this method is called because
496 // this method is triggered by InputRouterImpl::SendGestureEvent, which is
497 // triggered by TouchEventQueue::AckTouchEventToClient, which has just
498 // received an ack for the in-flight event. We leave the head of the queue
499 // untouched since it is the in-flight event.
500 //
501 // However, for the (integration) tests in RenderWidgetHostTest that trigger
502 // this method indirectly, they push the TouchScrollStarted event into
503 // TouchEventQueue without any way to dispatch it. Below we added a check for
504 // non-empty queue to keep those tests as-is w/o exposing internals of this
505 // class all the way up.
506 if (!touch_queue_.empty()) {
507 TouchEventWithLatencyInfo touch(
508 WebInputEvent::TouchScrollStarted, WebInputEvent::NoModifiers,
509 ui::EventTimeStampToSeconds(ui::EventTimeForNow()), LatencyInfo());
510 touch.event.dispatchType = WebInputEvent::EventNonBlocking;
511
512 auto it = touch_queue_.begin();
513 DCHECK(it != touch_queue_.end());
514 touch_queue_.insert(++it,
515 base::MakeUnique<CoalescedWebTouchEvent>(touch, false));
516 }
517 }
518
519 void TouchEventQueue::ProcessTouchAck(InputEventAckState ack_result,
520 const LatencyInfo& latency_info,
521 const uint32_t unique_touch_event_id) {
522 TRACE_EVENT0("input", "TouchEventQueue::ProcessTouchAck");
523
524 // We receive an ack for async touchmove from render.
525 if (!ack_pending_async_touchmove_ids_.empty() &&
526 ack_pending_async_touchmove_ids_.front() == unique_touch_event_id) {
527 // Remove the first touchmove from the ack_pending_async_touchmove queue.
528 ack_pending_async_touchmove_ids_.pop_front();
529 // Send the next pending async touch move once we receive all acks back.
530 if (pending_async_touchmove_ && ack_pending_async_touchmove_ids_.empty()) {
531 DCHECK(touch_queue_.empty());
532
533 // Dispatch the next pending async touch move when time expires.
534 if (pending_async_touchmove_->event.timeStampSeconds() >=
535 last_sent_touch_timestamp_sec_ + kAsyncTouchMoveIntervalSec) {
536 FlushPendingAsyncTouchmove();
537 }
538 }
539 return;
540 }
541
542 DCHECK(!dispatching_touch_ack_);
543 dispatching_touch_ = false;
544
545 if (timeout_handler_ && timeout_handler_->ConfirmTouchEvent(ack_result))
546 return;
547
548 touchmove_slop_suppressor_->ConfirmTouchEvent(ack_result);
549
550 if (touch_queue_.empty())
551 return;
552
553 // We don't care about the ordering of the acks vs the ordering of the
554 // dispatched events because we can receive the ack for event B before the ack
555 // for event A even though A was sent before B. This seems to be happening
556 // when, for example, A is acked from renderer but B isn't, so the ack for B
557 // is synthesized "locally" in InputRouter.
558 //
559 // TODO(crbug.com/600773): Bring the id checks back when dispatch triggering
560 // is sane.
561
562 PopTouchEventToClient(ack_result, latency_info);
563 TryForwardNextEventToRenderer();
564 }
565
566 void TouchEventQueue::TryForwardNextEventToRenderer() {
567 DCHECK(!dispatching_touch_ack_);
568 // If there are queued touch events, then try to forward them to the renderer
569 // immediately, or ACK the events back to the client if appropriate.
570 while (!touch_queue_.empty()) {
571 const WebTouchEvent& event = touch_queue_.front()->coalesced_event().event;
572 PreFilterResult filter_result = FilterBeforeForwarding(event);
573 if (filter_result != FORWARD_TO_RENDERER)
574 client_->OnFilteringTouchEvent(event);
575 switch (filter_result) {
576 case ACK_WITH_NO_CONSUMER_EXISTS:
577 PopTouchEventToClient(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
578 break;
579 case ACK_WITH_NOT_CONSUMED:
580 PopTouchEventToClient(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
581 break;
582 case FORWARD_TO_RENDERER:
583 ForwardNextEventToRenderer();
584 return;
585 }
586 }
587 }
588
589 void TouchEventQueue::ForwardNextEventToRenderer() {
590 TRACE_EVENT0("input", "TouchEventQueue::ForwardNextEventToRenderer");
591
592 DCHECK(!empty());
593 DCHECK(!dispatching_touch_);
594 TouchEventWithLatencyInfo touch = touch_queue_.front()->coalesced_event();
595
596 if (send_touch_events_async_ &&
597 touch.event.type() == WebInputEvent::TouchMove) {
598 // Throttling touchmove's in a continuous touchmove stream while scrolling
599 // reduces the risk of jank. However, it's still important that the web
600 // application be sent touches at key points in the gesture stream,
601 // e.g., when the application slop region is exceeded or touchmove
602 // coalescing fails because of different modifiers.
603 bool send_touchmove_now = size() > 1;
604 send_touchmove_now |= pending_async_touchmove_ &&
605 !pending_async_touchmove_->CanCoalesceWith(touch);
606 send_touchmove_now |=
607 ack_pending_async_touchmove_ids_.empty() &&
608 (touch.event.timeStampSeconds() >=
609 last_sent_touch_timestamp_sec_ + kAsyncTouchMoveIntervalSec);
610
611 if (!send_touchmove_now) {
612 if (!pending_async_touchmove_) {
613 pending_async_touchmove_.reset(new TouchEventWithLatencyInfo(touch));
614 } else {
615 DCHECK(pending_async_touchmove_->CanCoalesceWith(touch));
616 pending_async_touchmove_->CoalesceWith(touch);
617 }
618 DCHECK_EQ(1U, size());
619 PopTouchEventToClient(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
620 // It's possible (though unlikely) that ack'ing the current touch will
621 // trigger the queueing of another touch event (e.g., a touchcancel). As
622 // forwarding of the queued event will be deferred while the ack is being
623 // dispatched (see |OnTouchEvent()|), try forwarding it now.
624 TryForwardNextEventToRenderer();
625 return;
626 }
627 }
628
629 last_sent_touch_timestamp_sec_ = touch.event.timeStampSeconds();
630
631 // Flush any pending async touch move. If it can be combined with the current
632 // (touchmove) event, great, otherwise send it immediately but separately. Its
633 // ack will trigger forwarding of the original |touch| event.
634 if (pending_async_touchmove_) {
635 if (pending_async_touchmove_->CanCoalesceWith(touch)) {
636 pending_async_touchmove_->CoalesceWith(touch);
637 pending_async_touchmove_->event.dispatchType =
638 send_touch_events_async_ ? WebInputEvent::EventNonBlocking
639 : WebInputEvent::Blocking;
640 touch = *pending_async_touchmove_;
641 pending_async_touchmove_.reset();
642 } else {
643 FlushPendingAsyncTouchmove();
644 return;
645 }
646 }
647
648 // Note: Touchstart events are marked cancelable to allow transitions between
649 // platform scrolling and JS pinching. Touchend events, however, remain
650 // uncancelable, mitigating the risk of jank when transitioning to a fling.
651 if (send_touch_events_async_ &&
652 touch.event.type() != WebInputEvent::TouchStart)
653 touch.event.dispatchType = WebInputEvent::EventNonBlocking;
654
655 SendTouchEventImmediately(&touch);
656 }
657
658 void TouchEventQueue::FlushPendingAsyncTouchmove() {
659 DCHECK(!dispatching_touch_);
660 std::unique_ptr<TouchEventWithLatencyInfo> touch =
661 std::move(pending_async_touchmove_);
662 touch->event.dispatchType = WebInputEvent::EventNonBlocking;
663 touch_queue_.push_front(
664 base::MakeUnique<CoalescedWebTouchEvent>(*touch, true));
665 SendTouchEventImmediately(touch.get());
666 }
667
668 void TouchEventQueue::OnGestureScrollEvent(
669 const GestureEventWithLatencyInfo& gesture_event) {
670 if (gesture_event.event.type() == blink::WebInputEvent::GestureScrollBegin) {
671 if (has_handler_for_current_sequence_ &&
672 !drop_remaining_touches_in_sequence_) {
673 DCHECK(!touchmove_slop_suppressor_->suppressing_touchmoves())
674 << "A touch handler should be offered a touchmove before scrolling.";
675 }
676
677 pending_async_touchmove_.reset();
678
679 return;
680 }
681
682 if (gesture_event.event.type() == blink::WebInputEvent::GestureScrollUpdate &&
683 gesture_event.event.resendingPluginId == -1) {
684 send_touch_events_async_ = true;
685 }
686 }
687
688 void TouchEventQueue::OnGestureEventAck(
689 const GestureEventWithLatencyInfo& event,
690 InputEventAckState ack_result) {
691 // Throttle sending touchmove events as long as the scroll events are handled.
692 // Note that there's no guarantee that this ACK is for the most recent
693 // gesture event (or even part of the current sequence). Worst case, the
694 // delay in updating the absorption state will result in minor UI glitches.
695 // A valid |pending_async_touchmove_| will be flushed when the next event is
696 // forwarded. Scroll updates that are being resent from a GuestView are
697 // ignored.
698 if (event.event.type() == blink::WebInputEvent::GestureScrollUpdate &&
699 event.event.resendingPluginId == -1) {
700 send_touch_events_async_ = (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED);
701 }
702 }
703
704 void TouchEventQueue::OnHasTouchEventHandlers(bool has_handlers) {
705 DCHECK(!dispatching_touch_ack_);
706 DCHECK(!dispatching_touch_);
707 has_handlers_ = has_handlers;
708 }
709
710 bool TouchEventQueue::IsPendingAckTouchStart() const {
711 DCHECK(!dispatching_touch_ack_);
712 if (touch_queue_.empty())
713 return false;
714
715 const blink::WebTouchEvent& event =
716 touch_queue_.front()->coalesced_event().event;
717 return (event.type() == WebInputEvent::TouchStart);
718 }
719
720 void TouchEventQueue::SetAckTimeoutEnabled(bool enabled) {
721 if (timeout_handler_)
722 timeout_handler_->SetEnabled(enabled);
723 }
724
725 void TouchEventQueue::SetIsMobileOptimizedSite(bool mobile_optimized_site) {
726 if (timeout_handler_)
727 timeout_handler_->SetUseMobileTimeout(mobile_optimized_site);
728 }
729
730 bool TouchEventQueue::IsAckTimeoutEnabled() const {
731 return timeout_handler_ && timeout_handler_->IsEnabled();
732 }
733
734 bool TouchEventQueue::HasPendingAsyncTouchMoveForTesting() const {
735 return !!pending_async_touchmove_;
736 }
737
738 bool TouchEventQueue::IsTimeoutRunningForTesting() const {
739 return timeout_handler_ && timeout_handler_->IsTimeoutTimerRunning();
740 }
741
742 const TouchEventWithLatencyInfo&
743 TouchEventQueue::GetLatestEventForTesting() const {
744 return touch_queue_.back()->coalesced_event();
745 }
746
747 void TouchEventQueue::FlushQueue() {
748 DCHECK(!dispatching_touch_ack_);
749 DCHECK(!dispatching_touch_);
750 pending_async_touchmove_.reset();
751 drop_remaining_touches_in_sequence_ = true;
752 while (!touch_queue_.empty())
753 PopTouchEventToClient(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
754 }
755
756 void TouchEventQueue::PopTouchEventToClient(InputEventAckState ack_result) {
757 AckTouchEventToClient(ack_result, nullptr);
758 }
759
760 void TouchEventQueue::PopTouchEventToClient(
761 InputEventAckState ack_result,
762 const LatencyInfo& renderer_latency_info) {
763 AckTouchEventToClient(ack_result, &renderer_latency_info);
764 }
765
766 void TouchEventQueue::AckTouchEventToClient(
767 InputEventAckState ack_result,
768 const ui::LatencyInfo* optional_latency_info) {
769 DCHECK(!dispatching_touch_ack_);
770 if (touch_queue_.empty()) {
771 NOTREACHED() << "Too many acks";
772 return;
773 }
774 std::unique_ptr<CoalescedWebTouchEvent> acked_event =
775 std::move(touch_queue_.front());
776 DCHECK(acked_event);
777
778 UpdateTouchConsumerStates(acked_event->coalesced_event().event, ack_result);
779
780 // Note that acking the touch-event may result in multiple gestures being sent
781 // to the renderer, or touch-events being queued.
782 base::AutoReset<bool> dispatching_touch_ack(&dispatching_touch_ack_, true);
783
784 // Skip ack for TouchScrollStarted since it was synthesized within the queue.
785 if (acked_event->coalesced_event().event.type() !=
786 WebInputEvent::TouchScrollStarted) {
787 acked_event->DispatchAckToClient(ack_result, optional_latency_info,
788 client_);
789 }
790
791 touch_queue_.pop_front();
792 }
793
794 void TouchEventQueue::SendTouchEventImmediately(
795 TouchEventWithLatencyInfo* touch) {
796 // TODO(crbug.com/600773): Hack to avoid cyclic reentry to this method.
797 if (dispatching_touch_)
798 return;
799
800 if (touch->event.type() == WebInputEvent::TouchStart)
801 touch->event.touchStartOrFirstTouchMove = true;
802
803 // For touchmove events, compare touch points position from current event
804 // to last sent event and update touch points state.
805 if (touch->event.type() == WebInputEvent::TouchMove) {
806 CHECK(last_sent_touchevent_);
807 if (last_sent_touchevent_->type() == WebInputEvent::TouchStart)
808 touch->event.touchStartOrFirstTouchMove = true;
809 for (unsigned int i = 0; i < last_sent_touchevent_->touchesLength; ++i) {
810 const WebTouchPoint& last_touch_point =
811 last_sent_touchevent_->touches[i];
812 // Touches with same id may not have same index in Touches array.
813 for (unsigned int j = 0; j < touch->event.touchesLength; ++j) {
814 const WebTouchPoint& current_touchmove_point = touch->event.touches[j];
815 if (current_touchmove_point.id != last_touch_point.id)
816 continue;
817
818 if (!HasPointChanged(last_touch_point, current_touchmove_point))
819 touch->event.touches[j].state = WebTouchPoint::StateStationary;
820
821 break;
822 }
823 }
824 }
825
826 if (touch->event.type() != WebInputEvent::TouchScrollStarted) {
827 if (last_sent_touchevent_)
828 *last_sent_touchevent_ = touch->event;
829 else
830 last_sent_touchevent_.reset(new WebTouchEvent(touch->event));
831 }
832
833 base::AutoReset<bool> dispatching_touch(&dispatching_touch_, true);
834
835 client_->SendTouchEventImmediately(*touch);
836
837 // A synchronous ack will reset |dispatching_touch_|, in which case the touch
838 // timeout should not be started and the count also should not be increased.
839 if (dispatching_touch_) {
840 if (touch->event.type() == WebInputEvent::TouchMove &&
841 touch->event.dispatchType != WebInputEvent::Blocking) {
842 // When we send out a uncancelable touch move, we increase the count and
843 // we do not process input event ack any more, we will just ack to client
844 // and wait for the ack from render. Also we will remove it from the front
845 // of the queue.
846 ack_pending_async_touchmove_ids_.push_back(
847 touch->event.uniqueTouchEventId);
848 dispatching_touch_ = false;
849 PopTouchEventToClient(INPUT_EVENT_ACK_STATE_IGNORED);
850 TryForwardNextEventToRenderer();
851 return;
852 }
853
854 if (timeout_handler_)
855 timeout_handler_->StartIfNecessary(*touch);
856 }
857 }
858
859 TouchEventQueue::PreFilterResult
860 TouchEventQueue::FilterBeforeForwarding(const WebTouchEvent& event) {
861 if (event.type() == WebInputEvent::TouchScrollStarted)
862 return FORWARD_TO_RENDERER;
863
864 if (WebTouchEventTraits::IsTouchSequenceStart(event)) {
865 has_handler_for_current_sequence_ = false;
866 send_touch_events_async_ = false;
867 pending_async_touchmove_.reset();
868 last_sent_touchevent_.reset();
869
870 touch_sequence_start_position_ = gfx::PointF(event.touches[0].position);
871 drop_remaining_touches_in_sequence_ = false;
872 if (!has_handlers_) {
873 drop_remaining_touches_in_sequence_ = true;
874 return ACK_WITH_NO_CONSUMER_EXISTS;
875 }
876 }
877
878 if (timeout_handler_ && timeout_handler_->FilterEvent(event))
879 return ACK_WITH_NO_CONSUMER_EXISTS;
880
881 if (touchmove_slop_suppressor_->FilterEvent(event))
882 return ACK_WITH_NOT_CONSUMED;
883
884 if (drop_remaining_touches_in_sequence_ &&
885 event.type() != WebInputEvent::TouchCancel) {
886 return ACK_WITH_NO_CONSUMER_EXISTS;
887 }
888
889 if (event.type() == WebInputEvent::TouchStart) {
890 return (has_handlers_ || has_handler_for_current_sequence_)
891 ? FORWARD_TO_RENDERER
892 : ACK_WITH_NO_CONSUMER_EXISTS;
893 }
894
895 if (has_handler_for_current_sequence_) {
896 // Only forward a touch if it has a non-stationary pointer that is active
897 // in the current touch sequence.
898 for (size_t i = 0; i < event.touchesLength; ++i) {
899 const WebTouchPoint& point = event.touches[i];
900 if (point.state == WebTouchPoint::StateStationary)
901 continue;
902
903 // |last_sent_touchevent_| will be non-null as long as there is an
904 // active touch sequence being forwarded to the renderer.
905 if (!last_sent_touchevent_)
906 continue;
907
908 for (size_t j = 0; j < last_sent_touchevent_->touchesLength; ++j) {
909 if (point.id != last_sent_touchevent_->touches[j].id)
910 continue;
911
912 if (event.type() != WebInputEvent::TouchMove)
913 return FORWARD_TO_RENDERER;
914
915 // All pointers in TouchMove events may have state as StateMoved,
916 // even though none of the pointers have not changed in real.
917 // Forward these events when at least one pointer has changed.
918 if (HasPointChanged(last_sent_touchevent_->touches[j], point))
919 return FORWARD_TO_RENDERER;
920
921 // This is a TouchMove event for which we have yet to find a
922 // non-stationary pointer. Continue checking the next pointers
923 // in the |event|.
924 break;
925 }
926
927 }
928 }
929
930 return ACK_WITH_NO_CONSUMER_EXISTS;
931 }
932
933 void TouchEventQueue::UpdateTouchConsumerStates(const WebTouchEvent& event,
934 InputEventAckState ack_result) {
935 if (event.type() == WebInputEvent::TouchStart) {
936 if (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED)
937 send_touch_events_async_ = false;
938 has_handler_for_current_sequence_ |=
939 ack_result != INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS;
940 } else if (WebTouchEventTraits::IsTouchSequenceEnd(event)) {
941 has_handler_for_current_sequence_ = false;
942 }
943 }
944
945 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698