1 // Copyright 2014 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.
5 #include "content/browser/renderer_host/input/gesture_event_queue.h"
7 #include "base/command_line.h"
8 #include "base/strings/string_number_conversions.h"
9 #include "content/browser/renderer_host/input/input_router.h"
10 #include "content/browser/renderer_host/input/touchpad_tap_suppression_controller.h"
11 #include "content/browser/renderer_host/input/touchscreen_tap_suppression_controller.h"
12 #include "content/public/common/content_switches.h"
14 using blink::WebGestureEvent
;
15 using blink::WebInputEvent
;
20 // Default debouncing interval duration: if a scroll is in progress, non-scroll
21 // events during this interval are deferred to either its end or discarded on
22 // receipt of another GestureScrollUpdate.
23 static const int kDebouncingIntervalTimeMs
= 30;
27 GestureEventQueue::GestureEventQueue(
28 GestureEventQueueClient
* client
,
29 TouchpadTapSuppressionControllerClient
* touchpad_client
)
31 fling_in_progress_(false),
32 scrolling_in_progress_(false),
33 ignore_next_ack_(false),
34 combined_scroll_pinch_(gfx::Transform()),
35 touchpad_tap_suppression_controller_(
36 new TouchpadTapSuppressionController(touchpad_client
)),
37 touchscreen_tap_suppression_controller_(
38 new TouchscreenTapSuppressionController(this)),
39 debounce_interval_time_ms_(kDebouncingIntervalTimeMs
),
40 debounce_enabled_(true) {
42 DCHECK(touchpad_tap_suppression_controller_
);
43 if (CommandLine::ForCurrentProcess()->HasSwitch(
44 switches::kDisableGestureDebounce
)) {
45 debounce_enabled_
= false;
49 GestureEventQueue::~GestureEventQueue() { }
51 bool GestureEventQueue::ShouldDiscardFlingCancelEvent(
52 const GestureEventWithLatencyInfo
& gesture_event
) const {
53 if (coalesced_gesture_events_
.empty() && fling_in_progress_
)
55 GestureQueue::const_reverse_iterator it
=
56 coalesced_gesture_events_
.rbegin();
57 while (it
!= coalesced_gesture_events_
.rend()) {
58 if (it
->event
.type
== WebInputEvent::GestureFlingStart
)
60 if (it
->event
.type
== WebInputEvent::GestureFlingCancel
)
67 bool GestureEventQueue::ShouldForwardForBounceReduction(
68 const GestureEventWithLatencyInfo
& gesture_event
) {
69 if (!debounce_enabled_
)
71 switch (gesture_event
.event
.type
) {
72 case WebInputEvent::GestureScrollUpdate
:
73 if (!scrolling_in_progress_
) {
74 debounce_deferring_timer_
.Start(
76 base::TimeDelta::FromMilliseconds(debounce_interval_time_ms_
),
78 &GestureEventQueue::SendScrollEndingEventsNow
);
80 // Extend the bounce interval.
81 debounce_deferring_timer_
.Reset();
83 scrolling_in_progress_
= true;
84 debouncing_deferral_queue_
.clear();
86 case WebInputEvent::GesturePinchBegin
:
87 case WebInputEvent::GesturePinchEnd
:
88 case WebInputEvent::GesturePinchUpdate
:
89 // TODO(rjkroege): Debounce pinch (http://crbug.com/147647)
92 if (scrolling_in_progress_
) {
93 debouncing_deferral_queue_
.push_back(gesture_event
);
103 // NOTE: The filters are applied successively. This simplifies the change.
104 bool GestureEventQueue::ShouldForward(
105 const GestureEventWithLatencyInfo
& gesture_event
) {
106 return ShouldForwardForZeroVelocityFlingStart(gesture_event
) &&
107 ShouldForwardForBounceReduction(gesture_event
) &&
108 ShouldForwardForGFCFiltering(gesture_event
) &&
109 ShouldForwardForTapSuppression(gesture_event
) &&
110 ShouldForwardForCoalescing(gesture_event
);
113 bool GestureEventQueue::ShouldForwardForZeroVelocityFlingStart(
114 const GestureEventWithLatencyInfo
& gesture_event
) const {
115 return gesture_event
.event
.type
!= WebInputEvent::GestureFlingStart
||
116 gesture_event
.event
.sourceDevice
!= WebGestureEvent::Touchpad
||
117 gesture_event
.event
.data
.flingStart
.velocityX
!= 0 ||
118 gesture_event
.event
.data
.flingStart
.velocityY
!= 0;
121 bool GestureEventQueue::ShouldForwardForGFCFiltering(
122 const GestureEventWithLatencyInfo
& gesture_event
) const {
123 return gesture_event
.event
.type
!= WebInputEvent::GestureFlingCancel
||
124 !ShouldDiscardFlingCancelEvent(gesture_event
);
127 bool GestureEventQueue::ShouldForwardForTapSuppression(
128 const GestureEventWithLatencyInfo
& gesture_event
) {
129 switch (gesture_event
.event
.type
) {
130 case WebInputEvent::GestureFlingCancel
:
131 if (gesture_event
.event
.sourceDevice
== WebGestureEvent::Touchscreen
)
132 touchscreen_tap_suppression_controller_
->GestureFlingCancel();
134 touchpad_tap_suppression_controller_
->GestureFlingCancel();
136 case WebInputEvent::GestureTapDown
:
137 return !touchscreen_tap_suppression_controller_
->
138 ShouldDeferGestureTapDown(gesture_event
);
139 case WebInputEvent::GestureShowPress
:
140 return !touchscreen_tap_suppression_controller_
->
141 ShouldDeferGestureShowPress(gesture_event
);
142 case WebInputEvent::GestureTapCancel
:
143 case WebInputEvent::GestureTap
:
144 case WebInputEvent::GestureTapUnconfirmed
:
145 case WebInputEvent::GestureDoubleTap
:
146 return !touchscreen_tap_suppression_controller_
->
147 ShouldSuppressGestureTapEnd();
155 bool GestureEventQueue::ShouldForwardForCoalescing(
156 const GestureEventWithLatencyInfo
& gesture_event
) {
157 switch (gesture_event
.event
.type
) {
158 case WebInputEvent::GestureFlingCancel
:
159 fling_in_progress_
= false;
161 case WebInputEvent::GestureFlingStart
:
162 fling_in_progress_
= true;
164 case WebInputEvent::GesturePinchUpdate
:
165 case WebInputEvent::GestureScrollUpdate
:
166 MergeOrInsertScrollAndPinchEvent(gesture_event
);
167 return ShouldHandleEventNow();
171 EnqueueEvent(gesture_event
);
172 return ShouldHandleEventNow();
175 void GestureEventQueue::ProcessGestureAck(InputEventAckState ack_result
,
176 WebInputEvent::Type type
,
177 const ui::LatencyInfo
& latency
) {
178 if (coalesced_gesture_events_
.empty()) {
179 DLOG(ERROR
) << "Received unexpected ACK for event type " << type
;
183 // Ack'ing an event may enqueue additional gesture events. By ack'ing the
184 // event before the forwarding of queued events below, such additional events
185 // can be coalesced with existing queued events prior to dispatch.
186 GestureEventWithLatencyInfo event_with_latency
=
187 coalesced_gesture_events_
.front();
188 DCHECK_EQ(event_with_latency
.event
.type
, type
);
189 event_with_latency
.latency
.AddNewLatencyFrom(latency
);
190 client_
->OnGestureEventAck(event_with_latency
, ack_result
);
192 const bool processed
= (INPUT_EVENT_ACK_STATE_CONSUMED
== ack_result
);
193 if (type
== WebInputEvent::GestureFlingCancel
) {
194 if (event_with_latency
.event
.sourceDevice
== WebGestureEvent::Touchscreen
)
195 touchscreen_tap_suppression_controller_
->GestureFlingCancelAck(processed
);
197 touchpad_tap_suppression_controller_
->GestureFlingCancelAck(processed
);
199 coalesced_gesture_events_
.pop_front();
201 if (ignore_next_ack_
) {
202 ignore_next_ack_
= false;
206 if (coalesced_gesture_events_
.empty())
209 const GestureEventWithLatencyInfo
& first_gesture_event
=
210 coalesced_gesture_events_
.front();
212 // TODO(yusufo): Introduce GesturePanScroll so that these can be combined
213 // into one gesture and kept inside the queue that way.
214 // Check for the coupled GesturePinchUpdate before sending either event,
215 // handling the case where the first GestureScrollUpdate ack is synchronous.
216 GestureEventWithLatencyInfo second_gesture_event
;
217 if (first_gesture_event
.event
.type
== WebInputEvent::GestureScrollUpdate
&&
218 coalesced_gesture_events_
.size() > 1 &&
219 coalesced_gesture_events_
[1].event
.type
==
220 WebInputEvent::GesturePinchUpdate
) {
221 second_gesture_event
= coalesced_gesture_events_
[1];
222 ignore_next_ack_
= true;
225 client_
->SendGestureEventImmediately(first_gesture_event
);
226 if (second_gesture_event
.event
.type
!= WebInputEvent::Undefined
)
227 client_
->SendGestureEventImmediately(second_gesture_event
);
230 TouchpadTapSuppressionController
*
231 GestureEventQueue::GetTouchpadTapSuppressionController() {
232 return touchpad_tap_suppression_controller_
.get();
235 bool GestureEventQueue::HasQueuedGestureEvents() const {
236 return !coalesced_gesture_events_
.empty();
239 void GestureEventQueue::FlingHasBeenHalted() {
240 fling_in_progress_
= false;
243 bool GestureEventQueue::ShouldHandleEventNow() const {
244 return coalesced_gesture_events_
.size() == 1;
247 void GestureEventQueue::ForwardGestureEvent(
248 const GestureEventWithLatencyInfo
& gesture_event
) {
249 if (ShouldForwardForCoalescing(gesture_event
))
250 client_
->SendGestureEventImmediately(gesture_event
);
253 void GestureEventQueue::SendScrollEndingEventsNow() {
254 scrolling_in_progress_
= false;
255 if (debouncing_deferral_queue_
.empty())
257 GestureQueue debouncing_deferral_queue
;
258 debouncing_deferral_queue
.swap(debouncing_deferral_queue_
);
259 for (GestureQueue::const_iterator it
= debouncing_deferral_queue
.begin();
260 it
!= debouncing_deferral_queue
.end(); it
++) {
261 if (ShouldForwardForGFCFiltering(*it
) &&
262 ShouldForwardForTapSuppression(*it
) &&
263 ShouldForwardForCoalescing(*it
)) {
264 client_
->SendGestureEventImmediately(*it
);
269 void GestureEventQueue::MergeOrInsertScrollAndPinchEvent(
270 const GestureEventWithLatencyInfo
& gesture_event
) {
271 if (coalesced_gesture_events_
.size() <= 1) {
272 EnqueueEvent(gesture_event
);
275 GestureEventWithLatencyInfo
* last_event
= &coalesced_gesture_events_
.back();
276 if (last_event
->CanCoalesceWith(gesture_event
)) {
277 last_event
->CoalesceWith(gesture_event
);
278 if (!combined_scroll_pinch_
.IsIdentity()) {
279 combined_scroll_pinch_
.ConcatTransform(
280 GetTransformForEvent(gesture_event
));
284 if (coalesced_gesture_events_
.size() == 2 ||
285 (coalesced_gesture_events_
.size() == 3 && ignore_next_ack_
) ||
286 !ShouldTryMerging(gesture_event
, *last_event
)) {
287 EnqueueEvent(gesture_event
);
290 GestureEventWithLatencyInfo scroll_event
;
291 GestureEventWithLatencyInfo pinch_event
;
292 scroll_event
.event
.modifiers
|= gesture_event
.event
.modifiers
;
293 scroll_event
.event
.timeStampSeconds
= gesture_event
.event
.timeStampSeconds
;
294 // Keep the oldest LatencyInfo.
295 DCHECK_LE(last_event
->latency
.trace_id
, gesture_event
.latency
.trace_id
);
296 scroll_event
.latency
= last_event
->latency
;
297 pinch_event
= scroll_event
;
298 scroll_event
.event
.type
= WebInputEvent::GestureScrollUpdate
;
299 pinch_event
.event
.type
= WebInputEvent::GesturePinchUpdate
;
300 pinch_event
.event
.x
= gesture_event
.event
.type
==
301 WebInputEvent::GesturePinchUpdate
?
302 gesture_event
.event
.x
: last_event
->event
.x
;
303 pinch_event
.event
.y
= gesture_event
.event
.type
==
304 WebInputEvent::GesturePinchUpdate
?
305 gesture_event
.event
.y
: last_event
->event
.y
;
307 combined_scroll_pinch_
.ConcatTransform(GetTransformForEvent(gesture_event
));
308 GestureEventWithLatencyInfo
* second_last_event
= &coalesced_gesture_events_
309 [coalesced_gesture_events_
.size() - 2];
310 if (ShouldTryMerging(gesture_event
, *second_last_event
)) {
311 // Keep the oldest LatencyInfo.
312 DCHECK_LE(second_last_event
->latency
.trace_id
,
313 scroll_event
.latency
.trace_id
);
314 scroll_event
.latency
= second_last_event
->latency
;
315 pinch_event
.latency
= second_last_event
->latency
;
316 coalesced_gesture_events_
.pop_back();
318 DCHECK(combined_scroll_pinch_
== GetTransformForEvent(gesture_event
));
319 combined_scroll_pinch_
.
320 PreconcatTransform(GetTransformForEvent(*last_event
));
322 coalesced_gesture_events_
.pop_back();
323 float combined_scale
=
324 SkMScalarToFloat(combined_scroll_pinch_
.matrix().get(0, 0));
325 float combined_scroll_pinch_x
=
326 SkMScalarToFloat(combined_scroll_pinch_
.matrix().get(0, 3));
327 float combined_scroll_pinch_y
=
328 SkMScalarToFloat(combined_scroll_pinch_
.matrix().get(1, 3));
329 scroll_event
.event
.data
.scrollUpdate
.deltaX
=
330 (combined_scroll_pinch_x
+ pinch_event
.event
.x
) / combined_scale
-
332 scroll_event
.event
.data
.scrollUpdate
.deltaY
=
333 (combined_scroll_pinch_y
+ pinch_event
.event
.y
) / combined_scale
-
335 coalesced_gesture_events_
.push_back(scroll_event
);
336 pinch_event
.event
.data
.pinchUpdate
.scale
= combined_scale
;
337 coalesced_gesture_events_
.push_back(pinch_event
);
340 bool GestureEventQueue::ShouldTryMerging(
341 const GestureEventWithLatencyInfo
& new_event
,
342 const GestureEventWithLatencyInfo
& event_in_queue
) const {
344 new_event
.event
.timeStampSeconds
<
345 event_in_queue
.event
.timeStampSeconds
)
346 << "Event time not monotonic?\n";
347 return (event_in_queue
.event
.type
== WebInputEvent::GestureScrollUpdate
||
348 event_in_queue
.event
.type
== WebInputEvent::GesturePinchUpdate
) &&
349 event_in_queue
.event
.modifiers
== new_event
.event
.modifiers
;
352 gfx::Transform
GestureEventQueue::GetTransformForEvent(
353 const GestureEventWithLatencyInfo
& gesture_event
) const {
354 gfx::Transform gesture_transform
= gfx::Transform();
355 if (gesture_event
.event
.type
== WebInputEvent::GestureScrollUpdate
) {
356 gesture_transform
.Translate(gesture_event
.event
.data
.scrollUpdate
.deltaX
,
357 gesture_event
.event
.data
.scrollUpdate
.deltaY
);
358 } else if (gesture_event
.event
.type
== WebInputEvent::GesturePinchUpdate
) {
359 float scale
= gesture_event
.event
.data
.pinchUpdate
.scale
;
360 gesture_transform
.Translate(-gesture_event
.event
.x
, -gesture_event
.event
.y
);
361 gesture_transform
.Scale(scale
,scale
);
362 gesture_transform
.Translate(gesture_event
.event
.x
, gesture_event
.event
.y
);
364 return gesture_transform
;
367 void GestureEventQueue::EnqueueEvent(
368 const GestureEventWithLatencyInfo
& gesture_event
) {
369 coalesced_gesture_events_
.push_back(gesture_event
);
370 // Scroll and pinch events contributing to |combined_scroll_pinch_| will be
371 // manually added to the queue in |MergeOrInsertScrollAndPinchEvent()|.
372 combined_scroll_pinch_
= gfx::Transform();
375 } // namespace content