1 // Copyright 2013 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_filter.h" 6 7 #include "base/command_line.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 namespace { 19 20 // Default debouncing interval duration: if a scroll is in progress, non-scroll 21 // events during this interval are deferred to either its end or discarded on 22 // receipt of another GestureScrollUpdate. 23 static const int kDebouncingIntervalTimeMs = 30; 24 25 } // namespace 26 27 GestureEventFilter::GestureEventFilter( 28 GestureEventFilterClient* client, 29 TouchpadTapSuppressionControllerClient* touchpad_client) 30 : client_(client), 31 fling_in_progress_(false), 32 scrolling_in_progress_(false), 33 ignore_next_ack_(false), 34 combined_scroll_pinch_(gfx::Transform()), 35 touchpad_tap_suppression_controller_( 36 new TouchpadTapSuppressionController(touchpad_client)), 37 touchscreen_tap_suppression_controller_( 38 new TouchscreenTapSuppressionController(this)), 39 debounce_interval_time_ms_(kDebouncingIntervalTimeMs), 40 debounce_enabled_(true) { 41 DCHECK(client); 42 DCHECK(touchpad_tap_suppression_controller_); 43 if (CommandLine::ForCurrentProcess()->HasSwitch( 44 switches::kDisableGestureDebounce)) { 45 debounce_enabled_ = false; 46 } 47 } 48 49 GestureEventFilter::~GestureEventFilter() { } 50 51 bool GestureEventFilter::ShouldDiscardFlingCancelEvent( 52 const GestureEventWithLatencyInfo& gesture_event) const { 53 if (coalesced_gesture_events_.empty() && fling_in_progress_) 54 return false; 55 GestureEventQueue::const_reverse_iterator it = 56 coalesced_gesture_events_.rbegin(); 57 while (it != coalesced_gesture_events_.rend()) { 58 if (it->event.type == WebInputEvent::GestureFlingStart) 59 return false; 60 if (it->event.type == WebInputEvent::GestureFlingCancel) 61 return true; 62 it++; 63 } 64 return true; 65 } 66 67 bool GestureEventFilter::ShouldForwardForBounceReduction( 68 const GestureEventWithLatencyInfo& gesture_event) { 69 if (!debounce_enabled_) 70 return true; 71 switch (gesture_event.event.type) { 72 case WebInputEvent::GestureScrollUpdate: 73 if (!scrolling_in_progress_) { 74 debounce_deferring_timer_.Start( 75 FROM_HERE, 76 base::TimeDelta::FromMilliseconds(debounce_interval_time_ms_), 77 this, 78 &GestureEventFilter::SendScrollEndingEventsNow); 79 } else { 80 // Extend the bounce interval. 81 debounce_deferring_timer_.Reset(); 82 } 83 scrolling_in_progress_ = true; 84 debouncing_deferral_queue_.clear(); 85 return true; 86 case WebInputEvent::GesturePinchBegin: 87 case WebInputEvent::GesturePinchEnd: 88 case WebInputEvent::GesturePinchUpdate: 89 // TODO(rjkroege): Debounce pinch (http://crbug.com/147647) 90 return true; 91 default: 92 if (scrolling_in_progress_) { 93 debouncing_deferral_queue_.push_back(gesture_event); 94 return false; 95 } 96 return true; 97 } 98 99 NOTREACHED(); 100 return false; 101 } 102 103 // NOTE: The filters are applied successively. This simplifies the change. 104 bool GestureEventFilter::ShouldForward( 105 const GestureEventWithLatencyInfo& gesture_event) { 106 return ShouldForwardForZeroVelocityFlingStart(gesture_event) && 107 ShouldForwardForBounceReduction(gesture_event) && 108 ShouldForwardForGFCFiltering(gesture_event) && 109 ShouldForwardForTapSuppression(gesture_event) && 110 ShouldForwardForCoalescing(gesture_event); 111 } 112 113 bool GestureEventFilter::ShouldForwardForZeroVelocityFlingStart( 114 const GestureEventWithLatencyInfo& gesture_event) const { 115 return gesture_event.event.type != WebInputEvent::GestureFlingStart || 116 gesture_event.event.sourceDevice != WebGestureEvent::Touchpad || 117 gesture_event.event.data.flingStart.velocityX != 0 || 118 gesture_event.event.data.flingStart.velocityY != 0; 119 } 120 121 bool GestureEventFilter::ShouldForwardForGFCFiltering( 122 const GestureEventWithLatencyInfo& gesture_event) const { 123 return gesture_event.event.type != WebInputEvent::GestureFlingCancel || 124 !ShouldDiscardFlingCancelEvent(gesture_event); 125 } 126 127 bool GestureEventFilter::ShouldForwardForTapSuppression( 128 const GestureEventWithLatencyInfo& gesture_event) { 129 switch (gesture_event.event.type) { 130 case WebInputEvent::GestureFlingCancel: 131 if (gesture_event.event.sourceDevice == WebGestureEvent::Touchscreen) 132 touchscreen_tap_suppression_controller_->GestureFlingCancel(); 133 else 134 touchpad_tap_suppression_controller_->GestureFlingCancel(); 135 return true; 136 case WebInputEvent::GestureTapDown: 137 return !touchscreen_tap_suppression_controller_-> 138 ShouldDeferGestureTapDown(gesture_event); 139 case WebInputEvent::GestureShowPress: 140 return !touchscreen_tap_suppression_controller_-> 141 ShouldDeferGestureShowPress(gesture_event); 142 case WebInputEvent::GestureTapCancel: 143 case WebInputEvent::GestureTap: 144 case WebInputEvent::GestureTapUnconfirmed: 145 case WebInputEvent::GestureDoubleTap: 146 return !touchscreen_tap_suppression_controller_-> 147 ShouldSuppressGestureTapEnd(); 148 default: 149 return true; 150 } 151 NOTREACHED(); 152 return false; 153 } 154 155 bool GestureEventFilter::ShouldForwardForCoalescing( 156 const GestureEventWithLatencyInfo& gesture_event) { 157 switch (gesture_event.event.type) { 158 case WebInputEvent::GestureFlingCancel: 159 fling_in_progress_ = false; 160 break; 161 case WebInputEvent::GestureFlingStart: 162 fling_in_progress_ = true; 163 break; 164 case WebInputEvent::GesturePinchUpdate: 165 case WebInputEvent::GestureScrollUpdate: 166 MergeOrInsertScrollAndPinchEvent(gesture_event); 167 return ShouldHandleEventNow(); 168 default: 169 break; 170 } 171 EnqueueEvent(gesture_event); 172 return ShouldHandleEventNow(); 173 } 174 175 void GestureEventFilter::ProcessGestureAck(InputEventAckState ack_result, 176 WebInputEvent::Type type, 177 const ui::LatencyInfo& latency) { 178 if (coalesced_gesture_events_.empty()) { 179 DLOG(ERROR) << "Received unexpected ACK for event type " << type; 180 return; 181 } 182 183 // Ack'ing an event may enqueue additional gesture events. By ack'ing the 184 // event before the forwarding of queued events below, such additional events 185 // can be coalesced with existing queued events prior to dispatch. 186 GestureEventWithLatencyInfo event_with_latency = 187 coalesced_gesture_events_.front(); 188 DCHECK_EQ(event_with_latency.event.type, type); 189 event_with_latency.latency.AddNewLatencyFrom(latency); 190 client_->OnGestureEventAck(event_with_latency, ack_result); 191 192 const bool processed = (INPUT_EVENT_ACK_STATE_CONSUMED == ack_result); 193 if (type == WebInputEvent::GestureFlingCancel) { 194 if (event_with_latency.event.sourceDevice == WebGestureEvent::Touchscreen) 195 touchscreen_tap_suppression_controller_->GestureFlingCancelAck(processed); 196 else 197 touchpad_tap_suppression_controller_->GestureFlingCancelAck(processed); 198 } 199 coalesced_gesture_events_.pop_front(); 200 201 if (ignore_next_ack_) { 202 ignore_next_ack_ = false; 203 return; 204 } 205 206 if (coalesced_gesture_events_.empty()) 207 return; 208 209 const GestureEventWithLatencyInfo& first_gesture_event = 210 coalesced_gesture_events_.front(); 211 212 // TODO(yusufo): Introduce GesturePanScroll so that these can be combined 213 // into one gesture and kept inside the queue that way. 214 // Check for the coupled GesturePinchUpdate before sending either event, 215 // handling the case where the first GestureScrollUpdate ack is synchronous. 216 GestureEventWithLatencyInfo second_gesture_event; 217 if (first_gesture_event.event.type == WebInputEvent::GestureScrollUpdate && 218 coalesced_gesture_events_.size() > 1 && 219 coalesced_gesture_events_[1].event.type == 220 WebInputEvent::GesturePinchUpdate) { 221 second_gesture_event = coalesced_gesture_events_[1]; 222 ignore_next_ack_ = true; 223 } 224 225 client_->SendGestureEventImmediately(first_gesture_event); 226 if (second_gesture_event.event.type != WebInputEvent::Undefined) 227 client_->SendGestureEventImmediately(second_gesture_event); 228 } 229 230 TouchpadTapSuppressionController* 231 GestureEventFilter::GetTouchpadTapSuppressionController() { 232 return touchpad_tap_suppression_controller_.get(); 233 } 234 235 bool GestureEventFilter::HasQueuedGestureEvents() const { 236 return !coalesced_gesture_events_.empty(); 237 } 238 239 void GestureEventFilter::FlingHasBeenHalted() { 240 fling_in_progress_ = false; 241 } 242 243 bool GestureEventFilter::ShouldHandleEventNow() const { 244 return coalesced_gesture_events_.size() == 1; 245 } 246 247 void GestureEventFilter::ForwardGestureEvent( 248 const GestureEventWithLatencyInfo& gesture_event) { 249 if (ShouldForwardForCoalescing(gesture_event)) 250 client_->SendGestureEventImmediately(gesture_event); 251 } 252 253 void GestureEventFilter::SendScrollEndingEventsNow() { 254 scrolling_in_progress_ = false; 255 GestureEventQueue debouncing_deferral_queue; 256 debouncing_deferral_queue.swap(debouncing_deferral_queue_); 257 for (GestureEventQueue::const_iterator it = debouncing_deferral_queue.begin(); 258 it != debouncing_deferral_queue.end(); it++) { 259 if (ShouldForwardForGFCFiltering(*it) && 260 ShouldForwardForTapSuppression(*it) && 261 ShouldForwardForCoalescing(*it)) { 262 client_->SendGestureEventImmediately(*it); 263 } 264 } 265 } 266 267 void GestureEventFilter::MergeOrInsertScrollAndPinchEvent( 268 const GestureEventWithLatencyInfo& gesture_event) { 269 if (coalesced_gesture_events_.size() <= 1) { 270 EnqueueEvent(gesture_event); 271 return; 272 } 273 GestureEventWithLatencyInfo* last_event = &coalesced_gesture_events_.back(); 274 if (last_event->CanCoalesceWith(gesture_event)) { 275 last_event->CoalesceWith(gesture_event); 276 if (!combined_scroll_pinch_.IsIdentity()) { 277 combined_scroll_pinch_.ConcatTransform( 278 GetTransformForEvent(gesture_event)); 279 } 280 return; 281 } 282 if (coalesced_gesture_events_.size() == 2 || 283 (coalesced_gesture_events_.size() == 3 && ignore_next_ack_) || 284 !ShouldTryMerging(gesture_event, *last_event)) { 285 EnqueueEvent(gesture_event); 286 return; 287 } 288 GestureEventWithLatencyInfo scroll_event; 289 GestureEventWithLatencyInfo pinch_event; 290 scroll_event.event.modifiers |= gesture_event.event.modifiers; 291 scroll_event.event.timeStampSeconds = gesture_event.event.timeStampSeconds; 292 // Keep the oldest LatencyInfo. 293 DCHECK_LE(last_event->latency.trace_id, gesture_event.latency.trace_id); 294 scroll_event.latency = last_event->latency; 295 pinch_event = scroll_event; 296 scroll_event.event.type = WebInputEvent::GestureScrollUpdate; 297 pinch_event.event.type = WebInputEvent::GesturePinchUpdate; 298 pinch_event.event.x = gesture_event.event.type == 299 WebInputEvent::GesturePinchUpdate ? 300 gesture_event.event.x : last_event->event.x; 301 pinch_event.event.y = gesture_event.event.type == 302 WebInputEvent::GesturePinchUpdate ? 303 gesture_event.event.y : last_event->event.y; 304 305 combined_scroll_pinch_.ConcatTransform(GetTransformForEvent(gesture_event)); 306 GestureEventWithLatencyInfo* second_last_event = &coalesced_gesture_events_ 307 [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 coalesced_gesture_events_.pop_back(); 315 } else { 316 DCHECK(combined_scroll_pinch_ == GetTransformForEvent(gesture_event)); 317 combined_scroll_pinch_. 318 PreconcatTransform(GetTransformForEvent(*last_event)); 319 } 320 coalesced_gesture_events_.pop_back(); 321 float combined_scale = 322 SkMScalarToFloat(combined_scroll_pinch_.matrix().get(0, 0)); 323 float combined_scroll_pinch_x = 324 SkMScalarToFloat(combined_scroll_pinch_.matrix().get(0, 3)); 325 float combined_scroll_pinch_y = 326 SkMScalarToFloat(combined_scroll_pinch_.matrix().get(1, 3)); 327 scroll_event.event.data.scrollUpdate.deltaX = 328 (combined_scroll_pinch_x + pinch_event.event.x) / combined_scale - 329 pinch_event.event.x; 330 scroll_event.event.data.scrollUpdate.deltaY = 331 (combined_scroll_pinch_y + pinch_event.event.y) / combined_scale - 332 pinch_event.event.y; 333 coalesced_gesture_events_.push_back(scroll_event); 334 pinch_event.event.data.pinchUpdate.scale = combined_scale; 335 coalesced_gesture_events_.push_back(pinch_event); 336 } 337 338 bool GestureEventFilter::ShouldTryMerging( 339 const GestureEventWithLatencyInfo& new_event, 340 const GestureEventWithLatencyInfo& event_in_queue) const { 341 DLOG_IF(WARNING, 342 new_event.event.timeStampSeconds < 343 event_in_queue.event.timeStampSeconds) 344 << "Event time not monotonic?\n"; 345 return (event_in_queue.event.type == WebInputEvent::GestureScrollUpdate || 346 event_in_queue.event.type == WebInputEvent::GesturePinchUpdate) && 347 event_in_queue.event.modifiers == new_event.event.modifiers; 348 } 349 350 gfx::Transform GestureEventFilter::GetTransformForEvent( 351 const GestureEventWithLatencyInfo& gesture_event) const { 352 gfx::Transform gesture_transform = gfx::Transform(); 353 if (gesture_event.event.type == WebInputEvent::GestureScrollUpdate) { 354 gesture_transform.Translate(gesture_event.event.data.scrollUpdate.deltaX, 355 gesture_event.event.data.scrollUpdate.deltaY); 356 } else if (gesture_event.event.type == WebInputEvent::GesturePinchUpdate) { 357 float scale = gesture_event.event.data.pinchUpdate.scale; 358 gesture_transform.Translate(-gesture_event.event.x, -gesture_event.event.y); 359 gesture_transform.Scale(scale,scale); 360 gesture_transform.Translate(gesture_event.event.x, gesture_event.event.y); 361 } 362 return gesture_transform; 363 } 364 365 void GestureEventFilter::EnqueueEvent( 366 const GestureEventWithLatencyInfo& gesture_event) { 367 coalesced_gesture_events_.push_back(gesture_event); 368 // Scroll and pinch events contributing to |combined_scroll_pinch_| will be 369 // manually added to the queue in |MergeOrInsertScrollAndPinchEvent()|. 370 combined_scroll_pinch_ = gfx::Transform(); 371 } 372 373 } // namespace content 374