blob: 9ed29e924339b8032ffaa67209c409ae64296dca [file] [log] [blame]
// 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/web_contents/aura/overscroll_navigation_overlay.h"
#include <utility>
#include <vector>
#include "base/i18n/rtl.h"
#include "base/macros.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/user_metrics.h"
#include "content/browser/frame_host/navigation_entry_impl.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
#include "content/browser/web_contents/aura/overscroll_window_delegate.h"
#include "content/browser/web_contents/aura/uma_navigation_type.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/common/view_messages.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_widget_host_view.h"
#include "ui/aura/window.h"
#include "ui/base/layout.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_animation_observer.h"
#include "ui/compositor/paint_recorder.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/image/image_png_rep.h"
namespace content {
namespace {
// Returns true if the entry's URL or any of the URLs in entry's redirect chain
// match |url|.
bool DoesEntryMatchURL(NavigationEntry* entry, const GURL& url) {
if (!entry)
return false;
if (entry->GetURL() == url)
return true;
const std::vector<GURL>& redirect_chain = entry->GetRedirectChain();
for (std::vector<GURL>::const_iterator it = redirect_chain.begin();
it != redirect_chain.end();
it++) {
if (*it == url)
return true;
}
return false;
}
UmaNavigationType GetUmaNavigationType(
OverscrollNavigationOverlay::NavigationDirection direction,
OverscrollSource source) {
if (direction == OverscrollNavigationOverlay::NONE ||
source == OverscrollSource::NONE)
return NAVIGATION_TYPE_NONE;
if (direction == OverscrollNavigationOverlay::BACK)
return source == OverscrollSource::TOUCHPAD
? UmaNavigationType::BACK_TOUCHPAD
: UmaNavigationType::BACK_TOUCHSCREEN;
DCHECK_EQ(direction, OverscrollNavigationOverlay::FORWARD);
return source == OverscrollSource::TOUCHPAD
? UmaNavigationType::FORWARD_TOUCHPAD
: UmaNavigationType::FORWARD_TOUCHSCREEN;
}
// Records UMA historgram and also user action for the cancelled overscroll.
void RecordCancelled(OverscrollNavigationOverlay::NavigationDirection direction,
OverscrollSource source) {
UMA_HISTOGRAM_ENUMERATION("Overscroll.Cancelled3",
GetUmaNavigationType(direction, source),
NAVIGATION_TYPE_COUNT);
if (direction == OverscrollNavigationOverlay::BACK)
RecordAction(base::UserMetricsAction("Overscroll_Cancelled.Back"));
else
RecordAction(base::UserMetricsAction("Overscroll_Cancelled.Forward"));
}
} // namespace
// Responsible for fading out and deleting the layer of the overlay window.
class OverlayDismissAnimator
: public ui::LayerAnimationObserver {
public:
// Takes ownership of the layer.
explicit OverlayDismissAnimator(std::unique_ptr<ui::Layer> layer)
: layer_(std::move(layer)) {
CHECK(layer_.get());
}
// Starts the fadeout animation on the layer. When the animation finishes,
// the object deletes itself along with the layer.
void Animate() {
DCHECK(layer_.get());
ui::LayerAnimator* animator = layer_->GetAnimator();
// This makes SetOpacity() animate with default duration (which could be
// zero, e.g. when running tests).
ui::ScopedLayerAnimationSettings settings(animator);
animator->AddObserver(this);
layer_->SetOpacity(0);
}
// Overridden from ui::LayerAnimationObserver
void OnLayerAnimationEnded(ui::LayerAnimationSequence* sequence) override {
delete this;
}
void OnLayerAnimationAborted(ui::LayerAnimationSequence* sequence) override {
delete this;
}
void OnLayerAnimationScheduled(
ui::LayerAnimationSequence* sequence) override {}
private:
~OverlayDismissAnimator() override {}
std::unique_ptr<ui::Layer> layer_;
DISALLOW_COPY_AND_ASSIGN(OverlayDismissAnimator);
};
OverscrollNavigationOverlay::OverscrollNavigationOverlay(
WebContentsImpl* web_contents,
aura::Window* web_contents_window)
: direction_(NONE),
web_contents_(web_contents),
loading_complete_(false),
received_paint_update_(false),
owa_(new OverscrollWindowAnimation(this)),
web_contents_window_(web_contents_window) {
}
OverscrollNavigationOverlay::~OverscrollNavigationOverlay() {
aura::Window* event_window = GetMainWindow();
if (owa_->is_active() && event_window)
event_window->ReleaseCapture();
}
void OverscrollNavigationOverlay::StartObserving() {
loading_complete_ = false;
received_paint_update_ = false;
Observe(web_contents_);
// Assumes the navigation has been initiated.
NavigationEntry* pending_entry =
web_contents_->GetController().GetPendingEntry();
// Save url of the pending entry to identify when it loads and paints later.
// Under some circumstances navigation can leave a null pending entry -
// see comments in NavigationControllerImpl::NavigateToPendingEntry().
pending_entry_url_ = pending_entry ? pending_entry->GetURL() : GURL();
}
void OverscrollNavigationOverlay::StopObservingIfDone() {
// Normally we dismiss the overlay once we receive a paint update, however
// for in-page navigations DidFirstVisuallyNonEmptyPaint() does not get
// called, and we rely on loading_complete_ for those cases.
// If an overscroll gesture is in progress, then do not destroy the window.
if (!window_ || !(loading_complete_ || received_paint_update_) ||
owa_->is_active()) {
return;
}
// OverlayDismissAnimator deletes the dismiss layer and itself when the
// animation completes.
std::unique_ptr<ui::Layer> dismiss_layer = window_->AcquireLayer();
window_.reset();
(new OverlayDismissAnimator(std::move(dismiss_layer)))->Animate();
Observe(nullptr);
received_paint_update_ = false;
loading_complete_ = false;
}
std::unique_ptr<aura::Window> OverscrollNavigationOverlay::CreateOverlayWindow(
const gfx::Rect& bounds) {
UMA_HISTOGRAM_ENUMERATION(
"Overscroll.Started3",
GetUmaNavigationType(direction_, owa_->overscroll_source()),
NAVIGATION_TYPE_COUNT);
OverscrollWindowDelegate* overscroll_delegate = new OverscrollWindowDelegate(
owa_.get(), GetImageForDirection(direction_));
std::unique_ptr<aura::Window> window(new aura::Window(overscroll_delegate));
window->set_owned_by_parent(false);
window->SetTransparent(true);
window->Init(ui::LAYER_TEXTURED);
window->layer()->SetMasksToBounds(false);
window->SetName("OverscrollOverlay");
web_contents_window_->AddChild(window.get());
aura::Window* event_window = GetMainWindow();
if (direction_ == FORWARD)
web_contents_window_->StackChildAbove(window.get(), event_window);
else
web_contents_window_->StackChildBelow(window.get(), event_window);
window->SetBounds(bounds);
// Set capture on the window that is receiving the overscroll events so that
// trackpad scroll gestures keep targetting it even if the mouse pointer moves
// off its bounds.
event_window->SetCapture();
window->Show();
return window;
}
const gfx::Image OverscrollNavigationOverlay::GetImageForDirection(
NavigationDirection direction) const {
const NavigationControllerImpl& controller = web_contents_->GetController();
const NavigationEntryImpl* entry = NavigationEntryImpl::FromNavigationEntry(
controller.GetEntryAtOffset(direction == FORWARD ? 1 : -1));
if (entry && entry->screenshot().get()) {
std::vector<gfx::ImagePNGRep> image_reps;
image_reps.push_back(gfx::ImagePNGRep(entry->screenshot(), 1.0f));
return gfx::Image(image_reps);
}
return gfx::Image();
}
std::unique_ptr<aura::Window> OverscrollNavigationOverlay::CreateFrontWindow(
const gfx::Rect& bounds) {
if (!web_contents_->GetController().CanGoForward())
return nullptr;
direction_ = FORWARD;
return CreateOverlayWindow(bounds);
}
std::unique_ptr<aura::Window> OverscrollNavigationOverlay::CreateBackWindow(
const gfx::Rect& bounds) {
if (!web_contents_->GetController().CanGoBack())
return nullptr;
direction_ = BACK;
return CreateOverlayWindow(bounds);
}
aura::Window* OverscrollNavigationOverlay::GetMainWindow() const {
if (window_)
return window_.get();
return web_contents_->IsBeingDestroyed()
? nullptr
: web_contents_->GetContentNativeView();
}
void OverscrollNavigationOverlay::OnOverscrollCompleting() {
aura::Window* main_window = GetMainWindow();
if (!main_window)
return;
main_window->ReleaseCapture();
}
void OverscrollNavigationOverlay::OnOverscrollCompleted(
std::unique_ptr<aura::Window> window) {
DCHECK(direction_ != NONE);
aura::Window* main_window = GetMainWindow();
if (!main_window) {
RecordCancelled(direction_, owa_->overscroll_source());
return;
}
main_window->SetTransform(gfx::Transform());
window_ = std::move(window);
// Make sure the window is in its default position.
window_->SetBounds(gfx::Rect(web_contents_window_->bounds().size()));
window_->SetTransform(gfx::Transform());
// Make sure the overlay window is on top.
web_contents_window_->StackChildAtTop(window_.get());
// Make sure we can navigate first, as other factors can trigger a navigation
// during an overscroll gesture and navigating without history produces a
// crash.
bool navigated = false;
if (direction_ == FORWARD && web_contents_->GetController().CanGoForward()) {
web_contents_->GetController().GoForward();
navigated = true;
} else if (direction_ == BACK && web_contents_->GetController().CanGoBack()) {
web_contents_->GetController().GoBack();
navigated = true;
} else {
// We need to dismiss the overlay without navigating as soon as the
// overscroll finishes.
RecordCancelled(direction_, owa_->overscroll_source());
loading_complete_ = true;
}
if (navigated) {
UMA_HISTOGRAM_ENUMERATION(
"Overscroll.Navigated3",
GetUmaNavigationType(direction_, owa_->overscroll_source()),
NAVIGATION_TYPE_COUNT);
if (direction_ == BACK)
RecordAction(base::UserMetricsAction("Overscroll_Navigated.Back"));
else
RecordAction(base::UserMetricsAction("Overscroll_Navigated.Forward"));
StartObserving();
}
direction_ = NONE;
StopObservingIfDone();
}
void OverscrollNavigationOverlay::OnOverscrollCancelled() {
RecordCancelled(direction_, owa_->overscroll_source());
aura::Window* main_window = GetMainWindow();
if (!main_window)
return;
main_window->ReleaseCapture();
direction_ = NONE;
StopObservingIfDone();
}
void OverscrollNavigationOverlay::DidFirstVisuallyNonEmptyPaint() {
NavigationEntry* visible_entry =
web_contents_->GetController().GetVisibleEntry();
if (pending_entry_url_.is_empty() ||
DoesEntryMatchURL(visible_entry, pending_entry_url_)) {
received_paint_update_ = true;
StopObservingIfDone();
}
}
void OverscrollNavigationOverlay::DidStopLoading() {
// Don't compare URLs in this case - it's possible they won't match if
// a gesture-nav initiated navigation was interrupted by some other in-site
// navigation (e.g., from a script, or from a bookmark).
loading_complete_ = true;
StopObservingIfDone();
}
} // namespace content