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