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.
5 #include "content/browser/web_contents/aura/overscroll_navigation_overlay.h"
9 #include "base/i18n/rtl.h"
10 #include "base/metrics/histogram_macros.h"
11 #include "content/browser/frame_host/navigation_entry_impl.h"
12 #include "content/browser/renderer_host/render_view_host_impl.h"
13 #include "content/browser/web_contents/aura/overscroll_window_delegate.h"
14 #include "content/browser/web_contents/web_contents_impl.h"
15 #include "content/common/view_messages.h"
16 #include "content/public/browser/browser_thread.h"
17 #include "content/public/browser/render_widget_host_view.h"
18 #include "ui/aura/window.h"
19 #include "ui/base/layout.h"
20 #include "ui/compositor/layer.h"
21 #include "ui/compositor/layer_animation_observer.h"
22 #include "ui/compositor/paint_recorder.h"
23 #include "ui/compositor/scoped_layer_animation_settings.h"
24 #include "ui/gfx/canvas.h"
25 #include "ui/gfx/image/image_png_rep.h"
30 // Returns true if the entry's URL or any of the URLs in entry's redirect chain
32 bool DoesEntryMatchURL(NavigationEntry
* entry
, const GURL
& url
) {
35 if (entry
->GetURL() == url
)
37 const std::vector
<GURL
>& redirect_chain
= entry
->GetRedirectChain();
38 for (std::vector
<GURL
>::const_iterator it
= redirect_chain
.begin();
39 it
!= redirect_chain
.end();
49 // Responsible for fading out and deleting the layer of the overlay window.
50 class OverlayDismissAnimator
51 : public ui::LayerAnimationObserver
{
53 // Takes ownership of the layer.
54 explicit OverlayDismissAnimator(scoped_ptr
<ui::Layer
> layer
)
55 : layer_(layer
.Pass()) {
59 // Starts the fadeout animation on the layer. When the animation finishes,
60 // the object deletes itself along with the layer.
63 ui::LayerAnimator
* animator
= layer_
->GetAnimator();
64 // This makes SetOpacity() animate with default duration (which could be
65 // zero, e.g. when running tests).
66 ui::ScopedLayerAnimationSettings
settings(animator
);
67 animator
->AddObserver(this);
68 layer_
->SetOpacity(0);
71 // Overridden from ui::LayerAnimationObserver
72 void OnLayerAnimationEnded(ui::LayerAnimationSequence
* sequence
) override
{
76 void OnLayerAnimationAborted(ui::LayerAnimationSequence
* sequence
) override
{
80 void OnLayerAnimationScheduled(
81 ui::LayerAnimationSequence
* sequence
) override
{}
84 ~OverlayDismissAnimator() override
{}
86 scoped_ptr
<ui::Layer
> layer_
;
88 DISALLOW_COPY_AND_ASSIGN(OverlayDismissAnimator
);
91 OverscrollNavigationOverlay::OverscrollNavigationOverlay(
92 WebContentsImpl
* web_contents
,
93 aura::Window
* web_contents_window
)
95 web_contents_(web_contents
),
96 loading_complete_(false),
97 received_paint_update_(false),
98 owa_(new OverscrollWindowAnimation(this)),
99 web_contents_window_(web_contents_window
) {
102 OverscrollNavigationOverlay::~OverscrollNavigationOverlay() {
103 aura::Window
* event_window
= GetMainWindow();
104 if (owa_
->is_active() && event_window
)
105 event_window
->ReleaseCapture();
108 void OverscrollNavigationOverlay::StartObserving() {
109 loading_complete_
= false;
110 received_paint_update_
= false;
111 Observe(web_contents_
);
113 // Assumes the navigation has been initiated.
114 NavigationEntry
* pending_entry
=
115 web_contents_
->GetController().GetPendingEntry();
116 // Save url of the pending entry to identify when it loads and paints later.
117 // Under some circumstances navigation can leave a null pending entry -
118 // see comments in NavigationControllerImpl::NavigateToPendingEntry().
119 pending_entry_url_
= pending_entry
? pending_entry
->GetURL() : GURL();
122 void OverscrollNavigationOverlay::StopObservingIfDone() {
123 // Normally we dismiss the overlay once we receive a paint update, however
124 // for in-page navigations DidFirstVisuallyNonEmptyPaint() does not get
125 // called, and we rely on loading_complete_ for those cases.
126 // If an overscroll gesture is in progress, then do not destroy the window.
127 if (!window_
|| !(loading_complete_
|| received_paint_update_
) ||
132 // OverlayDismissAnimator deletes the dismiss layer and itself when the
133 // animation completes.
134 scoped_ptr
<ui::Layer
> dismiss_layer
= window_
->AcquireLayer();
136 (new OverlayDismissAnimator(dismiss_layer
.Pass()))->Animate();
138 received_paint_update_
= false;
139 loading_complete_
= false;
142 scoped_ptr
<aura::Window
> OverscrollNavigationOverlay::CreateOverlayWindow(
143 const gfx::Rect
& bounds
) {
144 UMA_HISTOGRAM_ENUMERATION(
145 "Overscroll.Started2", direction_
, NAVIGATION_COUNT
);
146 OverscrollWindowDelegate
* overscroll_delegate
= new OverscrollWindowDelegate(
147 owa_
.get(), GetImageForDirection(direction_
));
148 scoped_ptr
<aura::Window
> window(new aura::Window(overscroll_delegate
));
149 window
->set_owned_by_parent(false);
150 window
->SetTransparent(true);
151 window
->Init(ui::LAYER_TEXTURED
);
152 window
->layer()->SetMasksToBounds(false);
153 window
->SetName("OverscrollOverlay");
154 web_contents_window_
->AddChild(window
.get());
155 aura::Window
* event_window
= GetMainWindow();
156 if (direction_
== FORWARD
)
157 web_contents_window_
->StackChildAbove(window
.get(), event_window
);
159 web_contents_window_
->StackChildBelow(window
.get(), event_window
);
160 window
->SetBounds(bounds
);
161 // Set capture on the window that is receiving the overscroll events so that
162 // trackpad scroll gestures keep targetting it even if the mouse pointer moves
164 event_window
->SetCapture();
166 return window
.Pass();
169 const gfx::Image
OverscrollNavigationOverlay::GetImageForDirection(
170 NavigationDirection direction
) const {
171 const NavigationControllerImpl
& controller
= web_contents_
->GetController();
172 const NavigationEntryImpl
* entry
= NavigationEntryImpl::FromNavigationEntry(
173 controller
.GetEntryAtOffset(direction
== FORWARD
? 1 : -1));
175 if (entry
&& entry
->screenshot().get()) {
176 std::vector
<gfx::ImagePNGRep
> image_reps
;
177 image_reps
.push_back(gfx::ImagePNGRep(entry
->screenshot(), 1.0f
));
178 return gfx::Image(image_reps
);
183 scoped_ptr
<aura::Window
> OverscrollNavigationOverlay::CreateFrontWindow(
184 const gfx::Rect
& bounds
) {
185 if (!web_contents_
->GetController().CanGoForward())
187 direction_
= FORWARD
;
188 return CreateOverlayWindow(bounds
);
191 scoped_ptr
<aura::Window
> OverscrollNavigationOverlay::CreateBackWindow(
192 const gfx::Rect
& bounds
) {
193 if (!web_contents_
->GetController().CanGoBack())
196 return CreateOverlayWindow(bounds
);
199 aura::Window
* OverscrollNavigationOverlay::GetMainWindow() const {
201 return window_
.get();
202 return web_contents_
->IsBeingDestroyed()
204 : web_contents_
->GetContentNativeView();
207 void OverscrollNavigationOverlay::OnOverscrollCompleting() {
208 aura::Window
* main_window
= GetMainWindow();
211 main_window
->ReleaseCapture();
214 void OverscrollNavigationOverlay::OnOverscrollCompleted(
215 scoped_ptr
<aura::Window
> window
) {
216 DCHECK(direction_
!= NONE
);
217 aura::Window
* main_window
= GetMainWindow();
219 UMA_HISTOGRAM_ENUMERATION(
220 "Overscroll.Cancelled", direction_
, NAVIGATION_COUNT
);
224 // Make sure we can navigate first, as other factors can trigger a navigation
225 // during an overscroll gesture and navigating without history produces a
227 bool navigated
= false;
228 if (direction_
== FORWARD
&& web_contents_
->GetController().CanGoForward()) {
229 web_contents_
->GetController().GoForward();
231 } else if (direction_
== BACK
&& web_contents_
->GetController().CanGoBack()) {
232 web_contents_
->GetController().GoBack();
235 // We need to dismiss the overlay without navigating as soon as the
236 // overscroll finishes.
237 UMA_HISTOGRAM_ENUMERATION(
238 "Overscroll.Cancelled", direction_
, NAVIGATION_COUNT
);
239 loading_complete_
= true;
243 UMA_HISTOGRAM_ENUMERATION(
244 "Overscroll.Navigated2", direction_
, NAVIGATION_COUNT
);
248 main_window
->SetTransform(gfx::Transform());
249 window_
= window
.Pass();
250 // Make sure the window is in its default position.
251 window_
->SetBounds(gfx::Rect(web_contents_window_
->bounds().size()));
252 window_
->SetTransform(gfx::Transform());
253 // Make sure the overlay window is on top.
254 web_contents_window_
->StackChildAtTop(window_
.get());
256 StopObservingIfDone();
259 void OverscrollNavigationOverlay::OnOverscrollCancelled() {
260 UMA_HISTOGRAM_ENUMERATION(
261 "Overscroll.Cancelled", direction_
, NAVIGATION_COUNT
);
262 aura::Window
* main_window
= GetMainWindow();
265 main_window
->ReleaseCapture();
267 StopObservingIfDone();
270 void OverscrollNavigationOverlay::DidFirstVisuallyNonEmptyPaint() {
271 NavigationEntry
* visible_entry
=
272 web_contents_
->GetController().GetVisibleEntry();
273 if (pending_entry_url_
.is_empty() ||
274 DoesEntryMatchURL(visible_entry
, pending_entry_url_
)) {
275 received_paint_update_
= true;
276 StopObservingIfDone();
280 void OverscrollNavigationOverlay::DidStopLoading() {
281 // Don't compare URLs in this case - it's possible they won't match if
282 // a gesture-nav initiated navigation was interrupted by some other in-site
283 // navigation (e.g., from a script, or from a bookmark).
284 loading_complete_
= true;
285 StopObservingIfDone();
288 } // namespace content