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/renderer/input/input_handler_proxy.h" 6 7 #include "base/auto_reset.h" 8 #include "base/debug/trace_event.h" 9 #include "base/logging.h" 10 #include "base/metrics/histogram.h" 11 #include "content/common/input/did_overscroll_params.h" 12 #include "content/common/input/web_input_event_traits.h" 13 #include "content/renderer/input/input_handler_proxy_client.h" 14 #include "third_party/WebKit/public/platform/Platform.h" 15 #include "third_party/WebKit/public/web/WebInputEvent.h" 16 #include "ui/events/latency_info.h" 17 #include "ui/gfx/frame_time.h" 18 #include "ui/gfx/geometry/point_conversions.h" 19 20 using blink::WebFloatPoint; 21 using blink::WebFloatSize; 22 using blink::WebGestureEvent; 23 using blink::WebInputEvent; 24 using blink::WebMouseEvent; 25 using blink::WebMouseWheelEvent; 26 using blink::WebPoint; 27 using blink::WebTouchEvent; 28 using blink::WebTouchPoint; 29 30 namespace { 31 32 // Maximum time between a fling event's timestamp and the first |Animate| call 33 // for the fling curve to use the fling timestamp as the initial animation time. 34 // Two frames allows a minor delay between event creation and the first animate. 35 const double kMaxSecondsFromFlingTimestampToFirstAnimate = 2. / 60.; 36 37 // Threshold for determining whether a fling scroll delta should have caused the 38 // client to scroll. 39 const float kScrollEpsilon = 0.1f; 40 41 // Minimum fling velocity required for the active fling and new fling for the 42 // two to accumulate. 43 const double kMinBoostFlingSpeedSquare = 350. * 350.; 44 45 // Minimum velocity for the active touch scroll to preserve (boost) an active 46 // fling for which cancellation has been deferred. 47 const double kMinBoostTouchScrollSpeedSquare = 150 * 150.; 48 49 // Timeout window after which the active fling will be cancelled if no scrolls 50 // or flings of sufficient velocity relative to the current fling are received. 51 // The default value on Android native views is 40ms, but we use a slightly 52 // increased value to accomodate small IPC message delays. 53 const double kFlingBoostTimeoutDelaySeconds = 0.045; 54 55 gfx::Vector2dF ToClientScrollIncrement(const WebFloatSize& increment) { 56 return gfx::Vector2dF(-increment.width, -increment.height); 57 } 58 59 double InSecondsF(const base::TimeTicks& time) { 60 return (time - base::TimeTicks()).InSecondsF(); 61 } 62 63 bool ShouldSuppressScrollForFlingBoosting( 64 const gfx::Vector2dF& current_fling_velocity, 65 const WebGestureEvent& scroll_update_event, 66 double time_since_last_boost_event) { 67 DCHECK_EQ(WebInputEvent::GestureScrollUpdate, scroll_update_event.type); 68 69 gfx::Vector2dF dx(scroll_update_event.data.scrollUpdate.deltaX, 70 scroll_update_event.data.scrollUpdate.deltaY); 71 if (gfx::DotProduct(current_fling_velocity, dx) < 0) 72 return false; 73 74 if (time_since_last_boost_event < 0.001) 75 return true; 76 77 // TODO(jdduke): Use |scroll_update_event.data.scrollUpdate.velocity{X,Y}|. 78 // The scroll must be of sufficient velocity to maintain the active fling. 79 const gfx::Vector2dF scroll_velocity = 80 gfx::ScaleVector2d(dx, 1. / time_since_last_boost_event); 81 if (scroll_velocity.LengthSquared() < kMinBoostTouchScrollSpeedSquare) 82 return false; 83 84 return true; 85 } 86 87 bool ShouldBoostFling(const gfx::Vector2dF& current_fling_velocity, 88 const WebGestureEvent& fling_start_event) { 89 DCHECK_EQ(WebInputEvent::GestureFlingStart, fling_start_event.type); 90 91 gfx::Vector2dF new_fling_velocity( 92 fling_start_event.data.flingStart.velocityX, 93 fling_start_event.data.flingStart.velocityY); 94 95 if (gfx::DotProduct(current_fling_velocity, new_fling_velocity) < 0) 96 return false; 97 98 if (current_fling_velocity.LengthSquared() < kMinBoostFlingSpeedSquare) 99 return false; 100 101 if (new_fling_velocity.LengthSquared() < kMinBoostFlingSpeedSquare) 102 return false; 103 104 return true; 105 } 106 107 WebGestureEvent ObtainGestureScrollBegin(const WebGestureEvent& event) { 108 WebGestureEvent scroll_begin_event = event; 109 scroll_begin_event.type = WebInputEvent::GestureScrollBegin; 110 scroll_begin_event.data.scrollBegin.deltaXHint = 0; 111 scroll_begin_event.data.scrollBegin.deltaYHint = 0; 112 return scroll_begin_event; 113 } 114 115 void SendScrollLatencyUma(const WebInputEvent& event, 116 const ui::LatencyInfo& latency_info) { 117 if (!(event.type == WebInputEvent::GestureScrollBegin || 118 event.type == WebInputEvent::GestureScrollUpdate || 119 event.type == WebInputEvent::GestureScrollUpdateWithoutPropagation)) 120 return; 121 122 ui::LatencyInfo::LatencyMap::const_iterator it = 123 latency_info.latency_components.find(std::make_pair( 124 ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, 0)); 125 126 if (it == latency_info.latency_components.end()) 127 return; 128 129 base::TimeDelta delta = base::TimeTicks::HighResNow() - it->second.event_time; 130 for (size_t i = 0; i < it->second.event_count; ++i) { 131 UMA_HISTOGRAM_CUSTOM_COUNTS( 132 "Event.Latency.RendererImpl.GestureScroll2", 133 delta.InMicroseconds(), 134 1, 135 1000000, 136 100); 137 } 138 } // namespace 139 140 } 141 142 namespace content { 143 144 InputHandlerProxy::InputHandlerProxy(cc::InputHandler* input_handler, 145 InputHandlerProxyClient* client) 146 : client_(client), 147 input_handler_(input_handler), 148 deferred_fling_cancel_time_seconds_(0), 149 #ifndef NDEBUG 150 expect_scroll_update_end_(false), 151 #endif 152 gesture_scroll_on_impl_thread_(false), 153 gesture_pinch_on_impl_thread_(false), 154 fling_may_be_active_on_main_thread_(false), 155 disallow_horizontal_fling_scroll_(false), 156 disallow_vertical_fling_scroll_(false), 157 has_fling_animation_started_(false) { 158 DCHECK(client); 159 input_handler_->BindToClient(this); 160 } 161 162 InputHandlerProxy::~InputHandlerProxy() {} 163 164 void InputHandlerProxy::WillShutdown() { 165 input_handler_ = NULL; 166 client_->WillShutdown(); 167 } 168 169 InputHandlerProxy::EventDisposition 170 InputHandlerProxy::HandleInputEventWithLatencyInfo( 171 const WebInputEvent& event, 172 ui::LatencyInfo* latency_info) { 173 DCHECK(input_handler_); 174 175 SendScrollLatencyUma(event, *latency_info); 176 177 TRACE_EVENT_FLOW_STEP0( 178 "input", 179 "LatencyInfo.Flow", 180 TRACE_ID_DONT_MANGLE(latency_info->trace_id), 181 "HanldeInputEventImpl"); 182 183 scoped_ptr<cc::SwapPromiseMonitor> latency_info_swap_promise_monitor = 184 input_handler_->CreateLatencyInfoSwapPromiseMonitor(latency_info); 185 InputHandlerProxy::EventDisposition disposition = HandleInputEvent(event); 186 return disposition; 187 } 188 189 InputHandlerProxy::EventDisposition InputHandlerProxy::HandleInputEvent( 190 const WebInputEvent& event) { 191 DCHECK(input_handler_); 192 TRACE_EVENT1("input", "InputHandlerProxy::HandleInputEvent", 193 "type", WebInputEventTraits::GetName(event.type)); 194 195 if (FilterInputEventForFlingBoosting(event)) 196 return DID_HANDLE; 197 198 if (event.type == WebInputEvent::MouseWheel) { 199 const WebMouseWheelEvent& wheel_event = 200 *static_cast<const WebMouseWheelEvent*>(&event); 201 if (wheel_event.scrollByPage) { 202 // TODO(jamesr): We don't properly handle scroll by page in the compositor 203 // thread, so punt it to the main thread. http://crbug.com/236639 204 return DID_NOT_HANDLE; 205 } 206 if (wheel_event.modifiers & WebInputEvent::ControlKey) { 207 // Wheel events involving the control key never trigger scrolling, only 208 // event handlers. Forward to the main thread. 209 return DID_NOT_HANDLE; 210 } 211 cc::InputHandler::ScrollStatus scroll_status = input_handler_->ScrollBegin( 212 gfx::Point(wheel_event.x, wheel_event.y), cc::InputHandler::Wheel); 213 switch (scroll_status) { 214 case cc::InputHandler::ScrollStarted: { 215 TRACE_EVENT_INSTANT2( 216 "input", 217 "InputHandlerProxy::handle_input wheel scroll", 218 TRACE_EVENT_SCOPE_THREAD, 219 "deltaX", 220 -wheel_event.deltaX, 221 "deltaY", 222 -wheel_event.deltaY); 223 bool did_scroll = input_handler_->ScrollBy( 224 gfx::Point(wheel_event.x, wheel_event.y), 225 gfx::Vector2dF(-wheel_event.deltaX, -wheel_event.deltaY)); 226 input_handler_->ScrollEnd(); 227 return did_scroll ? DID_HANDLE : DROP_EVENT; 228 } 229 case cc::InputHandler::ScrollIgnored: 230 // TODO(jamesr): This should be DROP_EVENT, but in cases where we fail 231 // to properly sync scrollability it's safer to send the event to the 232 // main thread. Change back to DROP_EVENT once we have synchronization 233 // bugs sorted out. 234 return DID_NOT_HANDLE; 235 case cc::InputHandler::ScrollUnknown: 236 case cc::InputHandler::ScrollOnMainThread: 237 return DID_NOT_HANDLE; 238 case cc::InputHandler::ScrollStatusCount: 239 NOTREACHED(); 240 break; 241 } 242 } else if (event.type == WebInputEvent::GestureScrollBegin) { 243 DCHECK(!gesture_scroll_on_impl_thread_); 244 #ifndef NDEBUG 245 DCHECK(!expect_scroll_update_end_); 246 expect_scroll_update_end_ = true; 247 #endif 248 const WebGestureEvent& gesture_event = 249 *static_cast<const WebGestureEvent*>(&event); 250 cc::InputHandler::ScrollStatus scroll_status = input_handler_->ScrollBegin( 251 gfx::Point(gesture_event.x, gesture_event.y), 252 cc::InputHandler::Gesture); 253 UMA_HISTOGRAM_ENUMERATION("Renderer4.CompositorScrollHitTestResult", 254 scroll_status, 255 cc::InputHandler::ScrollStatusCount); 256 switch (scroll_status) { 257 case cc::InputHandler::ScrollStarted: 258 TRACE_EVENT_INSTANT0("input", 259 "InputHandlerProxy::handle_input gesture scroll", 260 TRACE_EVENT_SCOPE_THREAD); 261 gesture_scroll_on_impl_thread_ = true; 262 return DID_HANDLE; 263 case cc::InputHandler::ScrollUnknown: 264 case cc::InputHandler::ScrollOnMainThread: 265 return DID_NOT_HANDLE; 266 case cc::InputHandler::ScrollIgnored: 267 return DROP_EVENT; 268 case cc::InputHandler::ScrollStatusCount: 269 NOTREACHED(); 270 break; 271 } 272 } else if (event.type == WebInputEvent::GestureScrollUpdate) { 273 #ifndef NDEBUG 274 DCHECK(expect_scroll_update_end_); 275 #endif 276 277 if (!gesture_scroll_on_impl_thread_ && !gesture_pinch_on_impl_thread_) 278 return DID_NOT_HANDLE; 279 280 const WebGestureEvent& gesture_event = 281 *static_cast<const WebGestureEvent*>(&event); 282 bool did_scroll = input_handler_->ScrollBy( 283 gfx::Point(gesture_event.x, gesture_event.y), 284 gfx::Vector2dF(-gesture_event.data.scrollUpdate.deltaX, 285 -gesture_event.data.scrollUpdate.deltaY)); 286 return did_scroll ? DID_HANDLE : DROP_EVENT; 287 } else if (event.type == WebInputEvent::GestureScrollEnd) { 288 #ifndef NDEBUG 289 DCHECK(expect_scroll_update_end_); 290 expect_scroll_update_end_ = false; 291 #endif 292 input_handler_->ScrollEnd(); 293 294 if (!gesture_scroll_on_impl_thread_) 295 return DID_NOT_HANDLE; 296 297 gesture_scroll_on_impl_thread_ = false; 298 return DID_HANDLE; 299 } else if (event.type == WebInputEvent::GesturePinchBegin) { 300 input_handler_->PinchGestureBegin(); 301 DCHECK(!gesture_pinch_on_impl_thread_); 302 gesture_pinch_on_impl_thread_ = true; 303 return DID_HANDLE; 304 } else if (event.type == WebInputEvent::GesturePinchEnd) { 305 DCHECK(gesture_pinch_on_impl_thread_); 306 gesture_pinch_on_impl_thread_ = false; 307 input_handler_->PinchGestureEnd(); 308 return DID_HANDLE; 309 } else if (event.type == WebInputEvent::GesturePinchUpdate) { 310 DCHECK(gesture_pinch_on_impl_thread_); 311 const WebGestureEvent& gesture_event = 312 *static_cast<const WebGestureEvent*>(&event); 313 input_handler_->PinchGestureUpdate( 314 gesture_event.data.pinchUpdate.scale, 315 gfx::Point(gesture_event.x, gesture_event.y)); 316 return DID_HANDLE; 317 } else if (event.type == WebInputEvent::GestureFlingStart) { 318 const WebGestureEvent& gesture_event = 319 *static_cast<const WebGestureEvent*>(&event); 320 return HandleGestureFling(gesture_event); 321 } else if (event.type == WebInputEvent::GestureFlingCancel) { 322 if (CancelCurrentFling(true)) 323 return DID_HANDLE; 324 else if (!fling_may_be_active_on_main_thread_) 325 return DROP_EVENT; 326 } else if (event.type == WebInputEvent::TouchStart) { 327 const WebTouchEvent& touch_event = 328 *static_cast<const WebTouchEvent*>(&event); 329 for (size_t i = 0; i < touch_event.touchesLength; ++i) { 330 if (touch_event.touches[i].state != WebTouchPoint::StatePressed) 331 continue; 332 if (input_handler_->HaveTouchEventHandlersAt( 333 gfx::Point(touch_event.touches[i].position.x, 334 touch_event.touches[i].position.y))) { 335 return DID_NOT_HANDLE; 336 } 337 } 338 return DROP_EVENT; 339 } else if (WebInputEvent::isKeyboardEventType(event.type)) { 340 // Only call |CancelCurrentFling()| if a fling was active, as it will 341 // otherwise disrupt an in-progress touch scroll. 342 if (fling_curve_) 343 CancelCurrentFling(true); 344 } else if (event.type == WebInputEvent::MouseMove) { 345 const WebMouseEvent& mouse_event = 346 *static_cast<const WebMouseEvent*>(&event); 347 // TODO(tony): Ignore when mouse buttons are down? 348 // TODO(davemoore): This should never happen, but bug #326635 showed some 349 // surprising crashes. 350 CHECK(input_handler_); 351 input_handler_->MouseMoveAt(gfx::Point(mouse_event.x, mouse_event.y)); 352 } 353 354 return DID_NOT_HANDLE; 355 } 356 357 InputHandlerProxy::EventDisposition 358 InputHandlerProxy::HandleGestureFling( 359 const WebGestureEvent& gesture_event) { 360 cc::InputHandler::ScrollStatus scroll_status; 361 362 if (gesture_event.sourceDevice == blink::WebGestureDeviceTouchpad) { 363 scroll_status = input_handler_->ScrollBegin( 364 gfx::Point(gesture_event.x, gesture_event.y), 365 cc::InputHandler::NonBubblingGesture); 366 } else { 367 if (!gesture_scroll_on_impl_thread_) 368 scroll_status = cc::InputHandler::ScrollOnMainThread; 369 else 370 scroll_status = input_handler_->FlingScrollBegin(); 371 } 372 373 #ifndef NDEBUG 374 expect_scroll_update_end_ = false; 375 #endif 376 377 switch (scroll_status) { 378 case cc::InputHandler::ScrollStarted: { 379 if (gesture_event.sourceDevice == blink::WebGestureDeviceTouchpad) 380 input_handler_->ScrollEnd(); 381 382 const float vx = gesture_event.data.flingStart.velocityX; 383 const float vy = gesture_event.data.flingStart.velocityY; 384 current_fling_velocity_ = gfx::Vector2dF(vx, vy); 385 fling_curve_.reset(client_->CreateFlingAnimationCurve( 386 gesture_event.sourceDevice, 387 WebFloatPoint(vx, vy), 388 blink::WebSize())); 389 disallow_horizontal_fling_scroll_ = !vx; 390 disallow_vertical_fling_scroll_ = !vy; 391 TRACE_EVENT_ASYNC_BEGIN2("input", 392 "InputHandlerProxy::HandleGestureFling::started", 393 this, 394 "vx", 395 vx, 396 "vy", 397 vy); 398 // Note that the timestamp will only be used to kickstart the animation if 399 // its sufficiently close to the timestamp of the first call |Animate()|. 400 has_fling_animation_started_ = false; 401 fling_parameters_.startTime = gesture_event.timeStampSeconds; 402 fling_parameters_.delta = WebFloatPoint(vx, vy); 403 fling_parameters_.point = WebPoint(gesture_event.x, gesture_event.y); 404 fling_parameters_.globalPoint = 405 WebPoint(gesture_event.globalX, gesture_event.globalY); 406 fling_parameters_.modifiers = gesture_event.modifiers; 407 fling_parameters_.sourceDevice = gesture_event.sourceDevice; 408 input_handler_->SetNeedsAnimate(); 409 return DID_HANDLE; 410 } 411 case cc::InputHandler::ScrollUnknown: 412 case cc::InputHandler::ScrollOnMainThread: { 413 TRACE_EVENT_INSTANT0("input", 414 "InputHandlerProxy::HandleGestureFling::" 415 "scroll_on_main_thread", 416 TRACE_EVENT_SCOPE_THREAD); 417 fling_may_be_active_on_main_thread_ = true; 418 return DID_NOT_HANDLE; 419 } 420 case cc::InputHandler::ScrollIgnored: { 421 TRACE_EVENT_INSTANT0( 422 "input", 423 "InputHandlerProxy::HandleGestureFling::ignored", 424 TRACE_EVENT_SCOPE_THREAD); 425 if (gesture_event.sourceDevice == blink::WebGestureDeviceTouchpad) { 426 // We still pass the curve to the main thread if there's nothing 427 // scrollable, in case something 428 // registers a handler before the curve is over. 429 return DID_NOT_HANDLE; 430 } 431 return DROP_EVENT; 432 } 433 case cc::InputHandler::ScrollStatusCount: 434 NOTREACHED(); 435 break; 436 } 437 return DID_NOT_HANDLE; 438 } 439 440 bool InputHandlerProxy::FilterInputEventForFlingBoosting( 441 const WebInputEvent& event) { 442 if (!WebInputEvent::isGestureEventType(event.type)) 443 return false; 444 445 if (!fling_curve_) { 446 DCHECK(!deferred_fling_cancel_time_seconds_); 447 return false; 448 } 449 450 const WebGestureEvent& gesture_event = 451 static_cast<const WebGestureEvent&>(event); 452 if (gesture_event.type == WebInputEvent::GestureFlingCancel) { 453 if (current_fling_velocity_.LengthSquared() < kMinBoostFlingSpeedSquare) 454 return false; 455 456 TRACE_EVENT_INSTANT0("input", 457 "InputHandlerProxy::FlingBoostStart", 458 TRACE_EVENT_SCOPE_THREAD); 459 deferred_fling_cancel_time_seconds_ = 460 event.timeStampSeconds + kFlingBoostTimeoutDelaySeconds; 461 return true; 462 } 463 464 // A fling is either inactive or is "free spinning", i.e., has yet to be 465 // interrupted by a touch gesture, in which case there is nothing to filter. 466 if (!deferred_fling_cancel_time_seconds_) 467 return false; 468 469 // Gestures from a different source should immediately interrupt the fling. 470 if (gesture_event.sourceDevice != fling_parameters_.sourceDevice) { 471 FlingBoostCancelAndResumeScrollingIfNecessary(); 472 return false; 473 } 474 475 switch (gesture_event.type) { 476 case WebInputEvent::GestureTapCancel: 477 case WebInputEvent::GestureTapDown: 478 return false; 479 480 case WebInputEvent::GestureScrollBegin: 481 if (!input_handler_->IsCurrentlyScrollingLayerAt( 482 gfx::Point(gesture_event.x, gesture_event.y), 483 fling_parameters_.sourceDevice == blink::WebGestureDeviceTouchpad 484 ? cc::InputHandler::NonBubblingGesture 485 : cc::InputHandler::Gesture)) { 486 CancelCurrentFling(true); 487 return false; 488 } 489 490 // TODO(jdduke): Use |gesture_event.data.scrollBegin.delta{X,Y}Hint| to 491 // determine if the ScrollBegin should immediately cancel the fling. 492 FlingBoostExtend(gesture_event); 493 return true; 494 495 case WebInputEvent::GestureScrollUpdate: { 496 const double time_since_last_boost_event = 497 event.timeStampSeconds - last_fling_boost_event_.timeStampSeconds; 498 if (ShouldSuppressScrollForFlingBoosting(current_fling_velocity_, 499 gesture_event, 500 time_since_last_boost_event)) { 501 FlingBoostExtend(gesture_event); 502 return true; 503 } 504 505 FlingBoostCancelAndResumeScrollingIfNecessary(); 506 return false; 507 } 508 509 case WebInputEvent::GestureScrollEnd: 510 CancelCurrentFling(true); 511 return true; 512 513 case WebInputEvent::GestureFlingStart: { 514 DCHECK_EQ(fling_parameters_.sourceDevice, gesture_event.sourceDevice); 515 516 bool fling_boosted = 517 fling_parameters_.modifiers == gesture_event.modifiers && 518 ShouldBoostFling(current_fling_velocity_, gesture_event); 519 520 gfx::Vector2dF new_fling_velocity( 521 gesture_event.data.flingStart.velocityX, 522 gesture_event.data.flingStart.velocityY); 523 524 if (fling_boosted) 525 current_fling_velocity_ += new_fling_velocity; 526 else 527 current_fling_velocity_ = new_fling_velocity; 528 529 WebFloatPoint velocity(current_fling_velocity_.x(), 530 current_fling_velocity_.y()); 531 deferred_fling_cancel_time_seconds_ = 0; 532 last_fling_boost_event_ = WebGestureEvent(); 533 fling_curve_.reset(client_->CreateFlingAnimationCurve( 534 gesture_event.sourceDevice, 535 velocity, 536 blink::WebSize())); 537 fling_parameters_.startTime = gesture_event.timeStampSeconds; 538 fling_parameters_.delta = velocity; 539 fling_parameters_.point = WebPoint(gesture_event.x, gesture_event.y); 540 fling_parameters_.globalPoint = 541 WebPoint(gesture_event.globalX, gesture_event.globalY); 542 543 TRACE_EVENT_INSTANT2("input", 544 fling_boosted ? "InputHandlerProxy::FlingBoosted" 545 : "InputHandlerProxy::FlingReplaced", 546 TRACE_EVENT_SCOPE_THREAD, 547 "vx", 548 current_fling_velocity_.x(), 549 "vy", 550 current_fling_velocity_.y()); 551 552 // The client expects balanced calls between a consumed GestureFlingStart 553 // and |DidStopFlinging()|. TODO(jdduke): Provide a count parameter to 554 // |DidStopFlinging()| and only send after the accumulated fling ends. 555 client_->DidStopFlinging(); 556 return true; 557 } 558 559 default: 560 // All other types of gestures (taps, presses, etc...) will complete the 561 // deferred fling cancellation. 562 FlingBoostCancelAndResumeScrollingIfNecessary(); 563 return false; 564 } 565 } 566 567 void InputHandlerProxy::FlingBoostExtend(const blink::WebGestureEvent& event) { 568 TRACE_EVENT_INSTANT0( 569 "input", "InputHandlerProxy::FlingBoostExtend", TRACE_EVENT_SCOPE_THREAD); 570 deferred_fling_cancel_time_seconds_ = 571 event.timeStampSeconds + kFlingBoostTimeoutDelaySeconds; 572 last_fling_boost_event_ = event; 573 } 574 575 void InputHandlerProxy::FlingBoostCancelAndResumeScrollingIfNecessary() { 576 TRACE_EVENT_INSTANT0( 577 "input", "InputHandlerProxy::FlingBoostCancel", TRACE_EVENT_SCOPE_THREAD); 578 DCHECK(deferred_fling_cancel_time_seconds_); 579 580 // Note: |last_fling_boost_event_| is cleared by |CancelCurrentFling()|. 581 WebGestureEvent last_fling_boost_event = last_fling_boost_event_; 582 583 CancelCurrentFling(true); 584 585 if (last_fling_boost_event.type == WebInputEvent::GestureScrollBegin || 586 last_fling_boost_event.type == WebInputEvent::GestureScrollUpdate) { 587 // Synthesize a GestureScrollBegin, as the original was suppressed. 588 HandleInputEvent(ObtainGestureScrollBegin(last_fling_boost_event)); 589 } 590 } 591 592 void InputHandlerProxy::Animate(base::TimeTicks time) { 593 if (!fling_curve_) 594 return; 595 596 double monotonic_time_sec = InSecondsF(time); 597 598 if (deferred_fling_cancel_time_seconds_ && 599 monotonic_time_sec > deferred_fling_cancel_time_seconds_) { 600 FlingBoostCancelAndResumeScrollingIfNecessary(); 601 return; 602 } 603 604 if (!has_fling_animation_started_) { 605 has_fling_animation_started_ = true; 606 // Guard against invalid, future or sufficiently stale start times, as there 607 // are no guarantees fling event and animation timestamps are compatible. 608 if (!fling_parameters_.startTime || 609 monotonic_time_sec <= fling_parameters_.startTime || 610 monotonic_time_sec >= fling_parameters_.startTime + 611 kMaxSecondsFromFlingTimestampToFirstAnimate) { 612 fling_parameters_.startTime = monotonic_time_sec; 613 input_handler_->SetNeedsAnimate(); 614 return; 615 } 616 } 617 618 bool fling_is_active = 619 fling_curve_->apply(monotonic_time_sec - fling_parameters_.startTime, 620 this); 621 622 if (disallow_vertical_fling_scroll_ && disallow_horizontal_fling_scroll_) 623 fling_is_active = false; 624 625 if (fling_is_active) { 626 input_handler_->SetNeedsAnimate(); 627 } else { 628 TRACE_EVENT_INSTANT0("input", 629 "InputHandlerProxy::animate::flingOver", 630 TRACE_EVENT_SCOPE_THREAD); 631 CancelCurrentFling(true); 632 } 633 } 634 635 void InputHandlerProxy::MainThreadHasStoppedFlinging() { 636 fling_may_be_active_on_main_thread_ = false; 637 client_->DidStopFlinging(); 638 } 639 640 void InputHandlerProxy::DidOverscroll( 641 const gfx::Vector2dF& accumulated_overscroll, 642 const gfx::Vector2dF& latest_overscroll_delta) { 643 DCHECK(client_); 644 645 TRACE_EVENT2("input", 646 "InputHandlerProxy::DidOverscroll", 647 "dx", 648 latest_overscroll_delta.x(), 649 "dy", 650 latest_overscroll_delta.y()); 651 652 DidOverscrollParams params; 653 params.accumulated_overscroll = accumulated_overscroll; 654 params.latest_overscroll_delta = latest_overscroll_delta; 655 params.current_fling_velocity = 656 ToClientScrollIncrement(current_fling_velocity_); 657 658 if (fling_curve_) { 659 static const int kFlingOverscrollThreshold = 1; 660 disallow_horizontal_fling_scroll_ |= 661 std::abs(params.accumulated_overscroll.x()) >= 662 kFlingOverscrollThreshold; 663 disallow_vertical_fling_scroll_ |= 664 std::abs(params.accumulated_overscroll.y()) >= 665 kFlingOverscrollThreshold; 666 } 667 668 client_->DidOverscroll(params); 669 } 670 671 bool InputHandlerProxy::CancelCurrentFling( 672 bool send_fling_stopped_notification) { 673 bool had_fling_animation = fling_curve_; 674 if (had_fling_animation && 675 fling_parameters_.sourceDevice == blink::WebGestureDeviceTouchscreen) { 676 input_handler_->ScrollEnd(); 677 TRACE_EVENT_ASYNC_END0( 678 "input", 679 "InputHandlerProxy::HandleGestureFling::started", 680 this); 681 } 682 683 TRACE_EVENT_INSTANT1("input", 684 "InputHandlerProxy::CancelCurrentFling", 685 TRACE_EVENT_SCOPE_THREAD, 686 "had_fling_animation", 687 had_fling_animation); 688 fling_curve_.reset(); 689 has_fling_animation_started_ = false; 690 gesture_scroll_on_impl_thread_ = false; 691 current_fling_velocity_ = gfx::Vector2dF(); 692 fling_parameters_ = blink::WebActiveWheelFlingParameters(); 693 deferred_fling_cancel_time_seconds_ = 0; 694 last_fling_boost_event_ = WebGestureEvent(); 695 if (send_fling_stopped_notification && had_fling_animation) 696 client_->DidStopFlinging(); 697 return had_fling_animation; 698 } 699 700 bool InputHandlerProxy::TouchpadFlingScroll( 701 const WebFloatSize& increment) { 702 WebMouseWheelEvent synthetic_wheel; 703 synthetic_wheel.type = WebInputEvent::MouseWheel; 704 synthetic_wheel.deltaX = increment.width; 705 synthetic_wheel.deltaY = increment.height; 706 synthetic_wheel.hasPreciseScrollingDeltas = true; 707 synthetic_wheel.x = fling_parameters_.point.x; 708 synthetic_wheel.y = fling_parameters_.point.y; 709 synthetic_wheel.globalX = fling_parameters_.globalPoint.x; 710 synthetic_wheel.globalY = fling_parameters_.globalPoint.y; 711 synthetic_wheel.modifiers = fling_parameters_.modifiers; 712 713 InputHandlerProxy::EventDisposition disposition = 714 HandleInputEvent(synthetic_wheel); 715 switch (disposition) { 716 case DID_HANDLE: 717 return true; 718 case DROP_EVENT: 719 break; 720 case DID_NOT_HANDLE: 721 TRACE_EVENT_INSTANT0("input", 722 "InputHandlerProxy::scrollBy::AbortFling", 723 TRACE_EVENT_SCOPE_THREAD); 724 // If we got a DID_NOT_HANDLE, that means we need to deliver wheels on the 725 // main thread. In this case we need to schedule a commit and transfer the 726 // fling curve over to the main thread and run the rest of the wheels from 727 // there. This can happen when flinging a page that contains a scrollable 728 // subarea that we can't scroll on the thread if the fling starts outside 729 // the subarea but then is flung "under" the pointer. 730 client_->TransferActiveWheelFlingAnimation(fling_parameters_); 731 fling_may_be_active_on_main_thread_ = true; 732 CancelCurrentFling(false); 733 break; 734 } 735 736 return false; 737 } 738 739 bool InputHandlerProxy::scrollBy(const WebFloatSize& increment, 740 const WebFloatSize& velocity) { 741 WebFloatSize clipped_increment; 742 WebFloatSize clipped_velocity; 743 if (!disallow_horizontal_fling_scroll_) { 744 clipped_increment.width = increment.width; 745 clipped_velocity.width = velocity.width; 746 } 747 if (!disallow_vertical_fling_scroll_) { 748 clipped_increment.height = increment.height; 749 clipped_velocity.height = velocity.height; 750 } 751 752 current_fling_velocity_ = clipped_velocity; 753 754 // Early out if the increment is zero, but avoid early terimination if the 755 // velocity is still non-zero. 756 if (clipped_increment == WebFloatSize()) 757 return clipped_velocity != WebFloatSize(); 758 759 TRACE_EVENT2("input", 760 "InputHandlerProxy::scrollBy", 761 "x", 762 clipped_increment.width, 763 "y", 764 clipped_increment.height); 765 766 bool did_scroll = false; 767 768 switch (fling_parameters_.sourceDevice) { 769 case blink::WebGestureDeviceTouchpad: 770 did_scroll = TouchpadFlingScroll(clipped_increment); 771 break; 772 case blink::WebGestureDeviceTouchscreen: 773 clipped_increment = ToClientScrollIncrement(clipped_increment); 774 did_scroll = input_handler_->ScrollBy(fling_parameters_.point, 775 clipped_increment); 776 break; 777 } 778 779 if (did_scroll) { 780 fling_parameters_.cumulativeScroll.width += clipped_increment.width; 781 fling_parameters_.cumulativeScroll.height += clipped_increment.height; 782 } 783 784 // It's possible the provided |increment| is sufficiently small as to not 785 // trigger a scroll, e.g., with a trivial time delta between fling updates. 786 // Return true in this case to prevent early fling termination. 787 if (std::abs(clipped_increment.width) < kScrollEpsilon && 788 std::abs(clipped_increment.height) < kScrollEpsilon) 789 return true; 790 791 return did_scroll; 792 } 793 794 } // namespace content 795