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