Home | History | Annotate | Download | only in aura
      1 // Copyright 2014 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/web_contents/aura/overscroll_navigation_overlay.h"
      6 
      7 #include "content/browser/frame_host/navigation_entry_impl.h"
      8 #include "content/browser/renderer_host/render_view_host_impl.h"
      9 #include "content/browser/web_contents/aura/image_window_delegate.h"
     10 #include "content/browser/web_contents/web_contents_impl.h"
     11 #include "content/common/view_messages.h"
     12 #include "content/public/browser/browser_thread.h"
     13 #include "content/public/browser/render_widget_host_view.h"
     14 #include "ui/aura/window.h"
     15 #include "ui/base/layout.h"
     16 #include "ui/compositor/layer.h"
     17 #include "ui/compositor/layer_animation_observer.h"
     18 #include "ui/compositor/scoped_layer_animation_settings.h"
     19 #include "ui/gfx/canvas.h"
     20 #include "ui/gfx/image/image_png_rep.h"
     21 #include "ui/gfx/image/image_skia.h"
     22 
     23 namespace content {
     24 
     25 // A LayerDelegate that paints an image for the layer.
     26 class ImageLayerDelegate : public ui::LayerDelegate {
     27  public:
     28   ImageLayerDelegate() {}
     29 
     30   virtual ~ImageLayerDelegate() {}
     31 
     32   void SetImage(const gfx::Image& image) {
     33     image_ = image;
     34     image_size_ = image.AsImageSkia().size();
     35   }
     36   const gfx::Image& image() const { return image_; }
     37 
     38  private:
     39   // Overridden from ui::LayerDelegate:
     40   virtual void OnPaintLayer(gfx::Canvas* canvas) OVERRIDE {
     41     if (image_.IsEmpty()) {
     42       canvas->DrawColor(SK_ColorGRAY);
     43     } else {
     44       SkISize size = canvas->sk_canvas()->getDeviceSize();
     45       if (size.width() != image_size_.width() ||
     46           size.height() != image_size_.height()) {
     47         canvas->DrawColor(SK_ColorWHITE);
     48       }
     49       canvas->DrawImageInt(image_.AsImageSkia(), 0, 0);
     50     }
     51   }
     52 
     53   // Called when the layer's device scale factor has changed.
     54   virtual void OnDeviceScaleFactorChanged(float device_scale_factor) OVERRIDE {
     55   }
     56 
     57   // Invoked prior to the bounds changing. The returned closured is run after
     58   // the bounds change.
     59   virtual base::Closure PrepareForLayerBoundsChange() OVERRIDE {
     60     return base::Closure();
     61   }
     62 
     63   gfx::Image image_;
     64   gfx::Size image_size_;
     65 
     66   DISALLOW_COPY_AND_ASSIGN(ImageLayerDelegate);
     67 };
     68 
     69 // Responsible for fading out and deleting the layer of the overlay window.
     70 class OverlayDismissAnimator
     71     : public ui::LayerAnimationObserver {
     72  public:
     73   // Takes ownership of the layer.
     74   explicit OverlayDismissAnimator(scoped_ptr<ui::Layer> layer)
     75       : layer_(layer.Pass()) {
     76     CHECK(layer_.get());
     77   }
     78 
     79   // Starts the fadeout animation on the layer. When the animation finishes,
     80   // the object deletes itself along with the layer.
     81   void Animate() {
     82     DCHECK(layer_.get());
     83     ui::LayerAnimator* animator = layer_->GetAnimator();
     84     // This makes SetOpacity() animate with default duration (which could be
     85     // zero, e.g. when running tests).
     86     ui::ScopedLayerAnimationSettings settings(animator);
     87     animator->AddObserver(this);
     88     layer_->SetOpacity(0);
     89   }
     90 
     91   // Overridden from ui::LayerAnimationObserver
     92   virtual void OnLayerAnimationEnded(
     93       ui::LayerAnimationSequence* sequence) OVERRIDE {
     94     delete this;
     95   }
     96 
     97   virtual void OnLayerAnimationAborted(
     98       ui::LayerAnimationSequence* sequence) OVERRIDE {
     99     delete this;
    100   }
    101 
    102   virtual void OnLayerAnimationScheduled(
    103       ui::LayerAnimationSequence* sequence) OVERRIDE {}
    104 
    105  private:
    106   virtual ~OverlayDismissAnimator() {}
    107 
    108   scoped_ptr<ui::Layer> layer_;
    109 
    110   DISALLOW_COPY_AND_ASSIGN(OverlayDismissAnimator);
    111 };
    112 
    113 OverscrollNavigationOverlay::OverscrollNavigationOverlay(
    114     WebContentsImpl* web_contents)
    115     : web_contents_(web_contents),
    116       image_delegate_(NULL),
    117       loading_complete_(false),
    118       received_paint_update_(false),
    119       pending_entry_id_(0),
    120       slide_direction_(SLIDE_UNKNOWN) {
    121 }
    122 
    123 OverscrollNavigationOverlay::~OverscrollNavigationOverlay() {
    124 }
    125 
    126 void OverscrollNavigationOverlay::StartObserving() {
    127   loading_complete_ = false;
    128   received_paint_update_ = false;
    129   overlay_dismiss_layer_.reset();
    130   pending_entry_id_ = 0;
    131   Observe(web_contents_);
    132 
    133   // Make sure the overlay window is on top.
    134   if (window_.get() && window_->parent())
    135     window_->parent()->StackChildAtTop(window_.get());
    136 
    137   // Assumes the navigation has been initiated.
    138   NavigationEntry* pending_entry =
    139       web_contents_->GetController().GetPendingEntry();
    140   // Save id of the pending entry to identify when it loads and paints later.
    141   // Under some circumstances navigation can leave a null pending entry -
    142   // see comments in NavigationControllerImpl::NavigateToPendingEntry().
    143   pending_entry_id_ = pending_entry ? pending_entry->GetUniqueID() : 0;
    144 }
    145 
    146 void OverscrollNavigationOverlay::SetOverlayWindow(
    147     scoped_ptr<aura::Window> window,
    148     ImageWindowDelegate* delegate) {
    149   window_ = window.Pass();
    150   if (window_.get() && window_->parent())
    151     window_->parent()->StackChildAtTop(window_.get());
    152   image_delegate_ = delegate;
    153 
    154   if (window_.get() && delegate->has_image()) {
    155     window_slider_.reset(new WindowSlider(this,
    156                                           window_->parent(),
    157                                           window_.get()));
    158     slide_direction_ = SLIDE_UNKNOWN;
    159   } else {
    160     window_slider_.reset();
    161   }
    162 }
    163 
    164 void OverscrollNavigationOverlay::StopObservingIfDone() {
    165   // Normally we dismiss the overlay once we receive a paint update, however
    166   // for in-page navigations DidFirstVisuallyNonEmptyPaint() does not get
    167   // called, and we rely on loading_complete_ for those cases.
    168   if (!received_paint_update_ && !loading_complete_)
    169     return;
    170 
    171   // If a slide is in progress, then do not destroy the window or the slide.
    172   if (window_slider_.get() && window_slider_->IsSlideInProgress())
    173     return;
    174 
    175   // The layer to be animated by OverlayDismissAnimator
    176   scoped_ptr<ui::Layer> overlay_dismiss_layer;
    177   if (overlay_dismiss_layer_)
    178     overlay_dismiss_layer = overlay_dismiss_layer_.Pass();
    179   else if (window_.get())
    180     overlay_dismiss_layer = window_->AcquireLayer();
    181   Observe(NULL);
    182   window_slider_.reset();
    183   window_.reset();
    184   image_delegate_ = NULL;
    185   if (overlay_dismiss_layer.get()) {
    186     // OverlayDismissAnimator deletes overlay_dismiss_layer and itself when the
    187     // animation completes.
    188     (new OverlayDismissAnimator(overlay_dismiss_layer.Pass()))->Animate();
    189   }
    190 }
    191 
    192 ui::Layer* OverscrollNavigationOverlay::CreateSlideLayer(int offset) {
    193   const NavigationControllerImpl& controller = web_contents_->GetController();
    194   const NavigationEntryImpl* entry = NavigationEntryImpl::FromNavigationEntry(
    195       controller.GetEntryAtOffset(offset));
    196 
    197   gfx::Image image;
    198   if (entry && entry->screenshot().get()) {
    199     std::vector<gfx::ImagePNGRep> image_reps;
    200     image_reps.push_back(gfx::ImagePNGRep(entry->screenshot(),
    201         ui::GetScaleFactorForNativeView(window_.get())));
    202     image = gfx::Image(image_reps);
    203   }
    204   if (!layer_delegate_)
    205     layer_delegate_.reset(new ImageLayerDelegate());
    206   layer_delegate_->SetImage(image);
    207 
    208   ui::Layer* layer = new ui::Layer(ui::LAYER_TEXTURED);
    209   layer->set_delegate(layer_delegate_.get());
    210   return layer;
    211 }
    212 
    213 ui::Layer* OverscrollNavigationOverlay::CreateBackLayer() {
    214   if (!web_contents_->GetController().CanGoBack())
    215     return NULL;
    216   slide_direction_ = SLIDE_BACK;
    217   return CreateSlideLayer(-1);
    218 }
    219 
    220 ui::Layer* OverscrollNavigationOverlay::CreateFrontLayer() {
    221   if (!web_contents_->GetController().CanGoForward())
    222     return NULL;
    223   slide_direction_ = SLIDE_FRONT;
    224   return CreateSlideLayer(1);
    225 }
    226 
    227 void OverscrollNavigationOverlay::OnWindowSlideCompleting() {
    228   if (slide_direction_ == SLIDE_UNKNOWN)
    229     return;
    230 
    231   // Perform the navigation.
    232   if (slide_direction_ == SLIDE_BACK)
    233     web_contents_->GetController().GoBack();
    234   else if (slide_direction_ == SLIDE_FRONT)
    235     web_contents_->GetController().GoForward();
    236   else
    237     NOTREACHED();
    238 
    239   // Reset state and wait for the new navigation page to complete
    240   // loading/painting.
    241   StartObserving();
    242 }
    243 
    244 void OverscrollNavigationOverlay::OnWindowSlideCompleted(
    245     scoped_ptr<ui::Layer> layer) {
    246   if (slide_direction_ == SLIDE_UNKNOWN) {
    247     window_slider_.reset();
    248     StopObservingIfDone();
    249     return;
    250   }
    251 
    252   // Change the image used for the overlay window.
    253   image_delegate_->SetImage(layer_delegate_->image());
    254   window_->layer()->SetTransform(gfx::Transform());
    255   window_->SchedulePaintInRect(gfx::Rect(window_->bounds().size()));
    256   slide_direction_ = SLIDE_UNKNOWN;
    257   // We may end up dismissing the overlay before it has a chance to repaint, so
    258   // set the slider layer to be the one animated by OverlayDismissAnimator.
    259   if (layer.get())
    260     overlay_dismiss_layer_ = layer.Pass();
    261   StopObservingIfDone();
    262 }
    263 
    264 void OverscrollNavigationOverlay::OnWindowSlideAborted() {
    265   StopObservingIfDone();
    266 }
    267 
    268 void OverscrollNavigationOverlay::OnWindowSliderDestroyed() {
    269   // We only want to take an action here if WindowSlider is being destroyed
    270   // outside of OverscrollNavigationOverlay. If window_slider_.get() is NULL,
    271   // then OverscrollNavigationOverlay is the one destroying WindowSlider, and
    272   // we don't need to do anything.
    273   // This check prevents StopObservingIfDone() being called multiple times
    274   // (including recursively) for a single event.
    275   if (window_slider_.get()) {
    276     // The slider has just been destroyed. Release the ownership.
    277     WindowSlider* slider ALLOW_UNUSED = window_slider_.release();
    278     StopObservingIfDone();
    279   }
    280 }
    281 
    282 void OverscrollNavigationOverlay::DidFirstVisuallyNonEmptyPaint() {
    283   int visible_entry_id =
    284       web_contents_->GetController().GetVisibleEntry()->GetUniqueID();
    285   if (visible_entry_id == pending_entry_id_ || !pending_entry_id_) {
    286     received_paint_update_ = true;
    287     StopObservingIfDone();
    288   }
    289 }
    290 
    291 void OverscrollNavigationOverlay::DidStopLoading(RenderViewHost* host) {
    292   // Use the last committed entry rather than the active one, in case a
    293   // pending entry has been created.
    294   int committed_entry_id =
    295       web_contents_->GetController().GetLastCommittedEntry()->GetUniqueID();
    296   if (committed_entry_id == pending_entry_id_ || !pending_entry_id_) {
    297     loading_complete_ = true;
    298     StopObservingIfDone();
    299   }
    300 }
    301 
    302 }  // namespace content
    303