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/debug/trace_event.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
;
19 GestureEventQueue::Config::Config() {
22 GestureEventQueue::GestureEventQueue(
23 GestureEventQueueClient
* client
,
24 TouchpadTapSuppressionControllerClient
* touchpad_client
,
27 fling_in_progress_(false),
28 scrolling_in_progress_(false),
29 ignore_next_ack_(false),
30 touchpad_tap_suppression_controller_(
32 config
.touchpad_tap_suppression_config
),
33 touchscreen_tap_suppression_controller_(
35 config
.touchscreen_tap_suppression_config
),
36 debounce_interval_(config
.debounce_interval
) {
38 DCHECK(touchpad_client
);
41 GestureEventQueue::~GestureEventQueue() { }
43 bool GestureEventQueue::ShouldDiscardFlingCancelEvent(
44 const GestureEventWithLatencyInfo
& gesture_event
) const {
45 if (coalesced_gesture_events_
.empty() && fling_in_progress_
)
47 GestureQueue::const_reverse_iterator it
=
48 coalesced_gesture_events_
.rbegin();
49 while (it
!= coalesced_gesture_events_
.rend()) {
50 if (it
->event
.type
== WebInputEvent::GestureFlingStart
)
52 if (it
->event
.type
== WebInputEvent::GestureFlingCancel
)
59 bool GestureEventQueue::ShouldForwardForBounceReduction(
60 const GestureEventWithLatencyInfo
& gesture_event
) {
61 if (debounce_interval_
<= base::TimeDelta())
63 switch (gesture_event
.event
.type
) {
64 case WebInputEvent::GestureScrollUpdate
:
65 if (!scrolling_in_progress_
) {
66 debounce_deferring_timer_
.Start(
70 &GestureEventQueue::SendScrollEndingEventsNow
);
72 // Extend the bounce interval.
73 debounce_deferring_timer_
.Reset();
75 scrolling_in_progress_
= true;
76 debouncing_deferral_queue_
.clear();
78 case WebInputEvent::GesturePinchBegin
:
79 case WebInputEvent::GesturePinchEnd
:
80 case WebInputEvent::GesturePinchUpdate
:
81 // TODO(rjkroege): Debounce pinch (http://crbug.com/147647)
84 if (scrolling_in_progress_
) {
85 debouncing_deferral_queue_
.push_back(gesture_event
);
92 // NOTE: The filters are applied successively. This simplifies the change.
93 bool GestureEventQueue::ShouldForward(
94 const GestureEventWithLatencyInfo
& gesture_event
) {
95 TRACE_EVENT0("input", "GestureEventQueue::ShouldForward");
96 return ShouldForwardForBounceReduction(gesture_event
) &&
97 ShouldForwardForGFCFiltering(gesture_event
) &&
98 ShouldForwardForTapSuppression(gesture_event
) &&
99 ShouldForwardForCoalescing(gesture_event
);
102 bool GestureEventQueue::ShouldForwardForGFCFiltering(
103 const GestureEventWithLatencyInfo
& gesture_event
) const {
104 return gesture_event
.event
.type
!= WebInputEvent::GestureFlingCancel
||
105 !ShouldDiscardFlingCancelEvent(gesture_event
);
108 bool GestureEventQueue::ShouldForwardForTapSuppression(
109 const GestureEventWithLatencyInfo
& gesture_event
) {
110 switch (gesture_event
.event
.type
) {
111 case WebInputEvent::GestureFlingCancel
:
112 if (gesture_event
.event
.sourceDevice
==
113 blink::WebGestureDeviceTouchscreen
)
114 touchscreen_tap_suppression_controller_
.GestureFlingCancel();
116 touchpad_tap_suppression_controller_
.GestureFlingCancel();
118 case WebInputEvent::GestureTapDown
:
119 case WebInputEvent::GestureShowPress
:
120 case WebInputEvent::GestureTapUnconfirmed
:
121 case WebInputEvent::GestureTapCancel
:
122 case WebInputEvent::GestureTap
:
123 case WebInputEvent::GestureDoubleTap
:
124 if (gesture_event
.event
.sourceDevice
==
125 blink::WebGestureDeviceTouchscreen
) {
126 return !touchscreen_tap_suppression_controller_
.FilterTapEvent(
135 bool GestureEventQueue::ShouldForwardForCoalescing(
136 const GestureEventWithLatencyInfo
& gesture_event
) {
137 switch (gesture_event
.event
.type
) {
138 case WebInputEvent::GestureFlingCancel
:
139 fling_in_progress_
= false;
141 case WebInputEvent::GestureFlingStart
:
142 fling_in_progress_
= true;
144 case WebInputEvent::GesturePinchUpdate
:
145 case WebInputEvent::GestureScrollUpdate
:
146 MergeOrInsertScrollAndPinchEvent(gesture_event
);
147 return ShouldHandleEventNow();
151 coalesced_gesture_events_
.push_back(gesture_event
);
152 return ShouldHandleEventNow();
155 void GestureEventQueue::ProcessGestureAck(InputEventAckState ack_result
,
156 WebInputEvent::Type type
,
157 const ui::LatencyInfo
& latency
) {
158 TRACE_EVENT0("input", "GestureEventQueue::ProcessGestureAck");
160 if (coalesced_gesture_events_
.empty()) {
161 DLOG(ERROR
) << "Received unexpected ACK for event type " << type
;
165 // It's possible that the ack for the second event in an in-flight, coalesced
166 // Gesture{Scroll,Pinch}Update pair is received prior to the first event ack.
167 // TODO(jdduke): Unify GSU/GPU pairs into a single event, crbug.com/359115.
168 size_t event_index
= 0;
169 if (ignore_next_ack_
&&
170 coalesced_gesture_events_
.size() > 1 &&
171 coalesced_gesture_events_
[0].event
.type
!= type
&&
172 coalesced_gesture_events_
[1].event
.type
== type
) {
175 GestureEventWithLatencyInfo event_with_latency
=
176 coalesced_gesture_events_
[event_index
];
177 DCHECK_EQ(event_with_latency
.event
.type
, type
);
178 event_with_latency
.latency
.AddNewLatencyFrom(latency
);
180 // Ack'ing an event may enqueue additional gesture events. By ack'ing the
181 // event before the forwarding of queued events below, such additional events
182 // can be coalesced with existing queued events prior to dispatch.
183 client_
->OnGestureEventAck(event_with_latency
, ack_result
);
185 const bool processed
= (INPUT_EVENT_ACK_STATE_CONSUMED
== ack_result
);
186 if (type
== WebInputEvent::GestureFlingCancel
) {
187 if (event_with_latency
.event
.sourceDevice
==
188 blink::WebGestureDeviceTouchscreen
)
189 touchscreen_tap_suppression_controller_
.GestureFlingCancelAck(processed
);
191 touchpad_tap_suppression_controller_
.GestureFlingCancelAck(processed
);
193 DCHECK_LT(event_index
, coalesced_gesture_events_
.size());
194 coalesced_gesture_events_
.erase(coalesced_gesture_events_
.begin() +
197 if (ignore_next_ack_
) {
198 ignore_next_ack_
= false;
202 if (coalesced_gesture_events_
.empty())
205 const GestureEventWithLatencyInfo
& first_gesture_event
=
206 coalesced_gesture_events_
.front();
208 // TODO(jdduke): Unify GSU/GPU pairs into a single event, crbug.com/359115.
209 // Check for the coupled GesturePinchUpdate before sending either event,
210 // handling the case where the first GestureScrollUpdate ack is synchronous.
211 GestureEventWithLatencyInfo second_gesture_event
;
212 if (first_gesture_event
.event
.type
== WebInputEvent::GestureScrollUpdate
&&
213 coalesced_gesture_events_
.size() > 1 &&
214 coalesced_gesture_events_
[1].event
.type
==
215 WebInputEvent::GesturePinchUpdate
) {
216 second_gesture_event
= coalesced_gesture_events_
[1];
217 ignore_next_ack_
= true;
220 client_
->SendGestureEventImmediately(first_gesture_event
);
221 if (second_gesture_event
.event
.type
!= WebInputEvent::Undefined
)
222 client_
->SendGestureEventImmediately(second_gesture_event
);
225 TouchpadTapSuppressionController
*
226 GestureEventQueue::GetTouchpadTapSuppressionController() {
227 return &touchpad_tap_suppression_controller_
;
230 bool GestureEventQueue::ExpectingGestureAck() const {
231 return !coalesced_gesture_events_
.empty();
234 void GestureEventQueue::FlingHasBeenHalted() {
235 fling_in_progress_
= false;
238 bool GestureEventQueue::ShouldHandleEventNow() const {
239 return coalesced_gesture_events_
.size() == 1;
242 void GestureEventQueue::ForwardGestureEvent(
243 const GestureEventWithLatencyInfo
& gesture_event
) {
244 if (ShouldForwardForCoalescing(gesture_event
))
245 client_
->SendGestureEventImmediately(gesture_event
);
248 void GestureEventQueue::SendScrollEndingEventsNow() {
249 scrolling_in_progress_
= false;
250 if (debouncing_deferral_queue_
.empty())
252 GestureQueue debouncing_deferral_queue
;
253 debouncing_deferral_queue
.swap(debouncing_deferral_queue_
);
254 for (GestureQueue::const_iterator it
= debouncing_deferral_queue
.begin();
255 it
!= debouncing_deferral_queue
.end(); it
++) {
256 if (ShouldForwardForGFCFiltering(*it
) &&
257 ShouldForwardForTapSuppression(*it
) &&
258 ShouldForwardForCoalescing(*it
)) {
259 client_
->SendGestureEventImmediately(*it
);
264 void GestureEventQueue::MergeOrInsertScrollAndPinchEvent(
265 const GestureEventWithLatencyInfo
& gesture_event
) {
266 const size_t unsent_events_count
=
267 coalesced_gesture_events_
.size() - EventsInFlightCount();
268 if (!unsent_events_count
) {
269 coalesced_gesture_events_
.push_back(gesture_event
);
273 GestureEventWithLatencyInfo
* last_event
= &coalesced_gesture_events_
.back();
274 if (last_event
->CanCoalesceWith(gesture_event
)) {
275 last_event
->CoalesceWith(gesture_event
);
279 if (!ShouldTryMerging(gesture_event
, *last_event
)) {
280 coalesced_gesture_events_
.push_back(gesture_event
);
284 GestureEventWithLatencyInfo scroll_event
;
285 GestureEventWithLatencyInfo pinch_event
;
286 scroll_event
.event
.modifiers
|= gesture_event
.event
.modifiers
;
287 scroll_event
.event
.sourceDevice
= gesture_event
.event
.sourceDevice
;
288 scroll_event
.event
.timeStampSeconds
= gesture_event
.event
.timeStampSeconds
;
289 // Keep the oldest LatencyInfo.
290 DCHECK_LE(last_event
->latency
.trace_id
, gesture_event
.latency
.trace_id
);
291 scroll_event
.latency
= last_event
->latency
;
292 pinch_event
= scroll_event
;
293 scroll_event
.event
.type
= WebInputEvent::GestureScrollUpdate
;
294 pinch_event
.event
.type
= WebInputEvent::GesturePinchUpdate
;
295 pinch_event
.event
.x
= gesture_event
.event
.type
==
296 WebInputEvent::GesturePinchUpdate
?
297 gesture_event
.event
.x
: last_event
->event
.x
;
298 pinch_event
.event
.y
= gesture_event
.event
.type
==
299 WebInputEvent::GesturePinchUpdate
?
300 gesture_event
.event
.y
: last_event
->event
.y
;
302 gfx::Transform combined_scroll_pinch
= GetTransformForEvent(*last_event
);
303 // Only include the second-to-last event in the coalesced pair if it exists
304 // and can be combined with the new event.
305 if (unsent_events_count
> 1) {
306 const GestureEventWithLatencyInfo
& second_last_event
=
307 coalesced_gesture_events_
[coalesced_gesture_events_
.size() - 2];
308 if (ShouldTryMerging(gesture_event
, second_last_event
)) {
309 // Keep the oldest LatencyInfo.
310 DCHECK_LE(second_last_event
.latency
.trace_id
,
311 scroll_event
.latency
.trace_id
);
312 scroll_event
.latency
= second_last_event
.latency
;
313 pinch_event
.latency
= second_last_event
.latency
;
314 combined_scroll_pinch
.PreconcatTransform(
315 GetTransformForEvent(second_last_event
));
316 coalesced_gesture_events_
.pop_back();
319 combined_scroll_pinch
.ConcatTransform(GetTransformForEvent(gesture_event
));
320 coalesced_gesture_events_
.pop_back();
322 float combined_scale
=
323 SkMScalarToFloat(combined_scroll_pinch
.matrix().get(0, 0));
324 float combined_scroll_pinch_x
=
325 SkMScalarToFloat(combined_scroll_pinch
.matrix().get(0, 3));
326 float combined_scroll_pinch_y
=
327 SkMScalarToFloat(combined_scroll_pinch
.matrix().get(1, 3));
328 scroll_event
.event
.data
.scrollUpdate
.deltaX
=
329 (combined_scroll_pinch_x
+ pinch_event
.event
.x
) / combined_scale
-
331 scroll_event
.event
.data
.scrollUpdate
.deltaY
=
332 (combined_scroll_pinch_y
+ pinch_event
.event
.y
) / combined_scale
-
334 coalesced_gesture_events_
.push_back(scroll_event
);
335 pinch_event
.event
.data
.pinchUpdate
.scale
= combined_scale
;
336 coalesced_gesture_events_
.push_back(pinch_event
);
339 bool GestureEventQueue::ShouldTryMerging(
340 const GestureEventWithLatencyInfo
& new_event
,
341 const GestureEventWithLatencyInfo
& event_in_queue
) const {
343 new_event
.event
.timeStampSeconds
<
344 event_in_queue
.event
.timeStampSeconds
)
345 << "Event time not monotonic?\n";
346 return (event_in_queue
.event
.type
== WebInputEvent::GestureScrollUpdate
||
347 event_in_queue
.event
.type
== WebInputEvent::GesturePinchUpdate
) &&
348 event_in_queue
.event
.modifiers
== new_event
.event
.modifiers
&&
349 event_in_queue
.event
.sourceDevice
== new_event
.event
.sourceDevice
;
352 gfx::Transform
GestureEventQueue::GetTransformForEvent(
353 const GestureEventWithLatencyInfo
& gesture_event
) const {
354 gfx::Transform gesture_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 size_t GestureEventQueue::EventsInFlightCount() const {
368 if (coalesced_gesture_events_
.empty())
371 if (!ignore_next_ack_
)
374 DCHECK_GT(coalesced_gesture_events_
.size(), 1U);
378 } // namespace content