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