| Index: content/browser/android/overscroll_controller_android.cc
|
| diff --git a/content/browser/android/overscroll_controller_android.cc b/content/browser/android/overscroll_controller_android.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..db35ea49b6584653636c33e8241022b48dcd9c56
|
| --- /dev/null
|
| +++ b/content/browser/android/overscroll_controller_android.cc
|
| @@ -0,0 +1,227 @@
|
| +// Copyright 2014 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#include "content/browser/android/overscroll_controller_android.h"
|
| +
|
| +#include "base/android/build_info.h"
|
| +#include "base/bind.h"
|
| +#include "cc/layers/layer.h"
|
| +#include "cc/output/compositor_frame_metadata.h"
|
| +#include "content/browser/android/edge_effect.h"
|
| +#include "content/browser/android/edge_effect_l.h"
|
| +#include "content/common/input/did_overscroll_params.h"
|
| +#include "content/public/browser/web_contents.h"
|
| +#include "third_party/WebKit/public/web/WebInputEvent.h"
|
| +#include "ui/base/android/window_android_compositor.h"
|
| +
|
| +namespace content {
|
| +namespace {
|
| +
|
| +// Used for conditional creation of EdgeEffect types for the overscroll glow.
|
| +const int kAndroidLSDKVersion = 21;
|
| +
|
| +scoped_ptr<EdgeEffectBase> CreateGlowEdgeEffect(
|
| + ui::SystemUIResourceManager* resource_manager,
|
| + float dpi_scale) {
|
| + DCHECK(resource_manager);
|
| + static bool use_l_flavoured_effect =
|
| + base::android::BuildInfo::GetInstance()->sdk_int() >= kAndroidLSDKVersion;
|
| + if (use_l_flavoured_effect)
|
| + return scoped_ptr<EdgeEffectBase>(new EdgeEffectL(resource_manager));
|
| +
|
| + return scoped_ptr<EdgeEffectBase>(
|
| + new EdgeEffect(resource_manager, dpi_scale));
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +OverscrollControllerAndroid::OverscrollControllerAndroid(
|
| + WebContents* web_contents,
|
| + ui::WindowAndroidCompositor* compositor,
|
| + float dpi_scale)
|
| + : WebContentsObserver(web_contents),
|
| + compositor_(compositor),
|
| + dpi_scale_(dpi_scale),
|
| + enabled_(true),
|
| + glow_effect_(base::Bind(&CreateGlowEdgeEffect,
|
| + &compositor->GetSystemUIResourceManager(),
|
| + dpi_scale_)),
|
| + refresh_effect_(&compositor->GetSystemUIResourceManager(), this),
|
| + triggered_refresh_active_(false) {
|
| + DCHECK(web_contents);
|
| +}
|
| +
|
| +OverscrollControllerAndroid::~OverscrollControllerAndroid() {
|
| +}
|
| +
|
| +bool OverscrollControllerAndroid::WillHandleGestureEvent(
|
| + const blink::WebGestureEvent& event) {
|
| + if (!enabled_)
|
| + return false;
|
| +
|
| + bool handled = false;
|
| + bool maybe_needs_animate = false;
|
| + switch (event.type) {
|
| + case blink::WebInputEvent::GestureScrollBegin:
|
| + refresh_effect_.OnScrollBegin();
|
| + break;
|
| +
|
| + case blink::WebInputEvent::GestureScrollUpdate: {
|
| + gfx::Vector2dF scroll_delta(event.data.scrollUpdate.deltaX,
|
| + event.data.scrollUpdate.deltaY);
|
| + scroll_delta.Scale(dpi_scale_);
|
| + maybe_needs_animate = true;
|
| + handled = refresh_effect_.WillHandleScrollUpdate(scroll_delta);
|
| + } break;
|
| +
|
| + case blink::WebInputEvent::GestureScrollEnd:
|
| + refresh_effect_.OnScrollEnd(gfx::Vector2dF());
|
| + maybe_needs_animate = true;
|
| + break;
|
| +
|
| + case blink::WebInputEvent::GestureFlingStart: {
|
| + gfx::Vector2dF scroll_velocity(event.data.flingStart.velocityX,
|
| + event.data.flingStart.velocityY);
|
| + scroll_velocity.Scale(dpi_scale_);
|
| + refresh_effect_.OnScrollEnd(scroll_velocity);
|
| + if (refresh_effect_.IsActive()) {
|
| + // TODO(jdduke): Figure out a cleaner way of suppressing a fling.
|
| + // It's important that the any downstream code sees a scroll-ending
|
| + // event (in this case GestureFlingStart) if it has seen a scroll begin.
|
| + // Thus, we cannot simply consume the fling. Changing the event type to
|
| + // a GestureScrollEnd might work in practice, but could lead to
|
| + // unexpected results. For now, simply truncate the fling velocity, but
|
| + // not to zero as downstream code may not expect a zero-velocity fling.
|
| + blink::WebGestureEvent& modified_event =
|
| + const_cast<blink::WebGestureEvent&>(event);
|
| + modified_event.data.flingStart.velocityX = .01f;
|
| + modified_event.data.flingStart.velocityY = .01f;
|
| + }
|
| + maybe_needs_animate = true;
|
| + } break;
|
| +
|
| + default:
|
| + break;
|
| + }
|
| +
|
| + if (maybe_needs_animate && refresh_effect_.IsActive())
|
| + SetNeedsAnimate();
|
| +
|
| + return handled;
|
| +}
|
| +
|
| +void OverscrollControllerAndroid::OnGestureEventAck(
|
| + const blink::WebGestureEvent& event,
|
| + InputEventAckState ack_result) {
|
| + if (!enabled_)
|
| + return;
|
| +
|
| + // The overscroll effect requires an explicit release signal that may not be
|
| + // sent from the renderer compositor.
|
| + if (event.type == blink::WebInputEvent::GestureScrollEnd ||
|
| + event.type == blink::WebInputEvent::GestureFlingStart) {
|
| + OnOverscrolled(DidOverscrollParams());
|
| + }
|
| +
|
| + if (event.type == blink::WebInputEvent::GestureScrollUpdate) {
|
| + // The effect should only be allowed if both the causal touch events go
|
| + // unconsumed and the generated scroll events go unconsumed.
|
| + // TODO(jdduke): Prevent activation if the first touchmove was consumed,
|
| + // i.e., the first GSU was prevented.
|
| + bool consumed = ack_result == INPUT_EVENT_ACK_STATE_CONSUMED;
|
| + refresh_effect_.OnScrollUpdateAck(consumed);
|
| + }
|
| +}
|
| +
|
| +void OverscrollControllerAndroid::OnOverscrolled(
|
| + const DidOverscrollParams& params) {
|
| + if (!enabled_)
|
| + return;
|
| +
|
| + if (refresh_effect_.IsActive() ||
|
| + refresh_effect_.IsAwaitingScrollUpdateAck()) {
|
| + // An active (or potentially active) refresh effect should always pre-empt
|
| + // the passive glow effect.
|
| + return;
|
| + }
|
| +
|
| + if (glow_effect_.OnOverscrolled(
|
| + base::TimeTicks::Now(),
|
| + gfx::ScaleVector2d(params.accumulated_overscroll, dpi_scale_),
|
| + gfx::ScaleVector2d(params.latest_overscroll_delta, dpi_scale_),
|
| + gfx::ScaleVector2d(params.current_fling_velocity, dpi_scale_),
|
| + gfx::ScaleVector2d(params.causal_event_viewport_point.OffsetFromOrigin(),
|
| + dpi_scale_))) {
|
| + SetNeedsAnimate();
|
| + }
|
| +}
|
| +
|
| +bool OverscrollControllerAndroid::Animate(base::TimeTicks current_time,
|
| + cc::Layer* parent_layer) {
|
| + DCHECK(parent_layer);
|
| + if (!enabled_)
|
| + return false;
|
| +
|
| + bool needs_animate = refresh_effect_.Animate(current_time, parent_layer);
|
| + needs_animate |= glow_effect_.Animate(current_time, parent_layer);
|
| + return needs_animate;
|
| +}
|
| +
|
| +void OverscrollControllerAndroid::OnFrameMetadataUpdated(
|
| + const cc::CompositorFrameMetadata& frame_metadata) {
|
| + const float scale_factor =
|
| + frame_metadata.page_scale_factor * frame_metadata.device_scale_factor;
|
| + gfx::SizeF viewport_size =
|
| + gfx::ScaleSize(frame_metadata.scrollable_viewport_size, scale_factor);
|
| + gfx::SizeF content_size =
|
| + gfx::ScaleSize(frame_metadata.root_layer_size, scale_factor);
|
| + gfx::Vector2dF content_scroll_offset =
|
| + gfx::ScaleVector2d(frame_metadata.root_scroll_offset, scale_factor);
|
| +
|
| + refresh_effect_.UpdateDisplay(viewport_size, content_scroll_offset);
|
| + glow_effect_.UpdateDisplay(viewport_size, content_size,
|
| + content_scroll_offset);
|
| +}
|
| +
|
| +void OverscrollControllerAndroid::Enable() {
|
| + enabled_ = true;
|
| +}
|
| +
|
| +void OverscrollControllerAndroid::Disable() {
|
| + if (!enabled_)
|
| + return;
|
| + enabled_ = false;
|
| + if (!enabled_) {
|
| + refresh_effect_.Reset();
|
| + glow_effect_.Reset();
|
| + }
|
| +}
|
| +
|
| +void OverscrollControllerAndroid::DidNavigateMainFrame(
|
| + const LoadCommittedDetails& details,
|
| + const FrameNavigateParams& params) {
|
| + // Once the main frame has navigated, there's little need to further animate
|
| + // the reload effect. Note that the effect will naturally time out should the
|
| + // reload be interruped for any reason.
|
| + triggered_refresh_active_ = false;
|
| +}
|
| +
|
| +void OverscrollControllerAndroid::TriggerRefresh() {
|
| + triggered_refresh_active_ = false;
|
| + if (!web_contents())
|
| + return;
|
| +
|
| + triggered_refresh_active_ = true;
|
| + web_contents()->ReloadFocusedFrame(false);
|
| +}
|
| +
|
| +bool OverscrollControllerAndroid::IsStillRefreshing() const {
|
| + return triggered_refresh_active_;
|
| +}
|
| +
|
| +void OverscrollControllerAndroid::SetNeedsAnimate() {
|
| + compositor_->SetNeedsAnimate();
|
| +}
|
| +
|
| +} // namespace content
|
|
|