Home | History | Annotate | Download | only in renderer_host
      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 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 
    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 
    266 void 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 
    339 void 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 
    346 void 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