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