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