overscroll_navigation_overlay.cc revision cedac228d2dd51db4b79ea1e72c7f249408ee061
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
23namespace content {
24
25// A LayerDelegate that paints an image for the layer.
26class 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.
70class 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
113OverscrollNavigationOverlay::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      need_paint_update_(true) {
122}
123
124OverscrollNavigationOverlay::~OverscrollNavigationOverlay() {
125}
126
127void OverscrollNavigationOverlay::StartObserving() {
128  loading_complete_ = false;
129  received_paint_update_ = false;
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
146void 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
164void OverscrollNavigationOverlay::SetupForTesting() {
165  need_paint_update_ = false;
166}
167
168void OverscrollNavigationOverlay::StopObservingIfDone() {
169  if ((need_paint_update_ && !received_paint_update_)) {
170    return;
171  }
172
173  // If a slide is in progress, then do not destroy the window or the slide.
174  if (window_slider_.get() && window_slider_->IsSlideInProgress())
175    return;
176
177  scoped_ptr<ui::Layer> layer;
178  if (window_.get())
179    layer = window_->AcquireLayer();
180  Observe(NULL);
181  window_slider_.reset();
182  window_.reset();
183  image_delegate_ = NULL;
184  if (layer.get()) {
185    // OverlayDismissAnimator deletes the layer and itself when the animation
186    // completes.
187    (new OverlayDismissAnimator(layer.Pass()))->Animate();
188  }
189}
190
191ui::Layer* OverscrollNavigationOverlay::CreateSlideLayer(int offset) {
192  const NavigationControllerImpl& controller = web_contents_->GetController();
193  const NavigationEntryImpl* entry = NavigationEntryImpl::FromNavigationEntry(
194      controller.GetEntryAtOffset(offset));
195
196  gfx::Image image;
197  if (entry && entry->screenshot().get()) {
198    std::vector<gfx::ImagePNGRep> image_reps;
199    image_reps.push_back(gfx::ImagePNGRep(entry->screenshot(),
200        ui::GetScaleFactorForNativeView(window_.get())));
201    image = gfx::Image(image_reps);
202  }
203  if (!layer_delegate_)
204    layer_delegate_.reset(new ImageLayerDelegate());
205  layer_delegate_->SetImage(image);
206
207  ui::Layer* layer = new ui::Layer(ui::LAYER_TEXTURED);
208  layer->set_delegate(layer_delegate_.get());
209  return layer;
210}
211
212void OverscrollNavigationOverlay::OnUpdateRect(
213    const ViewHostMsg_UpdateRect_Params& params) {
214  if (loading_complete_ &&
215      ViewHostMsg_UpdateRect_Flags::is_repaint_ack(params.flags)) {
216    NavigationEntry* visible_entry =
217        web_contents_->GetController().GetVisibleEntry();
218    int visible_entry_id = visible_entry ? visible_entry->GetUniqueID() : 0;
219    if (visible_entry_id == pending_entry_id_ || !pending_entry_id_) {
220      // This is a paint update after the page has been loaded. So do not wait
221      // for a 'first non-empty' paint update.
222      received_paint_update_ = true;
223      StopObservingIfDone();
224    }
225  }
226}
227
228ui::Layer* OverscrollNavigationOverlay::CreateBackLayer() {
229  if (!web_contents_->GetController().CanGoBack())
230    return NULL;
231  slide_direction_ = SLIDE_BACK;
232  return CreateSlideLayer(-1);
233}
234
235ui::Layer* OverscrollNavigationOverlay::CreateFrontLayer() {
236  if (!web_contents_->GetController().CanGoForward())
237    return NULL;
238  slide_direction_ = SLIDE_FRONT;
239  return CreateSlideLayer(1);
240}
241
242void OverscrollNavigationOverlay::OnWindowSlideCompleting() {
243  if (slide_direction_ == SLIDE_UNKNOWN)
244    return;
245
246  // Perform the navigation.
247  if (slide_direction_ == SLIDE_BACK)
248    web_contents_->GetController().GoBack();
249  else if (slide_direction_ == SLIDE_FRONT)
250    web_contents_->GetController().GoForward();
251  else
252    NOTREACHED();
253
254  // Reset state and wait for the new navigation page to complete
255  // loading/painting.
256  StartObserving();
257}
258
259void OverscrollNavigationOverlay::OnWindowSlideCompleted() {
260  if (slide_direction_ == SLIDE_UNKNOWN) {
261    window_slider_.reset();
262    StopObservingIfDone();
263    return;
264  }
265
266  // Change the image used for the overlay window.
267  image_delegate_->SetImage(layer_delegate_->image());
268  window_->layer()->SetTransform(gfx::Transform());
269  window_->SchedulePaintInRect(gfx::Rect(window_->bounds().size()));
270  slide_direction_ = SLIDE_UNKNOWN;
271
272  // Make sure the overlay layer is repainted before we dismiss it, otherwise
273  // OverlayDismissAnimator may end up showing the wrong screenshot during the
274  // fadeout animation.
275  if (received_paint_update_ && need_paint_update_) {
276    received_paint_update_ = false;
277    RenderWidgetHost* host =
278        web_contents_->GetRenderWidgetHostView()->GetRenderWidgetHost();
279    RenderViewHostImpl* view_host =
280        static_cast<RenderViewHostImpl*> (RenderViewHost::From(host));
281    view_host->ScheduleComposite();
282  } else if (!need_paint_update_) {
283    StopObservingIfDone();
284  }
285}
286
287void OverscrollNavigationOverlay::OnWindowSlideAborted() {
288  StopObservingIfDone();
289}
290
291void OverscrollNavigationOverlay::OnWindowSliderDestroyed() {
292  // We only want to take an action here if WindowSlider is being destroyed
293  // outside of OverscrollNavigationOverlay. If window_slider_.get() is NULL,
294  // then OverscrollNavigationOverlay is the one destroying WindowSlider, and
295  // we don't need to do anything.
296  // This check prevents StopObservingIfDone() being called multiple times
297  // (including recursively) for a single event.
298  if (window_slider_.get()) {
299    // The slider has just been destroyed. Release the ownership.
300    WindowSlider* slider ALLOW_UNUSED = window_slider_.release();
301    StopObservingIfDone();
302  }
303}
304
305void OverscrollNavigationOverlay::DocumentOnLoadCompletedInMainFrame() {
306  // Use the last committed entry rather than the active one, in case a
307  // pending entry has been created.
308  int committed_entry_id =
309      web_contents_->GetController().GetLastCommittedEntry()->GetUniqueID();
310  // Consider the loading completed once the main frame has loaded.
311  if (committed_entry_id == pending_entry_id_ || !pending_entry_id_) {
312    loading_complete_ = true;
313    StopObservingIfDone();
314  }
315}
316
317void OverscrollNavigationOverlay::DidFirstVisuallyNonEmptyPaint() {
318  int visible_entry_id =
319      web_contents_->GetController().GetVisibleEntry()->GetUniqueID();
320  if (visible_entry_id == pending_entry_id_ || !pending_entry_id_) {
321    received_paint_update_ = true;
322    StopObservingIfDone();
323  }
324}
325
326void OverscrollNavigationOverlay::DidStopLoading(RenderViewHost* host) {
327  // Use the last committed entry rather than the active one, in case a
328  // pending entry has been created.
329  int committed_entry_id =
330      web_contents_->GetController().GetLastCommittedEntry()->GetUniqueID();
331  if (committed_entry_id == pending_entry_id_ || !pending_entry_id_) {
332    loading_complete_ = true;
333    if (!received_paint_update_ && need_paint_update_) {
334      // Force a repaint after the page is loaded.
335      RenderViewHostImpl* view = static_cast<RenderViewHostImpl*>(host);
336      view->ScheduleComposite();
337    }
338    StopObservingIfDone();
339  }
340}
341
342bool OverscrollNavigationOverlay::OnMessageReceived(
343    const IPC::Message& message) {
344  CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
345  IPC_BEGIN_MESSAGE_MAP(OverscrollNavigationOverlay, message)
346    IPC_MESSAGE_HANDLER(ViewHostMsg_UpdateRect, OnUpdateRect)
347  IPC_END_MESSAGE_MAP()
348  return false;
349}
350
351}  // namespace content
352