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.
4
5 #include "content/browser/renderer_host/input/gesture_event_queue.h"
6
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"
13
14 using blink::WebGestureEvent;
15 using blink::WebInputEvent;
16
17 namespace content {
18
Config()19 GestureEventQueue::Config::Config() {
20 }
21
GestureEventQueue(GestureEventQueueClient * client,TouchpadTapSuppressionControllerClient * touchpad_client,const Config & config)22 GestureEventQueue::GestureEventQueue(
23 GestureEventQueueClient* client,
24 TouchpadTapSuppressionControllerClient* touchpad_client,
25 const Config& config)
26 : client_(client),
27 fling_in_progress_(false),
28 scrolling_in_progress_(false),
29 ignore_next_ack_(false),
30 touchpad_tap_suppression_controller_(
31 touchpad_client,
32 config.touchpad_tap_suppression_config),
33 touchscreen_tap_suppression_controller_(
34 this,
35 config.touchscreen_tap_suppression_config),
36 debounce_interval_(config.debounce_interval) {
37 DCHECK(client);
38 DCHECK(touchpad_client);
39 }
40
~GestureEventQueue()41 GestureEventQueue::~GestureEventQueue() { }
42
ShouldDiscardFlingCancelEvent(const GestureEventWithLatencyInfo & gesture_event) const43 bool GestureEventQueue::ShouldDiscardFlingCancelEvent(
44 const GestureEventWithLatencyInfo& gesture_event) const {
45 if (coalesced_gesture_events_.empty() && fling_in_progress_)
46 return false;
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)
51 return false;
52 if (it->event.type == WebInputEvent::GestureFlingCancel)
53 return true;
54 it++;
55 }
56 return true;
57 }
58
ShouldForwardForBounceReduction(const GestureEventWithLatencyInfo & gesture_event)59 bool GestureEventQueue::ShouldForwardForBounceReduction(
60 const GestureEventWithLatencyInfo& gesture_event) {
61 if (debounce_interval_ <= base::TimeDelta())
62 return true;
63 switch (gesture_event.event.type) {
64 case WebInputEvent::GestureScrollUpdate:
65 if (!scrolling_in_progress_) {
66 debounce_deferring_timer_.Start(
67 FROM_HERE,
68 debounce_interval_,
69 this,
70 &GestureEventQueue::SendScrollEndingEventsNow);
71 } else {
72 // Extend the bounce interval.
73 debounce_deferring_timer_.Reset();
74 }
75 scrolling_in_progress_ = true;
76 debouncing_deferral_queue_.clear();
77 return true;
78 case WebInputEvent::GesturePinchBegin:
79 case WebInputEvent::GesturePinchEnd:
80 case WebInputEvent::GesturePinchUpdate:
81 // TODO(rjkroege): Debounce pinch (http://crbug.com/147647)
82 return true;
83 default:
84 if (scrolling_in_progress_) {
85 debouncing_deferral_queue_.push_back(gesture_event);
86 return false;
87 }
88 return true;
89 }
90 }
91
92 // NOTE: The filters are applied successively. This simplifies the change.
ShouldForward(const GestureEventWithLatencyInfo & gesture_event)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);
100 }
101
ShouldForwardForGFCFiltering(const GestureEventWithLatencyInfo & gesture_event) const102 bool GestureEventQueue::ShouldForwardForGFCFiltering(
103 const GestureEventWithLatencyInfo& gesture_event) const {
104 return gesture_event.event.type != WebInputEvent::GestureFlingCancel ||
105 !ShouldDiscardFlingCancelEvent(gesture_event);
106 }
107
ShouldForwardForTapSuppression(const GestureEventWithLatencyInfo & 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();
115 else
116 touchpad_tap_suppression_controller_.GestureFlingCancel();
117 return true;
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(
127 gesture_event);
128 }
129 return true;
130 default:
131 return true;
132 }
133 }
134
ShouldForwardForCoalescing(const GestureEventWithLatencyInfo & gesture_event)135 bool GestureEventQueue::ShouldForwardForCoalescing(
136 const GestureEventWithLatencyInfo& gesture_event) {
137 switch (gesture_event.event.type) {
138 case WebInputEvent::GestureFlingCancel:
139 fling_in_progress_ = false;
140 break;
141 case WebInputEvent::GestureFlingStart:
142 fling_in_progress_ = true;
143 break;
144 case WebInputEvent::GesturePinchUpdate:
145 case WebInputEvent::GestureScrollUpdate:
146 MergeOrInsertScrollAndPinchEvent(gesture_event);
147 return ShouldHandleEventNow();
148 default:
149 break;
150 }
151 coalesced_gesture_events_.push_back(gesture_event);
152 return ShouldHandleEventNow();
153 }
154
ProcessGestureAck(InputEventAckState ack_result,WebInputEvent::Type type,const ui::LatencyInfo & latency)155 void GestureEventQueue::ProcessGestureAck(InputEventAckState ack_result,
156 WebInputEvent::Type type,
157 const ui::LatencyInfo& latency) {
158 TRACE_EVENT0("input", "GestureEventQueue::ProcessGestureAck");
159
160 if (coalesced_gesture_events_.empty()) {
161 DLOG(ERROR) << "Received unexpected ACK for event type " << type;
162 return;
163 }
164
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) {
173 event_index = 1;
174 }
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);
179
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);
184
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);
190 else
191 touchpad_tap_suppression_controller_.GestureFlingCancelAck(processed);
192 }
193 DCHECK_LT(event_index, coalesced_gesture_events_.size());
194 coalesced_gesture_events_.erase(coalesced_gesture_events_.begin() +
195 event_index);
196
197 if (ignore_next_ack_) {
198 ignore_next_ack_ = false;
199 return;
200 }
201
202 if (coalesced_gesture_events_.empty())
203 return;
204
205 const GestureEventWithLatencyInfo& first_gesture_event =
206 coalesced_gesture_events_.front();
207
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;
218 }
219
220 client_->SendGestureEventImmediately(first_gesture_event);
221 if (second_gesture_event.event.type != WebInputEvent::Undefined)
222 client_->SendGestureEventImmediately(second_gesture_event);
223 }
224
225 TouchpadTapSuppressionController*
GetTouchpadTapSuppressionController()226 GestureEventQueue::GetTouchpadTapSuppressionController() {
227 return &touchpad_tap_suppression_controller_;
228 }
229
ExpectingGestureAck() const230 bool GestureEventQueue::ExpectingGestureAck() const {
231 return !coalesced_gesture_events_.empty();
232 }
233
FlingHasBeenHalted()234 void GestureEventQueue::FlingHasBeenHalted() {
235 fling_in_progress_ = false;
236 }
237
ShouldHandleEventNow() const238 bool GestureEventQueue::ShouldHandleEventNow() const {
239 return coalesced_gesture_events_.size() == 1;
240 }
241
ForwardGestureEvent(const GestureEventWithLatencyInfo & gesture_event)242 void GestureEventQueue::ForwardGestureEvent(
243 const GestureEventWithLatencyInfo& gesture_event) {
244 if (ShouldForwardForCoalescing(gesture_event))
245 client_->SendGestureEventImmediately(gesture_event);
246 }
247
SendScrollEndingEventsNow()248 void GestureEventQueue::SendScrollEndingEventsNow() {
249 scrolling_in_progress_ = false;
250 if (debouncing_deferral_queue_.empty())
251 return;
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);
260 }
261 }
262 }
263
MergeOrInsertScrollAndPinchEvent(const GestureEventWithLatencyInfo & gesture_event)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);
270 return;
271 }
272
273 GestureEventWithLatencyInfo* last_event = &coalesced_gesture_events_.back();
274 if (last_event->CanCoalesceWith(gesture_event)) {
275 last_event->CoalesceWith(gesture_event);
276 return;
277 }
278
279 if (!ShouldTryMerging(gesture_event, *last_event)) {
280 coalesced_gesture_events_.push_back(gesture_event);
281 return;
282 }
283
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;
301
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();
317 }
318 }
319 combined_scroll_pinch.ConcatTransform(GetTransformForEvent(gesture_event));
320 coalesced_gesture_events_.pop_back();
321
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 -
330 pinch_event.event.x;
331 scroll_event.event.data.scrollUpdate.deltaY =
332 (combined_scroll_pinch_y + pinch_event.event.y) / combined_scale -
333 pinch_event.event.y;
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);
337 }
338
ShouldTryMerging(const GestureEventWithLatencyInfo & new_event,const GestureEventWithLatencyInfo & event_in_queue) const339 bool GestureEventQueue::ShouldTryMerging(
340 const GestureEventWithLatencyInfo& new_event,
341 const GestureEventWithLatencyInfo& event_in_queue) const {
342 DLOG_IF(WARNING,
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;
350 }
351
GetTransformForEvent(const GestureEventWithLatencyInfo & gesture_event) const352 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);
363 }
364 return gesture_transform;
365 }
366
EventsInFlightCount() const367 size_t GestureEventQueue::EventsInFlightCount() const {
368 if (coalesced_gesture_events_.empty())
369 return 0;
370
371 if (!ignore_next_ack_)
372 return 1;
373
374 DCHECK_GT(coalesced_gesture_events_.size(), 1U);
375 return 2;
376 }
377
378 } // namespace content
379