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 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