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