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/trace_event/trace_event.h"
8 #include "content/browser/renderer_host/input/touchpad_tap_suppression_controller.h"
9 #include "content/browser/renderer_host/input/touchscreen_tap_suppression_controller.h"
11 using blink::WebGestureEvent
;
12 using blink::WebInputEvent
;
17 // Whether |event_in_queue| is GesturePinchUpdate or GestureScrollUpdate and
18 // has the same modifiers/source as the new scroll/pinch event. Compatible
19 // scroll and pinch event pairs can be logically coalesced.
20 bool IsCompatibleScrollorPinch(
21 const GestureEventWithLatencyInfo
& new_event
,
22 const GestureEventWithLatencyInfo
& event_in_queue
) {
23 DCHECK(new_event
.event
.type
== WebInputEvent::GestureScrollUpdate
||
24 new_event
.event
.type
== WebInputEvent::GesturePinchUpdate
)
25 << "Invalid event type for pinch/scroll coalescing: "
26 << WebInputEventTraits::GetName(new_event
.event
.type
);
27 DLOG_IF(WARNING
, new_event
.event
.timeStampSeconds
<
28 event_in_queue
.event
.timeStampSeconds
)
29 << "Event time not monotonic?\n";
30 return (event_in_queue
.event
.type
== WebInputEvent::GestureScrollUpdate
||
31 event_in_queue
.event
.type
== WebInputEvent::GesturePinchUpdate
) &&
32 event_in_queue
.event
.modifiers
== new_event
.event
.modifiers
&&
33 event_in_queue
.event
.sourceDevice
== new_event
.event
.sourceDevice
;
36 // Returns the transform matrix corresponding to the gesture event.
37 gfx::Transform
GetTransformForEvent(
38 const GestureEventWithLatencyInfo
& gesture_event
) {
39 gfx::Transform gesture_transform
;
40 if (gesture_event
.event
.type
== WebInputEvent::GestureScrollUpdate
) {
41 gesture_transform
.Translate(gesture_event
.event
.data
.scrollUpdate
.deltaX
,
42 gesture_event
.event
.data
.scrollUpdate
.deltaY
);
43 } else if (gesture_event
.event
.type
== WebInputEvent::GesturePinchUpdate
) {
44 float scale
= gesture_event
.event
.data
.pinchUpdate
.scale
;
45 gesture_transform
.Translate(-gesture_event
.event
.x
, -gesture_event
.event
.y
);
46 gesture_transform
.Scale(scale
, scale
);
47 gesture_transform
.Translate(gesture_event
.event
.x
, gesture_event
.event
.y
);
49 NOTREACHED() << "Invalid event type for transform retrieval: "
50 << WebInputEventTraits::GetName(gesture_event
.event
.type
);
52 return gesture_transform
;
57 GestureEventQueue::Config::Config() : enable_fling_cancel_filtering(true) {
60 GestureEventQueue::GestureEventQueue(
61 GestureEventQueueClient
* client
,
62 TouchpadTapSuppressionControllerClient
* touchpad_client
,
65 enable_fling_cancel_filtering_(config
.enable_fling_cancel_filtering
),
66 active_fling_count_(0),
67 scrolling_in_progress_(false),
68 ignore_next_ack_(false),
69 touchpad_tap_suppression_controller_(
71 config
.touchpad_tap_suppression_config
),
72 touchscreen_tap_suppression_controller_(
74 config
.touchscreen_tap_suppression_config
),
75 debounce_interval_(config
.debounce_interval
) {
77 DCHECK(touchpad_client
);
80 GestureEventQueue::~GestureEventQueue() { }
82 void GestureEventQueue::QueueEvent(
83 const GestureEventWithLatencyInfo
& gesture_event
) {
84 TRACE_EVENT0("input", "GestureEventQueue::QueueEvent");
85 if (!ShouldForwardForBounceReduction(gesture_event
) ||
86 !ShouldForwardForGFCFiltering(gesture_event
) ||
87 !ShouldForwardForTapSuppression(gesture_event
)) {
91 QueueAndForwardIfNecessary(gesture_event
);
94 bool GestureEventQueue::ShouldDiscardFlingCancelEvent(
95 const GestureEventWithLatencyInfo
& gesture_event
) const {
96 if (!enable_fling_cancel_filtering_
)
99 GestureQueue::const_reverse_iterator it
=
100 coalesced_gesture_events_
.rbegin();
101 while (it
!= coalesced_gesture_events_
.rend()) {
102 if (it
->event
.type
== WebInputEvent::GestureFlingStart
)
104 if (it
->event
.type
== WebInputEvent::GestureFlingCancel
)
108 // If there are no fling-affecting events in the queue, and there's still an
109 // active fling in the renderer, the cancel event should not be dropped.
110 return !active_fling_count_
;
113 bool GestureEventQueue::ShouldForwardForBounceReduction(
114 const GestureEventWithLatencyInfo
& gesture_event
) {
115 if (debounce_interval_
<= base::TimeDelta())
117 switch (gesture_event
.event
.type
) {
118 case WebInputEvent::GestureScrollUpdate
:
119 if (!scrolling_in_progress_
) {
120 debounce_deferring_timer_
.Start(
124 &GestureEventQueue::SendScrollEndingEventsNow
);
126 // Extend the bounce interval.
127 debounce_deferring_timer_
.Reset();
129 scrolling_in_progress_
= true;
130 debouncing_deferral_queue_
.clear();
132 case WebInputEvent::GesturePinchBegin
:
133 case WebInputEvent::GesturePinchEnd
:
134 case WebInputEvent::GesturePinchUpdate
:
135 // TODO(rjkroege): Debounce pinch (http://crbug.com/147647)
138 if (scrolling_in_progress_
) {
139 debouncing_deferral_queue_
.push_back(gesture_event
);
146 bool GestureEventQueue::ShouldForwardForGFCFiltering(
147 const GestureEventWithLatencyInfo
& gesture_event
) const {
148 return gesture_event
.event
.type
!= WebInputEvent::GestureFlingCancel
||
149 !ShouldDiscardFlingCancelEvent(gesture_event
);
152 bool GestureEventQueue::ShouldForwardForTapSuppression(
153 const GestureEventWithLatencyInfo
& gesture_event
) {
154 switch (gesture_event
.event
.type
) {
155 case WebInputEvent::GestureFlingCancel
:
156 if (gesture_event
.event
.sourceDevice
==
157 blink::WebGestureDeviceTouchscreen
)
158 touchscreen_tap_suppression_controller_
.GestureFlingCancel();
160 touchpad_tap_suppression_controller_
.GestureFlingCancel();
162 case WebInputEvent::GestureTapDown
:
163 case WebInputEvent::GestureShowPress
:
164 case WebInputEvent::GestureTapUnconfirmed
:
165 case WebInputEvent::GestureTapCancel
:
166 case WebInputEvent::GestureTap
:
167 case WebInputEvent::GestureDoubleTap
:
168 if (gesture_event
.event
.sourceDevice
==
169 blink::WebGestureDeviceTouchscreen
) {
170 return !touchscreen_tap_suppression_controller_
.FilterTapEvent(
179 void GestureEventQueue::QueueAndForwardIfNecessary(
180 const GestureEventWithLatencyInfo
& gesture_event
) {
181 switch (gesture_event
.event
.type
) {
182 case WebInputEvent::GesturePinchUpdate
:
183 case WebInputEvent::GestureScrollUpdate
:
184 QueueScrollOrPinchAndForwardIfNecessary(gesture_event
);
190 coalesced_gesture_events_
.push_back(gesture_event
);
191 if (coalesced_gesture_events_
.size() == 1)
192 client_
->SendGestureEventImmediately(gesture_event
);
195 void GestureEventQueue::ProcessGestureAck(InputEventAckState ack_result
,
196 WebInputEvent::Type type
,
197 const ui::LatencyInfo
& latency
) {
198 TRACE_EVENT0("input", "GestureEventQueue::ProcessGestureAck");
200 if (coalesced_gesture_events_
.empty()) {
201 DLOG(ERROR
) << "Received unexpected ACK for event type " << type
;
205 if (ack_result
== INPUT_EVENT_ACK_STATE_CONSUMED
&&
206 type
== blink::WebInputEvent::GestureFlingStart
) {
207 ++active_fling_count_
;
210 // It's possible that the ack for the second event in an in-flight, coalesced
211 // Gesture{Scroll,Pinch}Update pair is received prior to the first event ack.
212 // TODO(jdduke): Unify GSU/GPU pairs into a single event, crbug.com/359115.
213 size_t event_index
= 0;
214 if (ignore_next_ack_
&&
215 coalesced_gesture_events_
.size() > 1 &&
216 coalesced_gesture_events_
[0].event
.type
!= type
&&
217 coalesced_gesture_events_
[1].event
.type
== type
) {
220 GestureEventWithLatencyInfo event_with_latency
=
221 coalesced_gesture_events_
[event_index
];
222 DCHECK_EQ(event_with_latency
.event
.type
, type
);
223 event_with_latency
.latency
.AddNewLatencyFrom(latency
);
225 // Ack'ing an event may enqueue additional gesture events. By ack'ing the
226 // event before the forwarding of queued events below, such additional events
227 // can be coalesced with existing queued events prior to dispatch.
228 client_
->OnGestureEventAck(event_with_latency
, ack_result
);
230 const bool processed
= (INPUT_EVENT_ACK_STATE_CONSUMED
== ack_result
);
231 if (type
== WebInputEvent::GestureFlingCancel
) {
232 if (event_with_latency
.event
.sourceDevice
==
233 blink::WebGestureDeviceTouchscreen
)
234 touchscreen_tap_suppression_controller_
.GestureFlingCancelAck(processed
);
236 touchpad_tap_suppression_controller_
.GestureFlingCancelAck(processed
);
238 DCHECK_LT(event_index
, coalesced_gesture_events_
.size());
239 coalesced_gesture_events_
.erase(coalesced_gesture_events_
.begin() +
242 if (ignore_next_ack_
) {
243 ignore_next_ack_
= false;
247 if (coalesced_gesture_events_
.empty())
250 const GestureEventWithLatencyInfo
& first_gesture_event
=
251 coalesced_gesture_events_
.front();
253 // TODO(jdduke): Unify GSU/GPU pairs into a single event, crbug.com/359115.
254 // Check for the coupled GesturePinchUpdate before sending either event,
255 // handling the case where the first GestureScrollUpdate ack is synchronous.
256 GestureEventWithLatencyInfo second_gesture_event
;
257 if (first_gesture_event
.event
.type
== WebInputEvent::GestureScrollUpdate
&&
258 coalesced_gesture_events_
.size() > 1 &&
259 coalesced_gesture_events_
[1].event
.type
==
260 WebInputEvent::GesturePinchUpdate
) {
261 second_gesture_event
= coalesced_gesture_events_
[1];
262 ignore_next_ack_
= true;
265 client_
->SendGestureEventImmediately(first_gesture_event
);
266 if (second_gesture_event
.event
.type
!= WebInputEvent::Undefined
)
267 client_
->SendGestureEventImmediately(second_gesture_event
);
270 TouchpadTapSuppressionController
*
271 GestureEventQueue::GetTouchpadTapSuppressionController() {
272 return &touchpad_tap_suppression_controller_
;
275 void GestureEventQueue::DidStopFlinging() {
276 DCHECK_GE(active_fling_count_
, 0);
277 --active_fling_count_
;
280 void GestureEventQueue::ForwardGestureEvent(
281 const GestureEventWithLatencyInfo
& gesture_event
) {
282 QueueAndForwardIfNecessary(gesture_event
);
285 void GestureEventQueue::SendScrollEndingEventsNow() {
286 scrolling_in_progress_
= false;
287 if (debouncing_deferral_queue_
.empty())
289 GestureQueue debouncing_deferral_queue
;
290 debouncing_deferral_queue
.swap(debouncing_deferral_queue_
);
291 for (GestureQueue::const_iterator it
= debouncing_deferral_queue
.begin();
292 it
!= debouncing_deferral_queue
.end(); it
++) {
293 if (ShouldForwardForGFCFiltering(*it
) &&
294 ShouldForwardForTapSuppression(*it
)) {
295 QueueAndForwardIfNecessary(*it
);
300 void GestureEventQueue::QueueScrollOrPinchAndForwardIfNecessary(
301 const GestureEventWithLatencyInfo
& gesture_event
) {
302 DCHECK_GE(coalesced_gesture_events_
.size(), EventsInFlightCount());
303 const size_t unsent_events_count
=
304 coalesced_gesture_events_
.size() - EventsInFlightCount();
305 if (!unsent_events_count
) {
306 coalesced_gesture_events_
.push_back(gesture_event
);
307 if (coalesced_gesture_events_
.size() == 1) {
308 client_
->SendGestureEventImmediately(gesture_event
);
309 } else if (coalesced_gesture_events_
.size() == 2) {
310 DCHECK(!ignore_next_ack_
);
311 // If there is an in-flight scroll, the new pinch can be forwarded
312 // immediately, avoiding a potential frame delay between the two
313 // (similarly for an in-flight pinch with a new scroll).
314 const GestureEventWithLatencyInfo
& first_event
=
315 coalesced_gesture_events_
.front();
316 if (gesture_event
.event
.type
!= first_event
.event
.type
&&
317 IsCompatibleScrollorPinch(gesture_event
, first_event
)) {
318 ignore_next_ack_
= true;
319 client_
->SendGestureEventImmediately(gesture_event
);
325 GestureEventWithLatencyInfo
* last_event
= &coalesced_gesture_events_
.back();
326 if (last_event
->CanCoalesceWith(gesture_event
)) {
327 last_event
->CoalesceWith(gesture_event
);
331 if (!IsCompatibleScrollorPinch(gesture_event
, *last_event
)) {
332 coalesced_gesture_events_
.push_back(gesture_event
);
336 GestureEventWithLatencyInfo scroll_event
;
337 GestureEventWithLatencyInfo pinch_event
;
338 scroll_event
.event
.modifiers
|= gesture_event
.event
.modifiers
;
339 scroll_event
.event
.sourceDevice
= gesture_event
.event
.sourceDevice
;
340 scroll_event
.event
.timeStampSeconds
= gesture_event
.event
.timeStampSeconds
;
341 // Keep the oldest LatencyInfo.
342 DCHECK_LE(last_event
->latency
.trace_id
, gesture_event
.latency
.trace_id
);
343 scroll_event
.latency
= last_event
->latency
;
344 pinch_event
= scroll_event
;
345 scroll_event
.event
.type
= WebInputEvent::GestureScrollUpdate
;
346 pinch_event
.event
.type
= WebInputEvent::GesturePinchUpdate
;
347 pinch_event
.event
.x
= gesture_event
.event
.type
==
348 WebInputEvent::GesturePinchUpdate
?
349 gesture_event
.event
.x
: last_event
->event
.x
;
350 pinch_event
.event
.y
= gesture_event
.event
.type
==
351 WebInputEvent::GesturePinchUpdate
?
352 gesture_event
.event
.y
: last_event
->event
.y
;
354 gfx::Transform combined_scroll_pinch
= GetTransformForEvent(*last_event
);
355 // Only include the second-to-last event in the coalesced pair if it exists
356 // and can be combined with the new event.
357 if (unsent_events_count
> 1) {
358 const GestureEventWithLatencyInfo
& second_last_event
=
359 coalesced_gesture_events_
[coalesced_gesture_events_
.size() - 2];
360 if (IsCompatibleScrollorPinch(gesture_event
, second_last_event
)) {
361 // Keep the oldest LatencyInfo.
362 DCHECK_LE(second_last_event
.latency
.trace_id
,
363 scroll_event
.latency
.trace_id
);
364 scroll_event
.latency
= second_last_event
.latency
;
365 pinch_event
.latency
= second_last_event
.latency
;
366 combined_scroll_pinch
.PreconcatTransform(
367 GetTransformForEvent(second_last_event
));
368 coalesced_gesture_events_
.pop_back();
371 combined_scroll_pinch
.ConcatTransform(GetTransformForEvent(gesture_event
));
372 coalesced_gesture_events_
.pop_back();
374 float combined_scale
=
375 SkMScalarToFloat(combined_scroll_pinch
.matrix().get(0, 0));
376 float combined_scroll_pinch_x
=
377 SkMScalarToFloat(combined_scroll_pinch
.matrix().get(0, 3));
378 float combined_scroll_pinch_y
=
379 SkMScalarToFloat(combined_scroll_pinch
.matrix().get(1, 3));
380 scroll_event
.event
.data
.scrollUpdate
.deltaX
=
381 (combined_scroll_pinch_x
+ pinch_event
.event
.x
) / combined_scale
-
383 scroll_event
.event
.data
.scrollUpdate
.deltaY
=
384 (combined_scroll_pinch_y
+ pinch_event
.event
.y
) / combined_scale
-
386 coalesced_gesture_events_
.push_back(scroll_event
);
387 pinch_event
.event
.data
.pinchUpdate
.scale
= combined_scale
;
388 coalesced_gesture_events_
.push_back(pinch_event
);
391 size_t GestureEventQueue::EventsInFlightCount() const {
392 if (coalesced_gesture_events_
.empty())
395 if (!ignore_next_ack_
)
398 DCHECK_GT(coalesced_gesture_events_
.size(), 1U);
402 } // namespace content