Add ICU message format support
[chromium-blink-merge.git] / content / browser / renderer_host / input / gesture_event_queue.cc
blobc52bbdb53f3fed38ae26c45bf69ad4852375995f
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() {
60 GestureEventQueue::GestureEventQueue(
61 GestureEventQueueClient* client,
62 TouchpadTapSuppressionControllerClient* touchpad_client,
63 const Config& config)
64 : client_(client),
65 fling_in_progress_(false),
66 scrolling_in_progress_(false),
67 ignore_next_ack_(false),
68 touchpad_tap_suppression_controller_(
69 touchpad_client,
70 config.touchpad_tap_suppression_config),
71 touchscreen_tap_suppression_controller_(
72 this,
73 config.touchscreen_tap_suppression_config),
74 debounce_interval_(config.debounce_interval) {
75 DCHECK(client);
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)) {
87 return;
90 QueueAndForwardIfNecessary(gesture_event);
93 bool GestureEventQueue::ShouldDiscardFlingCancelEvent(
94 const GestureEventWithLatencyInfo& gesture_event) const {
95 if (coalesced_gesture_events_.empty() && fling_in_progress_)
96 return false;
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)
101 return false;
102 if (it->event.type == WebInputEvent::GestureFlingCancel)
103 return true;
104 it++;
106 return true;
109 bool GestureEventQueue::ShouldForwardForBounceReduction(
110 const GestureEventWithLatencyInfo& gesture_event) {
111 if (debounce_interval_ <= base::TimeDelta())
112 return true;
113 switch (gesture_event.event.type) {
114 case WebInputEvent::GestureScrollUpdate:
115 if (!scrolling_in_progress_) {
116 debounce_deferring_timer_.Start(
117 FROM_HERE,
118 debounce_interval_,
119 this,
120 &GestureEventQueue::SendScrollEndingEventsNow);
121 } else {
122 // Extend the bounce interval.
123 debounce_deferring_timer_.Reset();
125 scrolling_in_progress_ = true;
126 debouncing_deferral_queue_.clear();
127 return true;
128 case WebInputEvent::GesturePinchBegin:
129 case WebInputEvent::GesturePinchEnd:
130 case WebInputEvent::GesturePinchUpdate:
131 // TODO(rjkroege): Debounce pinch (http://crbug.com/147647)
132 return true;
133 default:
134 if (scrolling_in_progress_) {
135 debouncing_deferral_queue_.push_back(gesture_event);
136 return false;
138 return true;
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();
155 else
156 touchpad_tap_suppression_controller_.GestureFlingCancel();
157 return true;
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(
167 gesture_event);
169 return true;
170 default:
171 return true;
175 void GestureEventQueue::QueueAndForwardIfNecessary(
176 const GestureEventWithLatencyInfo& gesture_event) {
177 switch (gesture_event.event.type) {
178 case WebInputEvent::GestureFlingCancel:
179 fling_in_progress_ = false;
180 break;
181 case WebInputEvent::GestureFlingStart:
182 fling_in_progress_ = true;
183 break;
184 case WebInputEvent::GesturePinchUpdate:
185 case WebInputEvent::GestureScrollUpdate:
186 QueueScrollOrPinchAndForwardIfNecessary(gesture_event);
187 return;
188 default:
189 break;
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;
204 return;
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) {
215 event_index = 1;
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);
232 else
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() +
237 event_index);
239 if (ignore_next_ack_) {
240 ignore_next_ack_ = false;
241 return;
244 if (coalesced_gesture_events_.empty())
245 return;
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())
284 return;
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);
318 return;
321 GestureEventWithLatencyInfo* last_event = &coalesced_gesture_events_.back();
322 if (last_event->CanCoalesceWith(gesture_event)) {
323 last_event->CoalesceWith(gesture_event);
324 return;
327 if (!IsCompatibleScrollorPinch(gesture_event, *last_event)) {
328 coalesced_gesture_events_.push_back(gesture_event);
329 return;
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 -
378 pinch_event.event.x;
379 scroll_event.event.data.scrollUpdate.deltaY =
380 (combined_scroll_pinch_y + pinch_event.event.y) / combined_scale -
381 pinch_event.event.y;
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())
389 return 0;
391 if (!ignore_next_ack_)
392 return 1;
394 DCHECK_GT(coalesced_gesture_events_.size(), 1U);
395 return 2;
398 } // namespace content