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() {
60 GestureEventQueue::GestureEventQueue(
61 GestureEventQueueClient
* client
,
62 TouchpadTapSuppressionControllerClient
* touchpad_client
,
65 fling_in_progress_(false),
66 scrolling_in_progress_(false),
67 ignore_next_ack_(false),
68 touchpad_tap_suppression_controller_(
70 config
.touchpad_tap_suppression_config
),
71 touchscreen_tap_suppression_controller_(
73 config
.touchscreen_tap_suppression_config
),
74 debounce_interval_(config
.debounce_interval
) {
76 DCHECK(touchpad_client
);
79 GestureEventQueue::~GestureEventQueue() { }
81 void GestureEventQueue::QueueEvent(
82 const GestureEventWithLatencyInfo
& gesture_event
) {
83 TRACE_EVENT0("input", "GestureEventQueue::QueueEvent");
84 if (!ShouldForwardForBounceReduction(gesture_event
) ||
85 !ShouldForwardForGFCFiltering(gesture_event
) ||
86 !ShouldForwardForTapSuppression(gesture_event
)) {
90 QueueAndForwardIfNecessary(gesture_event
);
93 bool GestureEventQueue::ShouldDiscardFlingCancelEvent(
94 const GestureEventWithLatencyInfo
& gesture_event
) const {
95 if (coalesced_gesture_events_
.empty() && fling_in_progress_
)
97 GestureQueue::const_reverse_iterator it
=
98 coalesced_gesture_events_
.rbegin();
99 while (it
!= coalesced_gesture_events_
.rend()) {
100 if (it
->event
.type
== WebInputEvent::GestureFlingStart
)
102 if (it
->event
.type
== WebInputEvent::GestureFlingCancel
)
109 bool GestureEventQueue::ShouldForwardForBounceReduction(
110 const GestureEventWithLatencyInfo
& gesture_event
) {
111 if (debounce_interval_
<= base::TimeDelta())
113 switch (gesture_event
.event
.type
) {
114 case WebInputEvent::GestureScrollUpdate
:
115 if (!scrolling_in_progress_
) {
116 debounce_deferring_timer_
.Start(
120 &GestureEventQueue::SendScrollEndingEventsNow
);
122 // Extend the bounce interval.
123 debounce_deferring_timer_
.Reset();
125 scrolling_in_progress_
= true;
126 debouncing_deferral_queue_
.clear();
128 case WebInputEvent::GesturePinchBegin
:
129 case WebInputEvent::GesturePinchEnd
:
130 case WebInputEvent::GesturePinchUpdate
:
131 // TODO(rjkroege): Debounce pinch (http://crbug.com/147647)
134 if (scrolling_in_progress_
) {
135 debouncing_deferral_queue_
.push_back(gesture_event
);
142 bool GestureEventQueue::ShouldForwardForGFCFiltering(
143 const GestureEventWithLatencyInfo
& gesture_event
) const {
144 return gesture_event
.event
.type
!= WebInputEvent::GestureFlingCancel
||
145 !ShouldDiscardFlingCancelEvent(gesture_event
);
148 bool GestureEventQueue::ShouldForwardForTapSuppression(
149 const GestureEventWithLatencyInfo
& gesture_event
) {
150 switch (gesture_event
.event
.type
) {
151 case WebInputEvent::GestureFlingCancel
:
152 if (gesture_event
.event
.sourceDevice
==
153 blink::WebGestureDeviceTouchscreen
)
154 touchscreen_tap_suppression_controller_
.GestureFlingCancel();
156 touchpad_tap_suppression_controller_
.GestureFlingCancel();
158 case WebInputEvent::GestureTapDown
:
159 case WebInputEvent::GestureShowPress
:
160 case WebInputEvent::GestureTapUnconfirmed
:
161 case WebInputEvent::GestureTapCancel
:
162 case WebInputEvent::GestureTap
:
163 case WebInputEvent::GestureDoubleTap
:
164 if (gesture_event
.event
.sourceDevice
==
165 blink::WebGestureDeviceTouchscreen
) {
166 return !touchscreen_tap_suppression_controller_
.FilterTapEvent(
175 void GestureEventQueue::QueueAndForwardIfNecessary(
176 const GestureEventWithLatencyInfo
& gesture_event
) {
177 switch (gesture_event
.event
.type
) {
178 case WebInputEvent::GestureFlingCancel
:
179 fling_in_progress_
= false;
181 case WebInputEvent::GestureFlingStart
:
182 fling_in_progress_
= true;
184 case WebInputEvent::GesturePinchUpdate
:
185 case WebInputEvent::GestureScrollUpdate
:
186 QueueScrollOrPinchAndForwardIfNecessary(gesture_event
);
192 coalesced_gesture_events_
.push_back(gesture_event
);
193 if (coalesced_gesture_events_
.size() == 1)
194 client_
->SendGestureEventImmediately(gesture_event
);
197 void GestureEventQueue::ProcessGestureAck(InputEventAckState ack_result
,
198 WebInputEvent::Type type
,
199 const ui::LatencyInfo
& latency
) {
200 TRACE_EVENT0("input", "GestureEventQueue::ProcessGestureAck");
202 if (coalesced_gesture_events_
.empty()) {
203 DLOG(ERROR
) << "Received unexpected ACK for event type " << type
;
207 // It's possible that the ack for the second event in an in-flight, coalesced
208 // Gesture{Scroll,Pinch}Update pair is received prior to the first event ack.
209 // TODO(jdduke): Unify GSU/GPU pairs into a single event, crbug.com/359115.
210 size_t event_index
= 0;
211 if (ignore_next_ack_
&&
212 coalesced_gesture_events_
.size() > 1 &&
213 coalesced_gesture_events_
[0].event
.type
!= type
&&
214 coalesced_gesture_events_
[1].event
.type
== type
) {
217 GestureEventWithLatencyInfo event_with_latency
=
218 coalesced_gesture_events_
[event_index
];
219 DCHECK_EQ(event_with_latency
.event
.type
, type
);
220 event_with_latency
.latency
.AddNewLatencyFrom(latency
);
222 // Ack'ing an event may enqueue additional gesture events. By ack'ing the
223 // event before the forwarding of queued events below, such additional events
224 // can be coalesced with existing queued events prior to dispatch.
225 client_
->OnGestureEventAck(event_with_latency
, ack_result
);
227 const bool processed
= (INPUT_EVENT_ACK_STATE_CONSUMED
== ack_result
);
228 if (type
== WebInputEvent::GestureFlingCancel
) {
229 if (event_with_latency
.event
.sourceDevice
==
230 blink::WebGestureDeviceTouchscreen
)
231 touchscreen_tap_suppression_controller_
.GestureFlingCancelAck(processed
);
233 touchpad_tap_suppression_controller_
.GestureFlingCancelAck(processed
);
235 DCHECK_LT(event_index
, coalesced_gesture_events_
.size());
236 coalesced_gesture_events_
.erase(coalesced_gesture_events_
.begin() +
239 if (ignore_next_ack_
) {
240 ignore_next_ack_
= false;
244 if (coalesced_gesture_events_
.empty())
247 const GestureEventWithLatencyInfo
& first_gesture_event
=
248 coalesced_gesture_events_
.front();
250 // TODO(jdduke): Unify GSU/GPU pairs into a single event, crbug.com/359115.
251 // Check for the coupled GesturePinchUpdate before sending either event,
252 // handling the case where the first GestureScrollUpdate ack is synchronous.
253 GestureEventWithLatencyInfo second_gesture_event
;
254 if (first_gesture_event
.event
.type
== WebInputEvent::GestureScrollUpdate
&&
255 coalesced_gesture_events_
.size() > 1 &&
256 coalesced_gesture_events_
[1].event
.type
==
257 WebInputEvent::GesturePinchUpdate
) {
258 second_gesture_event
= coalesced_gesture_events_
[1];
259 ignore_next_ack_
= true;
262 client_
->SendGestureEventImmediately(first_gesture_event
);
263 if (second_gesture_event
.event
.type
!= WebInputEvent::Undefined
)
264 client_
->SendGestureEventImmediately(second_gesture_event
);
267 TouchpadTapSuppressionController
*
268 GestureEventQueue::GetTouchpadTapSuppressionController() {
269 return &touchpad_tap_suppression_controller_
;
272 void GestureEventQueue::FlingHasBeenHalted() {
273 fling_in_progress_
= false;
276 void GestureEventQueue::ForwardGestureEvent(
277 const GestureEventWithLatencyInfo
& gesture_event
) {
278 QueueAndForwardIfNecessary(gesture_event
);
281 void GestureEventQueue::SendScrollEndingEventsNow() {
282 scrolling_in_progress_
= false;
283 if (debouncing_deferral_queue_
.empty())
285 GestureQueue debouncing_deferral_queue
;
286 debouncing_deferral_queue
.swap(debouncing_deferral_queue_
);
287 for (GestureQueue::const_iterator it
= debouncing_deferral_queue
.begin();
288 it
!= debouncing_deferral_queue
.end(); it
++) {
289 if (ShouldForwardForGFCFiltering(*it
) &&
290 ShouldForwardForTapSuppression(*it
)) {
291 QueueAndForwardIfNecessary(*it
);
296 void GestureEventQueue::QueueScrollOrPinchAndForwardIfNecessary(
297 const GestureEventWithLatencyInfo
& gesture_event
) {
298 DCHECK_GE(coalesced_gesture_events_
.size(), EventsInFlightCount());
299 const size_t unsent_events_count
=
300 coalesced_gesture_events_
.size() - EventsInFlightCount();
301 if (!unsent_events_count
) {
302 coalesced_gesture_events_
.push_back(gesture_event
);
303 if (coalesced_gesture_events_
.size() == 1) {
304 client_
->SendGestureEventImmediately(gesture_event
);
305 } else if (coalesced_gesture_events_
.size() == 2) {
306 DCHECK(!ignore_next_ack_
);
307 // If there is an in-flight scroll, the new pinch can be forwarded
308 // immediately, avoiding a potential frame delay between the two
309 // (similarly for an in-flight pinch with a new scroll).
310 const GestureEventWithLatencyInfo
& first_event
=
311 coalesced_gesture_events_
.front();
312 if (gesture_event
.event
.type
!= first_event
.event
.type
&&
313 IsCompatibleScrollorPinch(gesture_event
, first_event
)) {
314 ignore_next_ack_
= true;
315 client_
->SendGestureEventImmediately(gesture_event
);
321 GestureEventWithLatencyInfo
* last_event
= &coalesced_gesture_events_
.back();
322 if (last_event
->CanCoalesceWith(gesture_event
)) {
323 last_event
->CoalesceWith(gesture_event
);
327 if (!IsCompatibleScrollorPinch(gesture_event
, *last_event
)) {
328 coalesced_gesture_events_
.push_back(gesture_event
);
332 GestureEventWithLatencyInfo scroll_event
;
333 GestureEventWithLatencyInfo pinch_event
;
334 scroll_event
.event
.modifiers
|= gesture_event
.event
.modifiers
;
335 scroll_event
.event
.sourceDevice
= gesture_event
.event
.sourceDevice
;
336 scroll_event
.event
.timeStampSeconds
= gesture_event
.event
.timeStampSeconds
;
337 // Keep the oldest LatencyInfo.
338 DCHECK_LE(last_event
->latency
.trace_id(), gesture_event
.latency
.trace_id());
339 scroll_event
.latency
= last_event
->latency
;
340 pinch_event
= scroll_event
;
341 scroll_event
.event
.type
= WebInputEvent::GestureScrollUpdate
;
342 pinch_event
.event
.type
= WebInputEvent::GesturePinchUpdate
;
343 pinch_event
.event
.x
= gesture_event
.event
.type
==
344 WebInputEvent::GesturePinchUpdate
?
345 gesture_event
.event
.x
: last_event
->event
.x
;
346 pinch_event
.event
.y
= gesture_event
.event
.type
==
347 WebInputEvent::GesturePinchUpdate
?
348 gesture_event
.event
.y
: last_event
->event
.y
;
350 gfx::Transform combined_scroll_pinch
= GetTransformForEvent(*last_event
);
351 // Only include the second-to-last event in the coalesced pair if it exists
352 // and can be combined with the new event.
353 if (unsent_events_count
> 1) {
354 const GestureEventWithLatencyInfo
& second_last_event
=
355 coalesced_gesture_events_
[coalesced_gesture_events_
.size() - 2];
356 if (IsCompatibleScrollorPinch(gesture_event
, second_last_event
)) {
357 // Keep the oldest LatencyInfo.
358 DCHECK_LE(second_last_event
.latency
.trace_id(),
359 scroll_event
.latency
.trace_id());
360 scroll_event
.latency
= second_last_event
.latency
;
361 pinch_event
.latency
= second_last_event
.latency
;
362 combined_scroll_pinch
.PreconcatTransform(
363 GetTransformForEvent(second_last_event
));
364 coalesced_gesture_events_
.pop_back();
367 combined_scroll_pinch
.ConcatTransform(GetTransformForEvent(gesture_event
));
368 coalesced_gesture_events_
.pop_back();
370 float combined_scale
=
371 SkMScalarToFloat(combined_scroll_pinch
.matrix().get(0, 0));
372 float combined_scroll_pinch_x
=
373 SkMScalarToFloat(combined_scroll_pinch
.matrix().get(0, 3));
374 float combined_scroll_pinch_y
=
375 SkMScalarToFloat(combined_scroll_pinch
.matrix().get(1, 3));
376 scroll_event
.event
.data
.scrollUpdate
.deltaX
=
377 (combined_scroll_pinch_x
+ pinch_event
.event
.x
) / combined_scale
-
379 scroll_event
.event
.data
.scrollUpdate
.deltaY
=
380 (combined_scroll_pinch_y
+ pinch_event
.event
.y
) / combined_scale
-
382 coalesced_gesture_events_
.push_back(scroll_event
);
383 pinch_event
.event
.data
.pinchUpdate
.scale
= combined_scale
;
384 coalesced_gesture_events_
.push_back(pinch_event
);
387 size_t GestureEventQueue::EventsInFlightCount() const {
388 if (coalesced_gesture_events_
.empty())
391 if (!ignore_next_ack_
)
394 DCHECK_GT(coalesced_gesture_events_
.size(), 1U);
398 } // namespace content