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