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 WebKit::WebGestureEvent; 15 using WebKit::WebInputEvent; 16 17 namespace content { 18 namespace { 19 20 // Default maximum time between the GestureRecognizer generating a 21 // GestureTapDown and when it is forwarded to the renderer. 22 #if !defined(OS_ANDROID) 23 static const int kTapDownDeferralTimeMs = 150; 24 #else 25 // Android OS sends this gesture with a delay already. 26 static const int kTapDownDeferralTimeMs = 0; 27 #endif 28 29 // Default debouncing interval duration: if a scroll is in progress, non-scroll 30 // events during this interval are deferred to either its end or discarded on 31 // receipt of another GestureScrollUpdate. 32 static const int kDebouncingIntervalTimeMs = 30; 33 34 // Sets |*value| to |switchKey| if it exists or sets it to |defaultValue|. 35 static void GetParamHelper(int* value, 36 int defaultValue, 37 const char switchKey[]) { 38 if (*value < 0) { 39 *value = defaultValue; 40 CommandLine* command_line = CommandLine::ForCurrentProcess(); 41 std::string command_line_param = 42 command_line->GetSwitchValueASCII(switchKey); 43 if (!command_line_param.empty()) { 44 int v; 45 if (base::StringToInt(command_line_param, &v)) 46 *value = v; 47 } 48 DCHECK_GE(*value, 0); 49 } 50 } 51 52 static int GetTapDownDeferralTimeMs() { 53 static int tap_down_deferral_time_window = -1; 54 GetParamHelper(&tap_down_deferral_time_window, 55 kTapDownDeferralTimeMs, 56 switches::kTapDownDeferralTimeMs); 57 return tap_down_deferral_time_window; 58 } 59 } // namespace 60 61 GestureEventFilter::GestureEventFilter(InputRouter* input_router) 62 : input_router_(input_router), 63 fling_in_progress_(false), 64 scrolling_in_progress_(false), 65 ignore_next_ack_(false), 66 combined_scroll_pinch_(gfx::Transform()), 67 touchpad_tap_suppression_controller_( 68 new TouchpadTapSuppressionController(input_router)), 69 touchscreen_tap_suppression_controller_( 70 new TouchscreenTapSuppressionController(this)), 71 maximum_tap_gap_time_ms_(GetTapDownDeferralTimeMs()), 72 debounce_interval_time_ms_(kDebouncingIntervalTimeMs) { 73 } 74 75 GestureEventFilter::~GestureEventFilter() { } 76 77 bool GestureEventFilter::ShouldDiscardFlingCancelEvent( 78 const GestureEventWithLatencyInfo& gesture_event) const { 79 if (coalesced_gesture_events_.empty() && fling_in_progress_) 80 return false; 81 GestureEventQueue::const_reverse_iterator it = 82 coalesced_gesture_events_.rbegin(); 83 while (it != coalesced_gesture_events_.rend()) { 84 if (it->event.type == WebInputEvent::GestureFlingStart) 85 return false; 86 if (it->event.type == WebInputEvent::GestureFlingCancel) 87 return true; 88 it++; 89 } 90 return true; 91 } 92 93 bool GestureEventFilter::ShouldForwardForBounceReduction( 94 const GestureEventWithLatencyInfo& gesture_event) { 95 if (debounce_interval_time_ms_ == 0) 96 return true; 97 switch (gesture_event.event.type) { 98 case WebInputEvent::GestureScrollUpdate: 99 if (!scrolling_in_progress_) { 100 debounce_deferring_timer_.Start( 101 FROM_HERE, 102 base::TimeDelta::FromMilliseconds(debounce_interval_time_ms_), 103 this, 104 &GestureEventFilter::SendScrollEndingEventsNow); 105 } else { 106 // Extend the bounce interval. 107 debounce_deferring_timer_.Reset(); 108 } 109 scrolling_in_progress_ = true; 110 debouncing_deferral_queue_.clear(); 111 return true; 112 case WebInputEvent::GesturePinchBegin: 113 case WebInputEvent::GesturePinchEnd: 114 case WebInputEvent::GesturePinchUpdate: 115 // TODO(rjkroege): Debounce pinch (http://crbug.com/147647) 116 return true; 117 default: 118 if (scrolling_in_progress_) { 119 debouncing_deferral_queue_.push_back(gesture_event); 120 return false; 121 } 122 return true; 123 } 124 125 NOTREACHED(); 126 return false; 127 } 128 129 // NOTE: The filters are applied successively. This simplifies the change. 130 bool GestureEventFilter::ShouldForward( 131 const GestureEventWithLatencyInfo& gesture_event) { 132 return ShouldForwardForZeroVelocityFlingStart(gesture_event) && 133 ShouldForwardForBounceReduction(gesture_event) && 134 ShouldForwardForGFCFiltering(gesture_event) && 135 ShouldForwardForTapSuppression(gesture_event) && 136 ShouldForwardForTapDeferral(gesture_event) && 137 ShouldForwardForCoalescing(gesture_event); 138 } 139 140 bool GestureEventFilter::ShouldForwardForZeroVelocityFlingStart( 141 const GestureEventWithLatencyInfo& gesture_event) const { 142 return gesture_event.event.type != WebInputEvent::GestureFlingStart || 143 gesture_event.event.sourceDevice != WebGestureEvent::Touchpad || 144 gesture_event.event.data.flingStart.velocityX != 0 || 145 gesture_event.event.data.flingStart.velocityY != 0; 146 } 147 148 bool GestureEventFilter::ShouldForwardForGFCFiltering( 149 const GestureEventWithLatencyInfo& gesture_event) const { 150 return gesture_event.event.type != WebInputEvent::GestureFlingCancel || 151 !ShouldDiscardFlingCancelEvent(gesture_event); 152 } 153 154 bool GestureEventFilter::ShouldForwardForTapSuppression( 155 const GestureEventWithLatencyInfo& gesture_event) { 156 switch (gesture_event.event.type) { 157 case WebInputEvent::GestureFlingCancel: 158 if (gesture_event.event.sourceDevice == WebGestureEvent::Touchscreen) 159 touchscreen_tap_suppression_controller_->GestureFlingCancel(); 160 else 161 touchpad_tap_suppression_controller_->GestureFlingCancel(); 162 return true; 163 case WebInputEvent::GestureTapDown: 164 return !touchscreen_tap_suppression_controller_-> 165 ShouldDeferGestureTapDown(gesture_event); 166 case WebInputEvent::GestureTapCancel: 167 return !touchscreen_tap_suppression_controller_-> 168 ShouldSuppressGestureTapCancel(); 169 case WebInputEvent::GestureTap: 170 case WebInputEvent::GestureTapUnconfirmed: 171 return !touchscreen_tap_suppression_controller_-> 172 ShouldSuppressGestureTap(); 173 default: 174 return true; 175 } 176 NOTREACHED(); 177 return false; 178 } 179 180 bool GestureEventFilter::ShouldForwardForTapDeferral( 181 const GestureEventWithLatencyInfo& gesture_event) { 182 switch (gesture_event.event.type) { 183 case WebInputEvent::GestureTapDown: 184 // GestureTapDown is always paired with either a Tap, or TapCancel, so it 185 // should be impossible to have more than one outstanding at a time. 186 DCHECK_EQ(deferred_tap_down_event_.event.type, WebInputEvent::Undefined); 187 deferred_tap_down_event_ = gesture_event; 188 send_gtd_timer_.Start( 189 FROM_HERE, 190 base::TimeDelta::FromMilliseconds(maximum_tap_gap_time_ms_), 191 this, 192 &GestureEventFilter::SendGestureTapDownNow); 193 return false; 194 case WebInputEvent::GestureTapCancel: 195 if (deferred_tap_down_event_.event.type == WebInputEvent::Undefined) { 196 // The TapDown has already been put in the queue, must send the 197 // corresponding TapCancel as well. 198 return true; 199 } 200 // Cancelling a deferred TapDown, just drop them on the floor. 201 send_gtd_timer_.Stop(); 202 deferred_tap_down_event_.event.type = WebInputEvent::Undefined; 203 return false; 204 case WebInputEvent::GestureTap: 205 send_gtd_timer_.Stop(); 206 if (deferred_tap_down_event_.event.type != WebInputEvent::Undefined) { 207 ForwardGestureEventSkipDeferral(deferred_tap_down_event_); 208 deferred_tap_down_event_.event.type = WebInputEvent::Undefined; 209 } 210 return true; 211 case WebInputEvent::GestureFlingStart: 212 case WebInputEvent::GestureScrollBegin: 213 case WebInputEvent::GesturePinchBegin: 214 send_gtd_timer_.Stop(); 215 deferred_tap_down_event_.event.type = WebInputEvent::Undefined; 216 return true; 217 default: 218 return true; 219 } 220 221 NOTREACHED(); 222 return true; 223 } 224 225 bool GestureEventFilter::ShouldForwardForCoalescing( 226 const GestureEventWithLatencyInfo& gesture_event) { 227 switch (gesture_event.event.type) { 228 case WebInputEvent::GestureFlingCancel: 229 fling_in_progress_ = false; 230 break; 231 case WebInputEvent::GestureFlingStart: 232 fling_in_progress_ = true; 233 break; 234 case WebInputEvent::GesturePinchUpdate: 235 case WebInputEvent::GestureScrollUpdate: 236 MergeOrInsertScrollAndPinchEvent(gesture_event); 237 return ShouldHandleEventNow(); 238 default: 239 break; 240 } 241 coalesced_gesture_events_.push_back(gesture_event); 242 return ShouldHandleEventNow(); 243 } 244 245 void GestureEventFilter::ProcessGestureAck(bool processed, int type) { 246 if (coalesced_gesture_events_.empty()) { 247 DLOG(ERROR) << "Received unexpected ACK for event type " << type; 248 return; 249 } 250 DCHECK_EQ(coalesced_gesture_events_.front().event.type, type); 251 if (type == WebInputEvent::GestureFlingCancel) { 252 if (coalesced_gesture_events_.front().event.sourceDevice == 253 WebGestureEvent::Touchscreen) 254 touchscreen_tap_suppression_controller_->GestureFlingCancelAck(processed); 255 else 256 touchpad_tap_suppression_controller_->GestureFlingCancelAck(processed); 257 } 258 coalesced_gesture_events_.pop_front(); 259 if (ignore_next_ack_) { 260 ignore_next_ack_ = false; 261 } else if (!coalesced_gesture_events_.empty()) { 262 const GestureEventWithLatencyInfo& next_gesture_event = 263 coalesced_gesture_events_.front(); 264 input_router_->SendGestureEventImmediately(next_gesture_event); 265 // TODO(yusufo): Introduce GesturePanScroll so that these can be combined 266 // into one gesture and kept inside the queue that way. 267 if (coalesced_gesture_events_.size() > 1) { 268 const GestureEventWithLatencyInfo& second_gesture_event = 269 coalesced_gesture_events_[1]; 270 if (next_gesture_event.event.type == 271 WebInputEvent::GestureScrollUpdate && 272 second_gesture_event.event.type == 273 WebInputEvent::GesturePinchUpdate) { 274 input_router_->SendGestureEventImmediately(second_gesture_event); 275 ignore_next_ack_ = true; 276 combined_scroll_pinch_ = gfx::Transform(); 277 } 278 } 279 } 280 } 281 282 TouchpadTapSuppressionController* 283 GestureEventFilter::GetTouchpadTapSuppressionController() { 284 return touchpad_tap_suppression_controller_.get(); 285 } 286 287 bool GestureEventFilter::HasQueuedGestureEvents() const { 288 return !coalesced_gesture_events_.empty(); 289 } 290 291 const WebKit::WebGestureEvent& 292 GestureEventFilter::GetGestureEventAwaitingAck() const { 293 DCHECK(!coalesced_gesture_events_.empty()); 294 if (!ignore_next_ack_) 295 return coalesced_gesture_events_.front().event; 296 else 297 return coalesced_gesture_events_.at(1).event; 298 } 299 300 void GestureEventFilter::FlingHasBeenHalted() { 301 fling_in_progress_ = false; 302 } 303 304 bool GestureEventFilter::ShouldHandleEventNow() const { 305 return coalesced_gesture_events_.size() == 1; 306 } 307 308 void GestureEventFilter::ForwardGestureEventForDeferral( 309 const GestureEventWithLatencyInfo& gesture_event) { 310 if (ShouldForwardForTapDeferral(gesture_event)) 311 ForwardGestureEventSkipDeferral(gesture_event); 312 } 313 314 void GestureEventFilter::ForwardGestureEventSkipDeferral( 315 const GestureEventWithLatencyInfo& gesture_event) { 316 if (ShouldForwardForCoalescing(gesture_event)) 317 input_router_->SendGestureEventImmediately(gesture_event); 318 } 319 320 void GestureEventFilter::SendGestureTapDownNow() { 321 // We must not have already sent the deferred TapDown (if we did, we would 322 // have stopped the timer, which prevents this task from running - even if 323 // it's time had already elapsed). 324 DCHECK_EQ(deferred_tap_down_event_.event.type, WebInputEvent::GestureTapDown); 325 ForwardGestureEventSkipDeferral(deferred_tap_down_event_); 326 deferred_tap_down_event_.event.type = WebInputEvent::Undefined; 327 } 328 329 void GestureEventFilter::SendScrollEndingEventsNow() { 330 scrolling_in_progress_ = false; 331 for (GestureEventQueue::const_iterator it = 332 debouncing_deferral_queue_.begin(); 333 it != debouncing_deferral_queue_.end(); it++) { 334 if (ShouldForwardForGFCFiltering(*it) && 335 ShouldForwardForTapSuppression(*it) && 336 ShouldForwardForTapDeferral(*it) && 337 ShouldForwardForCoalescing(*it)) { 338 input_router_->SendGestureEventImmediately(*it); 339 } 340 } 341 debouncing_deferral_queue_.clear(); 342 } 343 344 void GestureEventFilter::MergeOrInsertScrollAndPinchEvent( 345 const GestureEventWithLatencyInfo& gesture_event) { 346 if (coalesced_gesture_events_.size() <= 1) { 347 coalesced_gesture_events_.push_back(gesture_event); 348 return; 349 } 350 GestureEventWithLatencyInfo* last_event = &coalesced_gesture_events_.back(); 351 if (gesture_event.event.type == WebInputEvent::GestureScrollUpdate && 352 last_event->event.type == WebInputEvent::GestureScrollUpdate && 353 last_event->event.modifiers == gesture_event.event.modifiers) { 354 last_event->event.data.scrollUpdate.deltaX += 355 gesture_event.event.data.scrollUpdate.deltaX; 356 last_event->event.data.scrollUpdate.deltaY += 357 gesture_event.event.data.scrollUpdate.deltaY; 358 last_event->latency.MergeWith(gesture_event.latency); 359 return; 360 } 361 if (coalesced_gesture_events_.size() == 2 || 362 (coalesced_gesture_events_.size() == 3 && ignore_next_ack_) || 363 !ShouldTryMerging(gesture_event, *last_event)) { 364 coalesced_gesture_events_.push_back(gesture_event); 365 return; 366 } 367 GestureEventWithLatencyInfo scroll_event; 368 GestureEventWithLatencyInfo pinch_event; 369 scroll_event.event.modifiers |= gesture_event.event.modifiers; 370 scroll_event.event.timeStampSeconds = gesture_event.event.timeStampSeconds; 371 scroll_event.latency = gesture_event.latency; 372 scroll_event.latency.MergeWith(last_event->latency); 373 pinch_event = scroll_event; 374 scroll_event.event.type = WebInputEvent::GestureScrollUpdate; 375 pinch_event.event.type = WebInputEvent::GesturePinchUpdate; 376 pinch_event.event.x = gesture_event.event.type == 377 WebInputEvent::GesturePinchUpdate ? 378 gesture_event.event.x : last_event->event.x; 379 pinch_event.event.y = gesture_event.event.type == 380 WebInputEvent::GesturePinchUpdate ? 381 gesture_event.event.y : last_event->event.y; 382 383 combined_scroll_pinch_.ConcatTransform(GetTransformForEvent(gesture_event)); 384 GestureEventWithLatencyInfo* second_last_event = &coalesced_gesture_events_ 385 [coalesced_gesture_events_.size() - 2]; 386 if (ShouldTryMerging(gesture_event, *second_last_event)) { 387 scroll_event.latency.MergeWith(second_last_event->latency); 388 pinch_event.latency.MergeWith(second_last_event->latency); 389 coalesced_gesture_events_.pop_back(); 390 } else { 391 DCHECK(combined_scroll_pinch_ == GetTransformForEvent(gesture_event)); 392 combined_scroll_pinch_. 393 PreconcatTransform(GetTransformForEvent(*last_event)); 394 } 395 coalesced_gesture_events_.pop_back(); 396 float combined_scale = combined_scroll_pinch_.matrix().getDouble(0, 0); 397 scroll_event.event.data.scrollUpdate.deltaX = 398 (combined_scroll_pinch_.matrix().getDouble(0, 3) + pinch_event.event.x) 399 / combined_scale - pinch_event.event.x; 400 scroll_event.event.data.scrollUpdate.deltaY = 401 (combined_scroll_pinch_.matrix().getDouble(1, 3) + pinch_event.event.y) 402 / combined_scale - pinch_event.event.y; 403 coalesced_gesture_events_.push_back(scroll_event); 404 pinch_event.event.data.pinchUpdate.scale = combined_scale; 405 coalesced_gesture_events_.push_back(pinch_event); 406 } 407 408 bool GestureEventFilter::ShouldTryMerging( 409 const GestureEventWithLatencyInfo& new_event, 410 const GestureEventWithLatencyInfo& event_in_queue) const { 411 DLOG_IF(WARNING, 412 new_event.event.timeStampSeconds < 413 event_in_queue.event.timeStampSeconds) 414 << "Event time not monotonic?\n"; 415 return (event_in_queue.event.type == WebInputEvent::GestureScrollUpdate || 416 event_in_queue.event.type == WebInputEvent::GesturePinchUpdate) && 417 event_in_queue.event.modifiers == new_event.event.modifiers; 418 } 419 420 gfx::Transform GestureEventFilter::GetTransformForEvent( 421 const GestureEventWithLatencyInfo& gesture_event) const { 422 gfx::Transform gesture_transform = gfx::Transform(); 423 if (gesture_event.event.type == WebInputEvent::GestureScrollUpdate) { 424 gesture_transform.Translate(gesture_event.event.data.scrollUpdate.deltaX, 425 gesture_event.event.data.scrollUpdate.deltaY); 426 } else if (gesture_event.event.type == WebInputEvent::GesturePinchUpdate) { 427 float scale = gesture_event.event.data.pinchUpdate.scale; 428 gesture_transform.Translate(-gesture_event.event.x, -gesture_event.event.y); 429 gesture_transform.Scale(scale,scale); 430 gesture_transform.Translate(gesture_event.event.x, gesture_event.event.y); 431 } 432 return gesture_transform; 433 } 434 } // namespace content 435