Battery Status API: add UMA logging for Linux.
[chromium-blink-merge.git] / content / browser / renderer_host / input / gesture_event_queue.cc
blobb16923be17f7234c5e4d69ceb04979253800eca9
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;
17 namespace content {
19 GestureEventQueue::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);
41 GestureEventQueue::~GestureEventQueue() { }
43 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++;
56 return true;
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();
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;
88 return true;
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();
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);
129 return true;
130 default:
131 return true;
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;
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;
162 return;
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;
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);
190 else
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() +
195 event_index);
197 if (ignore_next_ack_) {
198 ignore_next_ack_ = false;
199 return;
202 if (coalesced_gesture_events_.empty())
203 return;
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())
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);
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;
273 GestureEventWithLatencyInfo* last_event = &coalesced_gesture_events_.back();
274 if (last_event->CanCoalesceWith(gesture_event)) {
275 last_event->CoalesceWith(gesture_event);
276 return;
279 if (!ShouldTryMerging(gesture_event, *last_event)) {
280 coalesced_gesture_events_.push_back(gesture_event);
281 return;
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 -
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);
339 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;
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())
369 return 0;
371 if (!ignore_next_ack_)
372 return 1;
374 DCHECK_GT(coalesced_gesture_events_.size(), 1U);
375 return 2;
378 } // namespace content