overscroll_controller.cc revision ca12bfac764ba476d6cd062bf1dde12cc64c3f40
1// Copyright (c) 2012 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/overscroll_controller.h" 6 7#include "content/browser/renderer_host/overscroll_controller_delegate.h" 8#include "content/browser/renderer_host/render_widget_host_impl.h" 9#include "content/public/browser/overscroll_configuration.h" 10#include "content/public/browser/render_widget_host_view.h" 11 12namespace content { 13 14OverscrollController::OverscrollController( 15 RenderWidgetHostImpl* render_widget_host) 16 : render_widget_host_(render_widget_host), 17 overscroll_mode_(OVERSCROLL_NONE), 18 scroll_state_(STATE_UNKNOWN), 19 overscroll_delta_x_(0.f), 20 overscroll_delta_y_(0.f), 21 delegate_(NULL) { 22} 23 24OverscrollController::~OverscrollController() { 25} 26 27bool OverscrollController::WillDispatchEvent( 28 const WebKit::WebInputEvent& event, 29 const ui::LatencyInfo& latency_info) { 30 if (scroll_state_ != STATE_UNKNOWN) { 31 switch (event.type) { 32 case WebKit::WebInputEvent::GestureScrollEnd: 33 case WebKit::WebInputEvent::GestureFlingStart: 34 scroll_state_ = STATE_UNKNOWN; 35 break; 36 37 case WebKit::WebInputEvent::MouseWheel: { 38 const WebKit::WebMouseWheelEvent& wheel = 39 static_cast<const WebKit::WebMouseWheelEvent&>(event); 40 if (!wheel.hasPreciseScrollingDeltas || 41 wheel.phase == WebKit::WebMouseWheelEvent::PhaseEnded || 42 wheel.phase == WebKit::WebMouseWheelEvent::PhaseCancelled) { 43 scroll_state_ = STATE_UNKNOWN; 44 } 45 break; 46 } 47 48 default: 49 if (WebKit::WebInputEvent::isMouseEventType(event.type) || 50 WebKit::WebInputEvent::isKeyboardEventType(event.type)) { 51 scroll_state_ = STATE_UNKNOWN; 52 } 53 break; 54 } 55 } 56 57 if (DispatchEventCompletesAction(event)) { 58 CompleteAction(); 59 60 // If the overscroll was caused by touch-scrolling, then the gesture event 61 // that completes the action needs to be sent to the renderer, because the 62 // touch-scrolls maintain state in the renderer side (in the compositor, for 63 // example), and the event that completes this action needs to be sent to 64 // the renderer so that those states can be updated/reset appropriately. 65 // Send the event through the host when appropriate. 66 if (ShouldForwardToHost(event)) { 67 const WebKit::WebGestureEvent& gevent = 68 static_cast<const WebKit::WebGestureEvent&>(event); 69 return render_widget_host_->ShouldForwardGestureEvent( 70 GestureEventWithLatencyInfo(gevent, latency_info)); 71 } 72 73 return false; 74 } 75 76 if (overscroll_mode_ != OVERSCROLL_NONE && DispatchEventResetsState(event)) { 77 SetOverscrollMode(OVERSCROLL_NONE); 78 // The overscroll gesture status is being reset. If the event is a 79 // gesture event (from either touchscreen or trackpad), then make sure the 80 // host gets the event first (if it didn't already process it). 81 if (ShouldForwardToHost(event)) { 82 const WebKit::WebGestureEvent& gevent = 83 static_cast<const WebKit::WebGestureEvent&>(event); 84 return render_widget_host_->ShouldForwardGestureEvent( 85 GestureEventWithLatencyInfo(gevent, latency_info)); 86 } 87 88 // Let the event be dispatched to the renderer. 89 return true; 90 } 91 92 if (overscroll_mode_ != OVERSCROLL_NONE) { 93 // Consume the event and update overscroll state when in the middle of the 94 // overscroll gesture. 95 ProcessEventForOverscroll(event); 96 97 if (event.type == WebKit::WebInputEvent::TouchEnd || 98 event.type == WebKit::WebInputEvent::TouchCancel || 99 event.type == WebKit::WebInputEvent::TouchMove) { 100 return true; 101 } 102 return false; 103 } 104 105 return true; 106} 107 108void OverscrollController::ReceivedEventACK(const WebKit::WebInputEvent& event, 109 bool processed) { 110 if (processed) { 111 // If a scroll event is consumed by the page, i.e. some content on the page 112 // has been scrolled, then there is not going to be an overscroll gesture, 113 // until the current scroll ends, and a new scroll gesture starts. 114 if (scroll_state_ == STATE_UNKNOWN && 115 (event.type == WebKit::WebInputEvent::MouseWheel || 116 event.type == WebKit::WebInputEvent::GestureScrollUpdate)) { 117 scroll_state_ = STATE_CONTENT_SCROLLING; 118 } 119 return; 120 } 121 ProcessEventForOverscroll(event); 122} 123 124void OverscrollController::DiscardingGestureEvent( 125 const WebKit::WebGestureEvent& gesture) { 126 if (scroll_state_ != STATE_UNKNOWN && 127 (gesture.type == WebKit::WebInputEvent::GestureScrollEnd || 128 gesture.type == WebKit::WebInputEvent::GestureFlingStart)) { 129 scroll_state_ = STATE_UNKNOWN; 130 } 131} 132 133void OverscrollController::Reset() { 134 overscroll_mode_ = OVERSCROLL_NONE; 135 overscroll_delta_x_ = overscroll_delta_y_ = 0.f; 136 scroll_state_ = STATE_UNKNOWN; 137} 138 139void OverscrollController::Cancel() { 140 SetOverscrollMode(OVERSCROLL_NONE); 141 overscroll_delta_x_ = overscroll_delta_y_ = 0.f; 142 scroll_state_ = STATE_UNKNOWN; 143} 144 145bool OverscrollController::DispatchEventCompletesAction ( 146 const WebKit::WebInputEvent& event) const { 147 if (overscroll_mode_ == OVERSCROLL_NONE) 148 return false; 149 150 // Complete the overscroll gesture if there was a mouse move or a scroll-end 151 // after the threshold. 152 if (event.type != WebKit::WebInputEvent::MouseMove && 153 event.type != WebKit::WebInputEvent::GestureScrollEnd && 154 event.type != WebKit::WebInputEvent::GestureFlingStart) 155 return false; 156 157 RenderWidgetHostView* view = render_widget_host_->GetView(); 158 if (!view->IsShowing()) 159 return false; 160 161 const gfx::Rect& bounds = view->GetViewBounds(); 162 if (bounds.IsEmpty()) 163 return false; 164 165 if (event.type == WebKit::WebInputEvent::GestureFlingStart) { 166 // Check to see if the fling is in the same direction of the overscroll. 167 const WebKit::WebGestureEvent gesture = 168 static_cast<const WebKit::WebGestureEvent&>(event); 169 switch (overscroll_mode_) { 170 case OVERSCROLL_EAST: 171 if (gesture.data.flingStart.velocityX < 0) 172 return false; 173 break; 174 case OVERSCROLL_WEST: 175 if (gesture.data.flingStart.velocityX > 0) 176 return false; 177 break; 178 case OVERSCROLL_NORTH: 179 if (gesture.data.flingStart.velocityY > 0) 180 return false; 181 break; 182 case OVERSCROLL_SOUTH: 183 if (gesture.data.flingStart.velocityY < 0) 184 return false; 185 break; 186 case OVERSCROLL_NONE: 187 case OVERSCROLL_COUNT: 188 NOTREACHED(); 189 } 190 } 191 192 float ratio, threshold; 193 if (overscroll_mode_ == OVERSCROLL_WEST || 194 overscroll_mode_ == OVERSCROLL_EAST) { 195 ratio = fabs(overscroll_delta_x_) / bounds.width(); 196 threshold = GetOverscrollConfig(OVERSCROLL_CONFIG_HORIZ_THRESHOLD_COMPLETE); 197 } else { 198 ratio = fabs(overscroll_delta_y_) / bounds.height(); 199 threshold = GetOverscrollConfig(OVERSCROLL_CONFIG_VERT_THRESHOLD_COMPLETE); 200 } 201 202 return ratio >= threshold; 203} 204 205bool OverscrollController::DispatchEventResetsState( 206 const WebKit::WebInputEvent& event) const { 207 switch (event.type) { 208 case WebKit::WebInputEvent::MouseWheel: { 209 // Only wheel events with precise deltas (i.e. from trackpad) contribute 210 // to the overscroll gesture. 211 const WebKit::WebMouseWheelEvent& wheel = 212 static_cast<const WebKit::WebMouseWheelEvent&>(event); 213 return !wheel.hasPreciseScrollingDeltas; 214 } 215 216 case WebKit::WebInputEvent::GestureScrollUpdate: 217 case WebKit::WebInputEvent::GestureFlingCancel: 218 return false; 219 220 default: 221 // Touch events can arrive during an overscroll gesture initiated by 222 // touch-scrolling. These events should not reset the overscroll state. 223 return !WebKit::WebInputEvent::isTouchEventType(event.type); 224 } 225} 226 227void OverscrollController::ProcessEventForOverscroll( 228 const WebKit::WebInputEvent& event) { 229 switch (event.type) { 230 case WebKit::WebInputEvent::MouseWheel: { 231 const WebKit::WebMouseWheelEvent& wheel = 232 static_cast<const WebKit::WebMouseWheelEvent&>(event); 233 if (!wheel.hasPreciseScrollingDeltas) 234 return; 235 236 ProcessOverscroll(wheel.deltaX * wheel.accelerationRatioX, 237 wheel.deltaY * wheel.accelerationRatioY); 238 break; 239 } 240 case WebKit::WebInputEvent::GestureScrollUpdate: { 241 const WebKit::WebGestureEvent& gesture = 242 static_cast<const WebKit::WebGestureEvent&>(event); 243 ProcessOverscroll(gesture.data.scrollUpdate.deltaX, 244 gesture.data.scrollUpdate.deltaY); 245 break; 246 } 247 case WebKit::WebInputEvent::GestureFlingStart: { 248 const float kFlingVelocityThreshold = 1100.f; 249 const WebKit::WebGestureEvent& gesture = 250 static_cast<const WebKit::WebGestureEvent&>(event); 251 float velocity_x = gesture.data.flingStart.velocityX; 252 float velocity_y = gesture.data.flingStart.velocityY; 253 if (fabs(velocity_x) > kFlingVelocityThreshold) { 254 if ((overscroll_mode_ == OVERSCROLL_WEST && velocity_x < 0) || 255 (overscroll_mode_ == OVERSCROLL_EAST && velocity_x > 0)) { 256 CompleteAction(); 257 break; 258 } 259 } else if (fabs(velocity_y) > kFlingVelocityThreshold) { 260 if ((overscroll_mode_ == OVERSCROLL_NORTH && velocity_y < 0) || 261 (overscroll_mode_ == OVERSCROLL_SOUTH && velocity_y > 0)) { 262 CompleteAction(); 263 break; 264 } 265 } 266 267 // Reset overscroll state if fling didn't complete the overscroll gesture. 268 SetOverscrollMode(OVERSCROLL_NONE); 269 break; 270 } 271 272 default: 273 DCHECK(WebKit::WebInputEvent::isGestureEventType(event.type) || 274 WebKit::WebInputEvent::isTouchEventType(event.type)) 275 << "Received unexpected event: " << event.type; 276 } 277} 278 279void OverscrollController::ProcessOverscroll(float delta_x, float delta_y) { 280 if (scroll_state_ == STATE_CONTENT_SCROLLING) 281 return; 282 overscroll_delta_x_ += delta_x; 283 overscroll_delta_y_ += delta_y; 284 285 float threshold = GetOverscrollConfig(OVERSCROLL_CONFIG_MIN_THRESHOLD_START); 286 if (fabs(overscroll_delta_x_) < threshold && 287 fabs(overscroll_delta_y_) < threshold) { 288 SetOverscrollMode(OVERSCROLL_NONE); 289 return; 290 } 291 292 // Compute the current overscroll direction. If the direction is different 293 // from the current direction, then always switch to no-overscroll mode first 294 // to make sure that subsequent scroll events go through to the page first. 295 OverscrollMode new_mode = OVERSCROLL_NONE; 296 const float kMinRatio = 2.5; 297 if (fabs(overscroll_delta_x_) > fabs(overscroll_delta_y_) * kMinRatio) 298 new_mode = overscroll_delta_x_ > 0.f ? OVERSCROLL_EAST : OVERSCROLL_WEST; 299 else if (fabs(overscroll_delta_y_) > fabs(overscroll_delta_x_) * kMinRatio) 300 new_mode = overscroll_delta_y_ > 0.f ? OVERSCROLL_SOUTH : OVERSCROLL_NORTH; 301 302 // The vertical oversrcoll currently does not have any UX effects, which can 303 // be confusing to users. So disable vertical overscroll for now. 304 // (http://crbug.com/243551 and http://crbug.com/151356). 305 if (new_mode == OVERSCROLL_SOUTH || new_mode == OVERSCROLL_NORTH) 306 new_mode = OVERSCROLL_NONE; 307 308 if (overscroll_mode_ == OVERSCROLL_NONE) { 309 SetOverscrollMode(new_mode); 310 } else if (new_mode != overscroll_mode_) { 311 SetOverscrollMode(OVERSCROLL_NONE); 312 return; 313 } 314 315 // Tell the delegate about the overscroll update so that it can update 316 // the display accordingly (e.g. show history preview etc.). 317 if (delegate_) { 318 // Do not include the threshold amount when sending the deltas to the 319 // delegate. 320 float delegate_delta_x = overscroll_delta_x_; 321 if (fabs(delegate_delta_x) > threshold) { 322 if (delegate_delta_x < 0) 323 delegate_delta_x += threshold; 324 else 325 delegate_delta_x -= threshold; 326 } else { 327 delegate_delta_x = 0.f; 328 } 329 330 float delegate_delta_y = overscroll_delta_y_; 331 if (fabs(delegate_delta_y) > threshold) { 332 if (delegate_delta_y < 0) 333 delegate_delta_y += threshold; 334 else 335 delegate_delta_y -= threshold; 336 } else { 337 delegate_delta_y = 0.f; 338 } 339 delegate_->OnOverscrollUpdate(delegate_delta_x, delegate_delta_y); 340 } 341} 342 343void OverscrollController::CompleteAction() { 344 if (delegate_) 345 delegate_->OnOverscrollComplete(overscroll_mode_); 346 overscroll_mode_ = OVERSCROLL_NONE; 347 overscroll_delta_x_ = overscroll_delta_y_ = 0.f; 348} 349 350void OverscrollController::SetOverscrollMode(OverscrollMode mode) { 351 if (overscroll_mode_ == mode) 352 return; 353 OverscrollMode old_mode = overscroll_mode_; 354 overscroll_mode_ = mode; 355 if (overscroll_mode_ == OVERSCROLL_NONE) 356 overscroll_delta_x_ = overscroll_delta_y_ = 0.f; 357 else 358 scroll_state_ = STATE_OVERSCROLLING; 359 if (delegate_) 360 delegate_->OnOverscrollModeChange(old_mode, overscroll_mode_); 361} 362 363bool OverscrollController::ShouldForwardToHost( 364 const WebKit::WebInputEvent& event) const { 365 if (!WebKit::WebInputEvent::isGestureEventType(event.type)) 366 return false; 367 368 // If the RenderWidgetHost already processed this event, then the event must 369 // not be sent again. 370 return !render_widget_host_->HasQueuedGestureEvents(); 371} 372 373} // namespace content 374