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 12 namespace content { 13 14 OverscrollController::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 24 OverscrollController::~OverscrollController() { 25 } 26 27 bool 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 108 void 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 124 void 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 133 void OverscrollController::Reset() { 134 overscroll_mode_ = OVERSCROLL_NONE; 135 overscroll_delta_x_ = overscroll_delta_y_ = 0.f; 136 scroll_state_ = STATE_UNKNOWN; 137 } 138 139 void OverscrollController::Cancel() { 140 SetOverscrollMode(OVERSCROLL_NONE); 141 overscroll_delta_x_ = overscroll_delta_y_ = 0.f; 142 scroll_state_ = STATE_UNKNOWN; 143 } 144 145 bool 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 205 bool 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 227 void 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 279 void OverscrollController::ProcessOverscroll(float delta_x, float delta_y) { 280 if (scroll_state_ != STATE_CONTENT_SCROLLING) 281 overscroll_delta_x_ += delta_x; 282 overscroll_delta_y_ += delta_y; 283 284 float horiz_threshold = GetOverscrollConfig( 285 OVERSCROLL_CONFIG_HORIZ_THRESHOLD_START); 286 float vert_threshold = GetOverscrollConfig( 287 OVERSCROLL_CONFIG_VERT_THRESHOLD_START); 288 if (fabs(overscroll_delta_x_) <= horiz_threshold && 289 fabs(overscroll_delta_y_) <= vert_threshold) { 290 SetOverscrollMode(OVERSCROLL_NONE); 291 return; 292 } 293 294 // Compute the current overscroll direction. If the direction is different 295 // from the current direction, then always switch to no-overscroll mode first 296 // to make sure that subsequent scroll events go through to the page first. 297 OverscrollMode new_mode = OVERSCROLL_NONE; 298 const float kMinRatio = 2.5; 299 if (fabs(overscroll_delta_x_) > horiz_threshold && 300 fabs(overscroll_delta_x_) > fabs(overscroll_delta_y_) * kMinRatio) 301 new_mode = overscroll_delta_x_ > 0.f ? OVERSCROLL_EAST : OVERSCROLL_WEST; 302 else if (fabs(overscroll_delta_y_) > vert_threshold && 303 fabs(overscroll_delta_y_) > fabs(overscroll_delta_x_) * kMinRatio) 304 new_mode = overscroll_delta_y_ > 0.f ? OVERSCROLL_SOUTH : OVERSCROLL_NORTH; 305 306 if (overscroll_mode_ == OVERSCROLL_NONE) 307 SetOverscrollMode(new_mode); 308 else if (new_mode != overscroll_mode_) 309 SetOverscrollMode(OVERSCROLL_NONE); 310 311 if (overscroll_mode_ == OVERSCROLL_NONE) 312 return; 313 314 // Tell the delegate about the overscroll update so that it can update 315 // the display accordingly (e.g. show history preview etc.). 316 if (delegate_) { 317 // Do not include the threshold amount when sending the deltas to the 318 // delegate. 319 float delegate_delta_x = overscroll_delta_x_; 320 if (fabs(delegate_delta_x) > horiz_threshold) { 321 if (delegate_delta_x < 0) 322 delegate_delta_x += horiz_threshold; 323 else 324 delegate_delta_x -= horiz_threshold; 325 } else { 326 delegate_delta_x = 0.f; 327 } 328 329 float delegate_delta_y = overscroll_delta_y_; 330 if (fabs(delegate_delta_y) > vert_threshold) { 331 if (delegate_delta_y < 0) 332 delegate_delta_y += vert_threshold; 333 else 334 delegate_delta_y -= vert_threshold; 335 } else { 336 delegate_delta_y = 0.f; 337 } 338 delegate_->OnOverscrollUpdate(delegate_delta_x, delegate_delta_y); 339 } 340 } 341 342 void OverscrollController::CompleteAction() { 343 if (delegate_) 344 delegate_->OnOverscrollComplete(overscroll_mode_); 345 overscroll_mode_ = OVERSCROLL_NONE; 346 overscroll_delta_x_ = overscroll_delta_y_ = 0.f; 347 } 348 349 void OverscrollController::SetOverscrollMode(OverscrollMode mode) { 350 if (overscroll_mode_ == mode) 351 return; 352 OverscrollMode old_mode = overscroll_mode_; 353 overscroll_mode_ = mode; 354 if (overscroll_mode_ == OVERSCROLL_NONE) 355 overscroll_delta_x_ = overscroll_delta_y_ = 0.f; 356 else 357 scroll_state_ = STATE_OVERSCROLLING; 358 if (delegate_) 359 delegate_->OnOverscrollModeChange(old_mode, overscroll_mode_); 360 } 361 362 bool OverscrollController::ShouldForwardToHost( 363 const WebKit::WebInputEvent& event) const { 364 if (!WebKit::WebInputEvent::isGestureEventType(event.type)) 365 return false; 366 367 // If the RenderWidgetHost already processed this event, then the event must 368 // not be sent again. 369 return !render_widget_host_->HasQueuedGestureEvents(); 370 } 371 372 } // namespace content 373