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/debug/trace_event.h" 8 #include "base/logging.h" 9 #include "base/metrics/histogram.h" 10 #include "content/renderer/input/input_handler_proxy_client.h" 11 #include "third_party/WebKit/public/platform/Platform.h" 12 #include "third_party/WebKit/public/web/WebInputEvent.h" 13 #include "ui/events/latency_info.h" 14 #include "ui/gfx/frame_time.h" 15 16 using blink::WebFloatPoint; 17 using blink::WebFloatSize; 18 using blink::WebGestureEvent; 19 using blink::WebInputEvent; 20 using blink::WebMouseEvent; 21 using blink::WebMouseWheelEvent; 22 using blink::WebPoint; 23 using blink::WebTouchEvent; 24 using blink::WebTouchPoint; 25 26 namespace { 27 28 // Validate provided event timestamps that interact with animation timestamps. 29 const double kBadTimestampDeltaFromNowInS = 60. * 60. * 24. * 7.; 30 31 double InSecondsF(const base::TimeTicks& time) { 32 return (time - base::TimeTicks()).InSecondsF(); 33 } 34 35 void SendScrollLatencyUma(const WebInputEvent& event, 36 const ui::LatencyInfo& latency_info) { 37 if (!(event.type == WebInputEvent::GestureScrollBegin || 38 event.type == WebInputEvent::GestureScrollUpdate || 39 event.type == WebInputEvent::GestureScrollUpdateWithoutPropagation)) 40 return; 41 42 ui::LatencyInfo::LatencyMap::const_iterator it = 43 latency_info.latency_components.find(std::make_pair( 44 ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, 0)); 45 46 if (it == latency_info.latency_components.end()) 47 return; 48 49 base::TimeDelta delta = base::TimeTicks::HighResNow() - it->second.event_time; 50 for (size_t i = 0; i < it->second.event_count; ++i) { 51 UMA_HISTOGRAM_CUSTOM_COUNTS( 52 "Event.Latency.RendererImpl.GestureScroll2", 53 delta.InMicroseconds(), 54 0, 55 1000000, 56 100); 57 } 58 } // namespace 59 60 } 61 62 namespace content { 63 64 InputHandlerProxy::InputHandlerProxy(cc::InputHandler* input_handler) 65 : client_(NULL), 66 input_handler_(input_handler), 67 #ifndef NDEBUG 68 expect_scroll_update_end_(false), 69 expect_pinch_update_end_(false), 70 #endif 71 gesture_scroll_on_impl_thread_(false), 72 gesture_pinch_on_impl_thread_(false), 73 fling_may_be_active_on_main_thread_(false), 74 fling_overscrolled_horizontally_(false), 75 fling_overscrolled_vertically_(false) { 76 input_handler_->BindToClient(this); 77 } 78 79 InputHandlerProxy::~InputHandlerProxy() {} 80 81 void InputHandlerProxy::WillShutdown() { 82 input_handler_ = NULL; 83 DCHECK(client_); 84 client_->WillShutdown(); 85 } 86 87 void InputHandlerProxy::SetClient(InputHandlerProxyClient* client) { 88 DCHECK(!client_ || !client); 89 client_ = client; 90 } 91 92 InputHandlerProxy::EventDisposition 93 InputHandlerProxy::HandleInputEventWithLatencyInfo( 94 const WebInputEvent& event, 95 ui::LatencyInfo* latency_info) { 96 DCHECK(input_handler_); 97 98 SendScrollLatencyUma(event, *latency_info); 99 100 scoped_ptr<cc::SwapPromiseMonitor> latency_info_swap_promise_monitor = 101 input_handler_->CreateLatencyInfoSwapPromiseMonitor(latency_info); 102 InputHandlerProxy::EventDisposition disposition = HandleInputEvent(event); 103 return disposition; 104 } 105 106 InputHandlerProxy::EventDisposition InputHandlerProxy::HandleInputEvent( 107 const WebInputEvent& event) { 108 DCHECK(client_); 109 DCHECK(input_handler_); 110 111 if (event.type == WebInputEvent::MouseWheel) { 112 const WebMouseWheelEvent& wheel_event = 113 *static_cast<const WebMouseWheelEvent*>(&event); 114 if (wheel_event.scrollByPage) { 115 // TODO(jamesr): We don't properly handle scroll by page in the compositor 116 // thread, so punt it to the main thread. http://crbug.com/236639 117 return DID_NOT_HANDLE; 118 } 119 cc::InputHandler::ScrollStatus scroll_status = input_handler_->ScrollBegin( 120 gfx::Point(wheel_event.x, wheel_event.y), cc::InputHandler::Wheel); 121 switch (scroll_status) { 122 case cc::InputHandler::ScrollStarted: { 123 TRACE_EVENT_INSTANT2( 124 "renderer", 125 "InputHandlerProxy::handle_input wheel scroll", 126 TRACE_EVENT_SCOPE_THREAD, 127 "deltaX", 128 -wheel_event.deltaX, 129 "deltaY", 130 -wheel_event.deltaY); 131 bool did_scroll = input_handler_->ScrollBy( 132 gfx::Point(wheel_event.x, wheel_event.y), 133 gfx::Vector2dF(-wheel_event.deltaX, -wheel_event.deltaY)); 134 input_handler_->ScrollEnd(); 135 return did_scroll ? DID_HANDLE : DROP_EVENT; 136 } 137 case cc::InputHandler::ScrollIgnored: 138 // TODO(jamesr): This should be DROP_EVENT, but in cases where we fail 139 // to properly sync scrollability it's safer to send the event to the 140 // main thread. Change back to DROP_EVENT once we have synchronization 141 // bugs sorted out. 142 return DID_NOT_HANDLE; 143 case cc::InputHandler::ScrollOnMainThread: 144 return DID_NOT_HANDLE; 145 } 146 } else if (event.type == WebInputEvent::GestureScrollBegin) { 147 DCHECK(!gesture_scroll_on_impl_thread_); 148 #ifndef NDEBUG 149 DCHECK(!expect_scroll_update_end_); 150 expect_scroll_update_end_ = true; 151 #endif 152 const WebGestureEvent& gesture_event = 153 *static_cast<const WebGestureEvent*>(&event); 154 cc::InputHandler::ScrollStatus scroll_status = input_handler_->ScrollBegin( 155 gfx::Point(gesture_event.x, gesture_event.y), 156 cc::InputHandler::Gesture); 157 switch (scroll_status) { 158 case cc::InputHandler::ScrollStarted: 159 gesture_scroll_on_impl_thread_ = true; 160 return DID_HANDLE; 161 case cc::InputHandler::ScrollOnMainThread: 162 return DID_NOT_HANDLE; 163 case cc::InputHandler::ScrollIgnored: 164 return DROP_EVENT; 165 } 166 } else if (event.type == WebInputEvent::GestureScrollUpdate) { 167 #ifndef NDEBUG 168 DCHECK(expect_scroll_update_end_); 169 #endif 170 171 if (!gesture_scroll_on_impl_thread_ && !gesture_pinch_on_impl_thread_) 172 return DID_NOT_HANDLE; 173 174 const WebGestureEvent& gesture_event = 175 *static_cast<const WebGestureEvent*>(&event); 176 bool did_scroll = input_handler_->ScrollBy( 177 gfx::Point(gesture_event.x, gesture_event.y), 178 gfx::Vector2dF(-gesture_event.data.scrollUpdate.deltaX, 179 -gesture_event.data.scrollUpdate.deltaY)); 180 return did_scroll ? DID_HANDLE : DROP_EVENT; 181 } else if (event.type == WebInputEvent::GestureScrollEnd) { 182 #ifndef NDEBUG 183 DCHECK(expect_scroll_update_end_); 184 expect_scroll_update_end_ = false; 185 #endif 186 input_handler_->ScrollEnd(); 187 188 if (!gesture_scroll_on_impl_thread_) 189 return DID_NOT_HANDLE; 190 191 gesture_scroll_on_impl_thread_ = false; 192 return DID_HANDLE; 193 } else if (event.type == WebInputEvent::GesturePinchBegin) { 194 #ifndef NDEBUG 195 DCHECK(!expect_pinch_update_end_); 196 expect_pinch_update_end_ = true; 197 #endif 198 input_handler_->PinchGestureBegin(); 199 gesture_pinch_on_impl_thread_ = true; 200 return DID_HANDLE; 201 } else if (event.type == WebInputEvent::GesturePinchEnd) { 202 #ifndef NDEBUG 203 DCHECK(expect_pinch_update_end_); 204 expect_pinch_update_end_ = false; 205 #endif 206 gesture_pinch_on_impl_thread_ = false; 207 input_handler_->PinchGestureEnd(); 208 return DID_HANDLE; 209 } else if (event.type == WebInputEvent::GesturePinchUpdate) { 210 #ifndef NDEBUG 211 DCHECK(expect_pinch_update_end_); 212 #endif 213 const WebGestureEvent& gesture_event = 214 *static_cast<const WebGestureEvent*>(&event); 215 input_handler_->PinchGestureUpdate( 216 gesture_event.data.pinchUpdate.scale, 217 gfx::Point(gesture_event.x, gesture_event.y)); 218 return DID_HANDLE; 219 } else if (event.type == WebInputEvent::GestureFlingStart) { 220 const WebGestureEvent& gesture_event = 221 *static_cast<const WebGestureEvent*>(&event); 222 return HandleGestureFling(gesture_event); 223 } else if (event.type == WebInputEvent::GestureFlingCancel) { 224 if (CancelCurrentFling()) 225 return DID_HANDLE; 226 else if (!fling_may_be_active_on_main_thread_) 227 return DROP_EVENT; 228 } else if (event.type == WebInputEvent::TouchStart) { 229 const WebTouchEvent& touch_event = 230 *static_cast<const WebTouchEvent*>(&event); 231 for (size_t i = 0; i < touch_event.touchesLength; ++i) { 232 if (touch_event.touches[i].state != WebTouchPoint::StatePressed) 233 continue; 234 if (input_handler_->HaveTouchEventHandlersAt(touch_event.touches[i] 235 .position)) 236 return DID_NOT_HANDLE; 237 } 238 return DROP_EVENT; 239 } else if (WebInputEvent::isKeyboardEventType(event.type)) { 240 CancelCurrentFling(); 241 } 242 243 return DID_NOT_HANDLE; 244 } 245 246 InputHandlerProxy::EventDisposition 247 InputHandlerProxy::HandleGestureFling( 248 const WebGestureEvent& gesture_event) { 249 cc::InputHandler::ScrollStatus scroll_status; 250 251 if (gesture_event.sourceDevice == WebGestureEvent::Touchpad) { 252 scroll_status = input_handler_->ScrollBegin( 253 gfx::Point(gesture_event.x, gesture_event.y), 254 cc::InputHandler::NonBubblingGesture); 255 } else { 256 if (!gesture_scroll_on_impl_thread_) 257 scroll_status = cc::InputHandler::ScrollOnMainThread; 258 else 259 scroll_status = input_handler_->FlingScrollBegin(); 260 } 261 262 #ifndef NDEBUG 263 expect_scroll_update_end_ = false; 264 #endif 265 266 switch (scroll_status) { 267 case cc::InputHandler::ScrollStarted: { 268 if (gesture_event.sourceDevice == WebGestureEvent::Touchpad) 269 input_handler_->ScrollEnd(); 270 271 fling_curve_.reset(client_->CreateFlingAnimationCurve( 272 gesture_event.sourceDevice, 273 WebFloatPoint(gesture_event.data.flingStart.velocityX, 274 gesture_event.data.flingStart.velocityY), 275 blink::WebSize())); 276 fling_overscrolled_horizontally_ = false; 277 fling_overscrolled_vertically_ = false; 278 TRACE_EVENT_ASYNC_BEGIN0( 279 "renderer", 280 "InputHandlerProxy::HandleGestureFling::started", 281 this); 282 if (gesture_event.timeStampSeconds) { 283 fling_parameters_.startTime = gesture_event.timeStampSeconds; 284 DCHECK_LT(fling_parameters_.startTime - 285 InSecondsF(gfx::FrameTime::Now()), 286 kBadTimestampDeltaFromNowInS); 287 } 288 fling_parameters_.delta = 289 WebFloatPoint(gesture_event.data.flingStart.velocityX, 290 gesture_event.data.flingStart.velocityY); 291 fling_parameters_.point = WebPoint(gesture_event.x, gesture_event.y); 292 fling_parameters_.globalPoint = 293 WebPoint(gesture_event.globalX, gesture_event.globalY); 294 fling_parameters_.modifiers = gesture_event.modifiers; 295 fling_parameters_.sourceDevice = gesture_event.sourceDevice; 296 input_handler_->ScheduleAnimation(); 297 return DID_HANDLE; 298 } 299 case cc::InputHandler::ScrollOnMainThread: { 300 TRACE_EVENT_INSTANT0("renderer", 301 "InputHandlerProxy::HandleGestureFling::" 302 "scroll_on_main_thread", 303 TRACE_EVENT_SCOPE_THREAD); 304 fling_may_be_active_on_main_thread_ = true; 305 return DID_NOT_HANDLE; 306 } 307 case cc::InputHandler::ScrollIgnored: { 308 TRACE_EVENT_INSTANT0( 309 "renderer", 310 "InputHandlerProxy::HandleGestureFling::ignored", 311 TRACE_EVENT_SCOPE_THREAD); 312 if (gesture_event.sourceDevice == WebGestureEvent::Touchpad) { 313 // We still pass the curve to the main thread if there's nothing 314 // scrollable, in case something 315 // registers a handler before the curve is over. 316 return DID_NOT_HANDLE; 317 } 318 return DROP_EVENT; 319 } 320 } 321 return DID_NOT_HANDLE; 322 } 323 324 void InputHandlerProxy::Animate(base::TimeTicks time) { 325 if (!fling_curve_) 326 return; 327 328 double monotonic_time_sec = InSecondsF(time); 329 if (!fling_parameters_.startTime) { 330 fling_parameters_.startTime = monotonic_time_sec; 331 input_handler_->ScheduleAnimation(); 332 return; 333 } 334 335 if (fling_curve_->apply(monotonic_time_sec - fling_parameters_.startTime, 336 this)) { 337 input_handler_->ScheduleAnimation(); 338 } else { 339 TRACE_EVENT_INSTANT0("renderer", 340 "InputHandlerProxy::animate::flingOver", 341 TRACE_EVENT_SCOPE_THREAD); 342 CancelCurrentFling(); 343 } 344 } 345 346 void InputHandlerProxy::MainThreadHasStoppedFlinging() { 347 fling_may_be_active_on_main_thread_ = false; 348 } 349 350 void InputHandlerProxy::DidOverscroll(const cc::DidOverscrollParams& params) { 351 DCHECK(client_); 352 if (fling_curve_) { 353 static const int kFlingOverscrollThreshold = 1; 354 fling_overscrolled_horizontally_ |= 355 std::abs(params.accumulated_overscroll.x()) >= 356 kFlingOverscrollThreshold; 357 fling_overscrolled_vertically_ |= 358 std::abs(params.accumulated_overscroll.y()) >= 359 kFlingOverscrollThreshold; 360 } 361 362 client_->DidOverscroll(params); 363 } 364 365 bool InputHandlerProxy::CancelCurrentFling() { 366 bool had_fling_animation = fling_curve_; 367 if (had_fling_animation && 368 fling_parameters_.sourceDevice == WebGestureEvent::Touchscreen) { 369 input_handler_->ScrollEnd(); 370 TRACE_EVENT_ASYNC_END0( 371 "renderer", 372 "InputHandlerProxy::HandleGestureFling::started", 373 this); 374 } 375 376 TRACE_EVENT_INSTANT1("renderer", 377 "InputHandlerProxy::CancelCurrentFling", 378 TRACE_EVENT_SCOPE_THREAD, 379 "had_fling_animation", 380 had_fling_animation); 381 fling_curve_.reset(); 382 gesture_scroll_on_impl_thread_ = false; 383 fling_parameters_ = blink::WebActiveWheelFlingParameters(); 384 return had_fling_animation; 385 } 386 387 bool InputHandlerProxy::TouchpadFlingScroll( 388 const WebFloatSize& increment) { 389 WebMouseWheelEvent synthetic_wheel; 390 synthetic_wheel.type = WebInputEvent::MouseWheel; 391 synthetic_wheel.deltaX = increment.width; 392 synthetic_wheel.deltaY = increment.height; 393 synthetic_wheel.hasPreciseScrollingDeltas = true; 394 synthetic_wheel.x = fling_parameters_.point.x; 395 synthetic_wheel.y = fling_parameters_.point.y; 396 synthetic_wheel.globalX = fling_parameters_.globalPoint.x; 397 synthetic_wheel.globalY = fling_parameters_.globalPoint.y; 398 synthetic_wheel.modifiers = fling_parameters_.modifiers; 399 400 InputHandlerProxy::EventDisposition disposition = 401 HandleInputEvent(synthetic_wheel); 402 switch (disposition) { 403 case DID_HANDLE: 404 return true; 405 case DROP_EVENT: 406 break; 407 case DID_NOT_HANDLE: 408 TRACE_EVENT_INSTANT0("renderer", 409 "InputHandlerProxy::scrollBy::AbortFling", 410 TRACE_EVENT_SCOPE_THREAD); 411 // If we got a DID_NOT_HANDLE, that means we need to deliver wheels on the 412 // main thread. In this case we need to schedule a commit and transfer the 413 // fling curve over to the main thread and run the rest of the wheels from 414 // there. This can happen when flinging a page that contains a scrollable 415 // subarea that we can't scroll on the thread if the fling starts outside 416 // the subarea but then is flung "under" the pointer. 417 client_->TransferActiveWheelFlingAnimation(fling_parameters_); 418 fling_may_be_active_on_main_thread_ = true; 419 CancelCurrentFling(); 420 break; 421 } 422 423 return false; 424 } 425 426 static gfx::Vector2dF ToClientScrollIncrement(const WebFloatSize& increment) { 427 return gfx::Vector2dF(-increment.width, -increment.height); 428 } 429 430 void InputHandlerProxy::scrollBy(const WebFloatSize& increment) { 431 WebFloatSize clipped_increment; 432 if (!fling_overscrolled_horizontally_) 433 clipped_increment.width = increment.width; 434 if (!fling_overscrolled_vertically_) 435 clipped_increment.height = increment.height; 436 437 if (clipped_increment == WebFloatSize()) 438 return; 439 440 TRACE_EVENT2("renderer", 441 "InputHandlerProxy::scrollBy", 442 "x", 443 clipped_increment.width, 444 "y", 445 clipped_increment.height); 446 447 bool did_scroll = false; 448 449 switch (fling_parameters_.sourceDevice) { 450 case WebGestureEvent::Touchpad: 451 did_scroll = TouchpadFlingScroll(clipped_increment); 452 break; 453 case WebGestureEvent::Touchscreen: 454 clipped_increment = ToClientScrollIncrement(clipped_increment); 455 did_scroll = input_handler_->ScrollBy(fling_parameters_.point, 456 clipped_increment); 457 break; 458 } 459 460 if (did_scroll) { 461 fling_parameters_.cumulativeScroll.width += clipped_increment.width; 462 fling_parameters_.cumulativeScroll.height += clipped_increment.height; 463 } 464 } 465 466 void InputHandlerProxy::notifyCurrentFlingVelocity( 467 const WebFloatSize& velocity) { 468 TRACE_EVENT2("renderer", 469 "InputHandlerProxy::notifyCurrentFlingVelocity", 470 "vx", 471 velocity.width, 472 "vy", 473 velocity.height); 474 input_handler_->NotifyCurrentFlingVelocity(ToClientScrollIncrement(velocity)); 475 } 476 477 } // namespace content 478