overscroll_controller.cc revision 46d4c2bc3267f3f028f39e7e311b0f89aba2e4fd
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 13using blink::WebInputEvent; 14 15namespace { 16 17bool IsScrollEndEffectEnabled() { 18 return CommandLine::ForCurrentProcess()->GetSwitchValueASCII( 19 switches::kScrollEndEffect) == "1"; 20} 21 22} // namespace 23 24namespace content { 25 26OverscrollController::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 34OverscrollController::~OverscrollController() { 35} 36 37bool 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 88void 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 104void 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 113void OverscrollController::Reset() { 114 overscroll_mode_ = OVERSCROLL_NONE; 115 overscroll_delta_x_ = overscroll_delta_y_ = 0.f; 116 scroll_state_ = STATE_UNKNOWN; 117} 118 119void OverscrollController::Cancel() { 120 SetOverscrollMode(OVERSCROLL_NONE); 121 overscroll_delta_x_ = overscroll_delta_y_ = 0.f; 122 scroll_state_ = STATE_UNKNOWN; 123} 124 125bool 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 184bool 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 206bool 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 216 ProcessOverscroll(wheel.deltaX * wheel.accelerationRatioX, 217 wheel.deltaY * wheel.accelerationRatioY, 218 wheel.type); 219 event_processed = true; 220 break; 221 } 222 case blink::WebInputEvent::GestureScrollUpdate: { 223 const blink::WebGestureEvent& gesture = 224 static_cast<const blink::WebGestureEvent&>(event); 225 ProcessOverscroll(gesture.data.scrollUpdate.deltaX, 226 gesture.data.scrollUpdate.deltaY, 227 gesture.type); 228 event_processed = true; 229 break; 230 } 231 case blink::WebInputEvent::GestureFlingStart: { 232 const float kFlingVelocityThreshold = 1100.f; 233 const blink::WebGestureEvent& gesture = 234 static_cast<const blink::WebGestureEvent&>(event); 235 float velocity_x = gesture.data.flingStart.velocityX; 236 float velocity_y = gesture.data.flingStart.velocityY; 237 if (fabs(velocity_x) > kFlingVelocityThreshold) { 238 if ((overscroll_mode_ == OVERSCROLL_WEST && velocity_x < 0) || 239 (overscroll_mode_ == OVERSCROLL_EAST && velocity_x > 0)) { 240 CompleteAction(); 241 event_processed = true; 242 break; 243 } 244 } else if (fabs(velocity_y) > kFlingVelocityThreshold) { 245 if ((overscroll_mode_ == OVERSCROLL_NORTH && velocity_y < 0) || 246 (overscroll_mode_ == OVERSCROLL_SOUTH && velocity_y > 0)) { 247 CompleteAction(); 248 event_processed = true; 249 break; 250 } 251 } 252 253 // Reset overscroll state if fling didn't complete the overscroll gesture. 254 SetOverscrollMode(OVERSCROLL_NONE); 255 break; 256 } 257 258 default: 259 DCHECK(blink::WebInputEvent::isGestureEventType(event.type) || 260 blink::WebInputEvent::isTouchEventType(event.type)) 261 << "Received unexpected event: " << event.type; 262 } 263 return event_processed; 264} 265 266void OverscrollController::ProcessOverscroll(float delta_x, 267 float delta_y, 268 blink::WebInputEvent::Type type) { 269 if (scroll_state_ != STATE_CONTENT_SCROLLING) 270 overscroll_delta_x_ += delta_x; 271 overscroll_delta_y_ += delta_y; 272 273 float horiz_threshold = GetOverscrollConfig( 274 WebInputEvent::isGestureEventType(type) ? 275 OVERSCROLL_CONFIG_HORIZ_THRESHOLD_START_TOUCHSCREEN : 276 OVERSCROLL_CONFIG_HORIZ_THRESHOLD_START_TOUCHPAD); 277 float vert_threshold = GetOverscrollConfig( 278 OVERSCROLL_CONFIG_VERT_THRESHOLD_START); 279 if (fabs(overscroll_delta_x_) <= horiz_threshold && 280 fabs(overscroll_delta_y_) <= vert_threshold) { 281 SetOverscrollMode(OVERSCROLL_NONE); 282 return; 283 } 284 285 // Compute the current overscroll direction. If the direction is different 286 // from the current direction, then always switch to no-overscroll mode first 287 // to make sure that subsequent scroll events go through to the page first. 288 OverscrollMode new_mode = OVERSCROLL_NONE; 289 const float kMinRatio = 2.5; 290 if (fabs(overscroll_delta_x_) > horiz_threshold && 291 fabs(overscroll_delta_x_) > fabs(overscroll_delta_y_) * kMinRatio) 292 new_mode = overscroll_delta_x_ > 0.f ? OVERSCROLL_EAST : OVERSCROLL_WEST; 293 else if (fabs(overscroll_delta_y_) > vert_threshold && 294 fabs(overscroll_delta_y_) > fabs(overscroll_delta_x_) * kMinRatio) 295 new_mode = overscroll_delta_y_ > 0.f ? OVERSCROLL_SOUTH : OVERSCROLL_NORTH; 296 297 // The vertical oversrcoll currently does not have any UX effects other then 298 // for the scroll end effect, so testing if it is enabled. 299 if ((new_mode == OVERSCROLL_SOUTH || new_mode == OVERSCROLL_NORTH) && 300 !IsScrollEndEffectEnabled()) 301 new_mode = OVERSCROLL_NONE; 302 303 if (overscroll_mode_ == OVERSCROLL_NONE) 304 SetOverscrollMode(new_mode); 305 else if (new_mode != overscroll_mode_) 306 SetOverscrollMode(OVERSCROLL_NONE); 307 308 if (overscroll_mode_ == OVERSCROLL_NONE) 309 return; 310 311 // Tell the delegate about the overscroll update so that it can update 312 // the display accordingly (e.g. show history preview etc.). 313 if (delegate_) { 314 // Do not include the threshold amount when sending the deltas to the 315 // delegate. 316 float delegate_delta_x = overscroll_delta_x_; 317 if (fabs(delegate_delta_x) > horiz_threshold) { 318 if (delegate_delta_x < 0) 319 delegate_delta_x += horiz_threshold; 320 else 321 delegate_delta_x -= horiz_threshold; 322 } else { 323 delegate_delta_x = 0.f; 324 } 325 326 float delegate_delta_y = overscroll_delta_y_; 327 if (fabs(delegate_delta_y) > vert_threshold) { 328 if (delegate_delta_y < 0) 329 delegate_delta_y += vert_threshold; 330 else 331 delegate_delta_y -= vert_threshold; 332 } else { 333 delegate_delta_y = 0.f; 334 } 335 delegate_->OnOverscrollUpdate(delegate_delta_x, delegate_delta_y); 336 } 337} 338 339void OverscrollController::CompleteAction() { 340 if (delegate_) 341 delegate_->OnOverscrollComplete(overscroll_mode_); 342 overscroll_mode_ = OVERSCROLL_NONE; 343 overscroll_delta_x_ = overscroll_delta_y_ = 0.f; 344} 345 346void OverscrollController::SetOverscrollMode(OverscrollMode mode) { 347 if (overscroll_mode_ == mode) 348 return; 349 OverscrollMode old_mode = overscroll_mode_; 350 overscroll_mode_ = mode; 351 if (overscroll_mode_ == OVERSCROLL_NONE) 352 overscroll_delta_x_ = overscroll_delta_y_ = 0.f; 353 else 354 scroll_state_ = STATE_OVERSCROLLING; 355 if (delegate_) 356 delegate_->OnOverscrollModeChange(old_mode, overscroll_mode_); 357} 358 359} // namespace content 360