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