// 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 #include "base/i18n/rtl.h" #include "base/metrics/histogram_macros.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/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& redirect_chain = entry->GetRedirectChain(); for (std::vector::const_iterator it = redirect_chain.begin(); it != redirect_chain.end(); it++) { if (*it == url) return true; } return false; } } // 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(scoped_ptr layer) : layer_(layer.Pass()) { 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 {} scoped_ptr 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. scoped_ptr dismiss_layer = window_->AcquireLayer(); window_.reset(); (new OverlayDismissAnimator(dismiss_layer.Pass()))->Animate(); Observe(nullptr); received_paint_update_ = false; loading_complete_ = false; } scoped_ptr OverscrollNavigationOverlay::CreateOverlayWindow( const gfx::Rect& bounds) { UMA_HISTOGRAM_ENUMERATION( "Overscroll.Started2", direction_, NAVIGATION_COUNT); OverscrollWindowDelegate* overscroll_delegate = new OverscrollWindowDelegate( owa_.get(), GetImageForDirection(direction_)); scoped_ptr 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.Pass(); } 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 image_reps; image_reps.push_back(gfx::ImagePNGRep(entry->screenshot(), 1.0f)); return gfx::Image(image_reps); } return gfx::Image(); } scoped_ptr OverscrollNavigationOverlay::CreateFrontWindow( const gfx::Rect& bounds) { if (!web_contents_->GetController().CanGoForward()) return nullptr; direction_ = FORWARD; return CreateOverlayWindow(bounds); } scoped_ptr 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( scoped_ptr window) { DCHECK(direction_ != NONE); aura::Window* main_window = GetMainWindow(); if (!main_window) { UMA_HISTOGRAM_ENUMERATION( "Overscroll.Cancelled", direction_, NAVIGATION_COUNT); return; } // 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. UMA_HISTOGRAM_ENUMERATION( "Overscroll.Cancelled", direction_, NAVIGATION_COUNT); loading_complete_ = true; } if (navigated) { UMA_HISTOGRAM_ENUMERATION( "Overscroll.Navigated2", direction_, NAVIGATION_COUNT); StartObserving(); } main_window->SetTransform(gfx::Transform()); window_ = window.Pass(); // 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()); direction_ = NONE; StopObservingIfDone(); } void OverscrollNavigationOverlay::OnOverscrollCancelled() { UMA_HISTOGRAM_ENUMERATION( "Overscroll.Cancelled", direction_, NAVIGATION_COUNT); 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