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