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