Re-subimission of https://codereview.chromium.org/1041213003/
[chromium-blink-merge.git] / content / browser / renderer_host / input / gesture_event_queue.cc
blob6aa926926ccc55cc6c258e6bf7117d36518b1b2c
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;
14 namespace content {
15 namespace {
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);
48 } else {
49 NOTREACHED() << "Invalid event type for transform retrieval: "
50 << WebInputEventTraits::GetName(gesture_event.event.type);
52 return gesture_transform;
55 } // namespace
57 GestureEventQueue::Config::Config() : enable_fling_cancel_filtering(true) {
60 GestureEventQueue::GestureEventQueue(
61 GestureEventQueueClient* client,
62 TouchpadTapSuppressionControllerClient* touchpad_client,
63 const Config& config)
64 : client_(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_(
70 touchpad_client,
71 config.touchpad_tap_suppression_config),
72 touchscreen_tap_suppression_controller_(
73 this,
74 config.touchscreen_tap_suppression_config),
75 debounce_interval_(config.debounce_interval) {
76 DCHECK(client);
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)) {
88 return;
91 QueueAndForwardIfNecessary(gesture_event);
94 bool GestureEventQueue::ShouldDiscardFlingCancelEvent(
95 const GestureEventWithLatencyInfo& gesture_event) const {
96 if (!enable_fling_cancel_filtering_)
97 return false;
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)
103 return false;
104 if (it->event.type == WebInputEvent::GestureFlingCancel)
105 return true;
106 it++;
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())
116 return true;
117 switch (gesture_event.event.type) {
118 case WebInputEvent::GestureScrollUpdate:
119 if (!scrolling_in_progress_) {
120 debounce_deferring_timer_.Start(
121 FROM_HERE,
122 debounce_interval_,
123 this,
124 &GestureEventQueue::SendScrollEndingEventsNow);
125 } else {
126 // Extend the bounce interval.
127 debounce_deferring_timer_.Reset();
129 scrolling_in_progress_ = true;
130 debouncing_deferral_queue_.clear();
131 return true;
132 case WebInputEvent::GesturePinchBegin:
133 case WebInputEvent::GesturePinchEnd:
134 case WebInputEvent::GesturePinchUpdate:
135 // TODO(rjkroege): Debounce pinch (http://crbug.com/147647)
136 return true;
137 default:
138 if (scrolling_in_progress_) {
139 debouncing_deferral_queue_.push_back(gesture_event);
140 return false;
142 return true;
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();
159 else
160 touchpad_tap_suppression_controller_.GestureFlingCancel();
161 return true;
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(
171 gesture_event);
173 return true;
174 default:
175 return true;
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);
185 return;
186 default:
187 break;
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;
202 return;
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) {
218 event_index = 1;
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);
235 else
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() +
240 event_index);
242 if (ignore_next_ack_) {
243 ignore_next_ack_ = false;
244 return;
247 if (coalesced_gesture_events_.empty())
248 return;
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())
288 return;
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);
322 return;
325 GestureEventWithLatencyInfo* last_event = &coalesced_gesture_events_.back();
326 if (last_event->CanCoalesceWith(gesture_event)) {
327 last_event->CoalesceWith(gesture_event);
328 return;
331 if (!IsCompatibleScrollorPinch(gesture_event, *last_event)) {
332 coalesced_gesture_events_.push_back(gesture_event);
333 return;
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 -
382 pinch_event.event.x;
383 scroll_event.event.data.scrollUpdate.deltaY =
384 (combined_scroll_pinch_y + pinch_event.event.y) / combined_scale -
385 pinch_event.event.y;
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())
393 return 0;
395 if (!ignore_next_ack_)
396 return 1;
398 DCHECK_GT(coalesced_gesture_events_.size(), 1U);
399 return 2;
402 } // namespace content