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