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"
8 #include "base/command_line.h"
9 #include "content/browser/frame_host/navigation_entry_impl.h"
10 #include "content/browser/web_contents/web_contents_view.h"
11 #include "content/common/frame_messages.h"
12 #include "content/common/view_messages.h"
13 #include "content/public/browser/overscroll_configuration.h"
14 #include "content/public/common/content_switches.h"
15 #include "content/public/test/mock_render_process_host.h"
16 #include "content/test/test_render_frame_host.h"
17 #include "content/test/test_render_view_host.h"
18 #include "content/test/test_web_contents.h"
19 #include "ui/aura/test/test_windows.h"
20 #include "ui/aura/window.h"
21 #include "ui/aura_extra/image_window_delegate.h"
22 #include "ui/compositor/scoped_animation_duration_scale_mode.h"
23 #include "ui/compositor/scoped_layer_animation_settings.h"
24 #include "ui/compositor/test/layer_animator_test_controller.h"
25 #include "ui/events/gesture_detection/gesture_configuration.h"
26 #include "ui/events/test/event_generator.h"
27 #include "ui/gfx/codec/png_codec.h"
31 // A subclass of TestWebContents that offers a fake content window.
32 class OverscrollTestWebContents
: public TestWebContents
{
34 ~OverscrollTestWebContents() override
{}
36 static OverscrollTestWebContents
* Create(
37 BrowserContext
* browser_context
,
38 SiteInstance
* instance
,
39 scoped_ptr
<aura::Window
> fake_native_view
,
40 scoped_ptr
<aura::Window
> fake_contents_window
) {
41 OverscrollTestWebContents
* web_contents
= new OverscrollTestWebContents(
42 browser_context
, fake_native_view
.Pass(), fake_contents_window
.Pass());
43 web_contents
->Init(WebContents::CreateParams(browser_context
, instance
));
47 void ResetNativeView() { fake_native_view_
.reset(); }
49 void ResetContentNativeView() { fake_contents_window_
.reset(); }
51 void set_is_being_destroyed(bool val
) { is_being_destroyed_
= val
; }
53 gfx::NativeView
GetNativeView() override
{ return fake_native_view_
.get(); }
55 gfx::NativeView
GetContentNativeView() override
{
56 return fake_contents_window_
.get();
59 bool IsBeingDestroyed() const override
{ return is_being_destroyed_
; }
62 explicit OverscrollTestWebContents(
63 BrowserContext
* browser_context
,
64 scoped_ptr
<aura::Window
> fake_native_view
,
65 scoped_ptr
<aura::Window
> fake_contents_window
)
66 : TestWebContents(browser_context
),
67 fake_native_view_(fake_native_view
.Pass()),
68 fake_contents_window_(fake_contents_window
.Pass()),
69 is_being_destroyed_(false) {}
72 scoped_ptr
<aura::Window
> fake_native_view_
;
73 scoped_ptr
<aura::Window
> fake_contents_window_
;
74 bool is_being_destroyed_
;
77 class OverscrollNavigationOverlayTest
: public RenderViewHostImplTestHarness
{
79 OverscrollNavigationOverlayTest()
80 : first_("https://www.google.com"),
81 second_("http://www.chromium.org"),
82 third_("https://www.kernel.org/"),
83 fourth_("https://github.com/") {}
85 ~OverscrollNavigationOverlayTest() override
{}
87 void SetDummyScreenshotOnNavEntry(NavigationEntry
* entry
) {
89 bitmap
.allocN32Pixels(1, 1);
90 bitmap
.eraseColor(SK_ColorWHITE
);
91 std::vector
<unsigned char> png_data
;
92 gfx::PNGCodec::EncodeBGRASkBitmap(bitmap
, true, &png_data
);
93 scoped_refptr
<base::RefCountedBytes
> png_bytes
=
94 base::RefCountedBytes::TakeVector(&png_data
);
95 NavigationEntryImpl
* entry_impl
=
96 NavigationEntryImpl::FromNavigationEntry(entry
);
97 entry_impl
->SetScreenshotPNGData(png_bytes
);
100 void ReceivePaintUpdate() {
101 ViewHostMsg_DidFirstVisuallyNonEmptyPaint
msg(
102 main_test_rfh()->GetRoutingID());
103 RenderViewHostTester::TestOnMessageReceived(test_rvh(), msg
);
106 void PerformBackNavigationViaSliderCallbacks() {
107 // Sets slide direction to BACK, sets screenshot from NavEntry at
108 // offset -1 on layer_delegate_.
109 scoped_ptr
<aura::Window
> window(
110 GetOverlay()->CreateBackWindow(GetBackSlideWindowBounds()));
111 bool window_created
= window
;
112 // Performs BACK navigation, sets image from layer_delegate_ on
114 GetOverlay()->OnOverscrollCompleting();
116 EXPECT_EQ(GetOverlay()->direction_
, OverscrollNavigationOverlay::BACK
);
118 EXPECT_EQ(GetOverlay()->direction_
, OverscrollNavigationOverlay::NONE
);
119 window
->SetBounds(gfx::Rect(root_window()->bounds().size()));
120 GetOverlay()->OnOverscrollCompleted(window
.Pass());
121 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
122 switches::kEnableBrowserSideNavigation
)) {
123 main_test_rfh()->PrepareForCommit();
125 contents()->GetPendingMainFrame()->PrepareForCommit();
128 EXPECT_TRUE(contents()->CrossProcessNavigationPending());
130 EXPECT_FALSE(contents()->CrossProcessNavigationPending());
133 gfx::Rect
GetFrontSlideWindowBounds() {
134 gfx::Rect bounds
= gfx::Rect(root_window()->bounds().size());
135 bounds
.Offset(root_window()->bounds().size().width(), 0);
139 gfx::Rect
GetBackSlideWindowBounds() {
140 return gfx::Rect(root_window()->bounds().size());
144 const GURL
first() { return first_
; }
145 const GURL
second() { return second_
; }
146 const GURL
third() { return third_
; }
147 const GURL
fourth() { return fourth_
; }
150 // RenderViewHostImplTestHarness:
151 void SetUp() override
{
152 RenderViewHostImplTestHarness::SetUp();
154 // Set up the fake web contents native view.
155 scoped_ptr
<aura::Window
> fake_native_view(new aura::Window(nullptr));
156 fake_native_view
->Init(ui::LAYER_SOLID_COLOR
);
157 root_window()->AddChild(fake_native_view
.get());
158 fake_native_view
->SetBounds(gfx::Rect(root_window()->bounds().size()));
160 // Set up the fake contents window.
161 scoped_ptr
<aura::Window
> fake_contents_window(new aura::Window(nullptr));
162 fake_contents_window
->Init(ui::LAYER_SOLID_COLOR
);
163 root_window()->AddChild(fake_contents_window
.get());
164 fake_contents_window
->SetBounds(gfx::Rect(root_window()->bounds().size()));
166 // Replace the default test web contents with our custom class.
167 SetContents(OverscrollTestWebContents::Create(
169 SiteInstance::Create(browser_context()),
170 fake_native_view
.Pass(),
171 fake_contents_window
.Pass()));
173 contents()->NavigateAndCommit(first());
174 EXPECT_TRUE(controller().GetVisibleEntry());
175 EXPECT_FALSE(controller().CanGoBack());
177 contents()->NavigateAndCommit(second());
178 EXPECT_TRUE(controller().CanGoBack());
180 contents()->NavigateAndCommit(third());
181 EXPECT_TRUE(controller().CanGoBack());
183 contents()->NavigateAndCommit(fourth_
);
184 EXPECT_TRUE(controller().CanGoBack());
185 EXPECT_FALSE(controller().CanGoForward());
187 // Receive a paint update. This is necessary to make sure the size is set
188 // correctly in RenderWidgetHostImpl.
189 ViewHostMsg_UpdateRect_Params params
;
190 memset(¶ms
, 0, sizeof(params
));
191 params
.view_size
= gfx::Size(10, 10);
192 ViewHostMsg_UpdateRect
rect(test_rvh()->GetRoutingID(), params
);
193 RenderViewHostTester::TestOnMessageReceived(test_rvh(), rect
);
195 // Reset pending flags for size/paint.
196 test_rvh()->ResetSizeAndRepaintPendingFlags();
198 // Create the overlay, and set the contents of the overlay window.
199 overlay_
.reset(new OverscrollNavigationOverlay(contents(), root_window()));
202 void TearDown() override
{
204 RenderViewHostImplTestHarness::TearDown();
207 OverscrollNavigationOverlay
* GetOverlay() {
208 return overlay_
.get();
218 scoped_ptr
<OverscrollNavigationOverlay
> overlay_
;
220 DISALLOW_COPY_AND_ASSIGN(OverscrollNavigationOverlayTest
);
223 // Tests that if a screenshot is available, it is set in the overlay window
225 TEST_F(OverscrollNavigationOverlayTest
, WithScreenshot
) {
226 SetDummyScreenshotOnNavEntry(controller().GetEntryAtOffset(-1));
227 PerformBackNavigationViaSliderCallbacks();
228 // Screenshot was set on NavEntry at offset -1.
229 EXPECT_TRUE(static_cast<aura_extra::ImageWindowDelegate
*>(
230 GetOverlay()->window_
->delegate())->has_image());
233 // Tests that if a screenshot is not available, no image is set in the overlay
235 TEST_F(OverscrollNavigationOverlayTest
, WithoutScreenshot
) {
236 PerformBackNavigationViaSliderCallbacks();
237 // No screenshot was set on NavEntry at offset -1.
238 EXPECT_FALSE(static_cast<aura_extra::ImageWindowDelegate
*>(
239 GetOverlay()->window_
->delegate())->has_image());
242 // Tests that if a navigation is attempted but there is nothing to navigate to,
243 // we return a null window.
244 TEST_F(OverscrollNavigationOverlayTest
, CannotNavigate
) {
245 EXPECT_EQ(GetOverlay()->CreateFrontWindow(GetFrontSlideWindowBounds()),
249 // Tests that if a navigation is cancelled, no navigation is performed and the
250 // state is restored.
251 TEST_F(OverscrollNavigationOverlayTest
, CancelNavigation
) {
252 scoped_ptr
<aura::Window
> window
=
253 GetOverlay()->CreateBackWindow(GetBackSlideWindowBounds());
254 EXPECT_EQ(GetOverlay()->direction_
, OverscrollNavigationOverlay::BACK
);
256 GetOverlay()->OnOverscrollCancelled();
257 EXPECT_FALSE(contents()->CrossProcessNavigationPending());
258 EXPECT_EQ(GetOverlay()->direction_
, OverscrollNavigationOverlay::NONE
);
261 // Performs two navigations. The second navigation is cancelled, tests that the
262 // first one worked correctly.
263 TEST_F(OverscrollNavigationOverlayTest
, CancelAfterSuccessfulNavigation
) {
264 PerformBackNavigationViaSliderCallbacks();
265 scoped_ptr
<aura::Window
> wrapper
=
266 GetOverlay()->CreateBackWindow(GetBackSlideWindowBounds());
267 EXPECT_EQ(GetOverlay()->direction_
, OverscrollNavigationOverlay::BACK
);
269 GetOverlay()->OnOverscrollCancelled();
270 EXPECT_EQ(GetOverlay()->direction_
, OverscrollNavigationOverlay::NONE
);
272 EXPECT_TRUE(contents()->CrossProcessNavigationPending());
273 NavigationEntry
* pending
= contents()->GetController().GetPendingEntry();
274 contents()->GetPendingMainFrame()->SendNavigate(
275 pending
->GetPageID(), pending
->GetUniqueID(), false, pending
->GetURL());
276 EXPECT_EQ(contents()->GetURL(), third());
279 // Tests that an overscroll navigation that receives a paint update actually
281 TEST_F(OverscrollNavigationOverlayTest
, Navigation_PaintUpdate
) {
282 PerformBackNavigationViaSliderCallbacks();
283 ReceivePaintUpdate();
285 // Paint updates until the navigation is committed typically represent updates
286 // for the previous page, so we should still be observing.
287 EXPECT_TRUE(GetOverlay()->web_contents());
289 NavigationEntry
* pending
= contents()->GetController().GetPendingEntry();
290 contents()->GetPendingMainFrame()->SendNavigate(
291 pending
->GetPageID(), pending
->GetUniqueID(), false, pending
->GetURL());
292 ReceivePaintUpdate();
294 // Navigation was committed and the paint update was received - we should no
295 // longer be observing.
296 EXPECT_FALSE(GetOverlay()->web_contents());
297 EXPECT_EQ(contents()->GetURL(), third());
300 // Tests that an overscroll navigation that receives a loading update actually
302 TEST_F(OverscrollNavigationOverlayTest
, Navigation_LoadingUpdate
) {
303 PerformBackNavigationViaSliderCallbacks();
304 EXPECT_TRUE(GetOverlay()->web_contents());
305 // DidStopLoading for any navigation should always reset the load flag and
306 // dismiss the overlay even if the pending navigation wasn't committed -
307 // this is a "safety net" in case we mis-identify the destination webpage
308 // (which can happen if a new navigation is performed while while a GestureNav
309 // navigation is in progress).
310 contents()->TestSetIsLoading(true);
311 contents()->TestSetIsLoading(false);
312 EXPECT_FALSE(GetOverlay()->web_contents());
313 NavigationEntry
* pending
= contents()->GetController().GetPendingEntry();
314 contents()->GetPendingMainFrame()->SendNavigate(
315 pending
->GetPageID(), pending
->GetUniqueID(), false, pending
->GetURL());
316 EXPECT_EQ(contents()->GetURL(), third());
319 TEST_F(OverscrollNavigationOverlayTest
, CloseDuringAnimation
) {
320 ui::ScopedAnimationDurationScaleMode
normal_duration_(
321 ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION
);
322 GetOverlay()->owa_
->OnOverscrollModeChange(OVERSCROLL_NONE
, OVERSCROLL_EAST
);
323 GetOverlay()->owa_
->OnOverscrollComplete(OVERSCROLL_EAST
);
324 EXPECT_EQ(GetOverlay()->direction_
, OverscrollNavigationOverlay::BACK
);
325 OverscrollTestWebContents
* test_web_contents
=
326 static_cast<OverscrollTestWebContents
*>(web_contents());
327 test_web_contents
->set_is_being_destroyed(true);
328 test_web_contents
->ResetContentNativeView();
329 test_web_contents
->ResetNativeView();
330 // Ensure a clean close.
334 // Tests that swapping the overlay window at the end of a gesture caused by the
335 // start of a new overscroll does not crash and the events still reach the new
337 TEST_F(OverscrollNavigationOverlayTest
, OverlayWindowSwap
) {
338 PerformBackNavigationViaSliderCallbacks();
339 aura::Window
* first_overlay_window
= GetOverlay()->window_
.get();
340 EXPECT_TRUE(GetOverlay()->web_contents());
341 EXPECT_TRUE(first_overlay_window
);
343 // At this stage, the overlay window is covering the web contents. Configure
344 // the animator of the overlay window for the test.
345 ui::ScopedAnimationDurationScaleMode
normal_duration(
346 ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION
);
347 ui::LayerAnimator
* animator
= GetOverlay()->window_
->layer()->GetAnimator();
348 animator
->set_disable_timer_for_test(true);
349 ui::LayerAnimatorTestController
test_controller(animator
);
351 int overscroll_complete_distance
=
352 root_window()->bounds().size().width() *
353 content::GetOverscrollConfig(
354 content::OVERSCROLL_CONFIG_HORIZ_THRESHOLD_COMPLETE
) +
355 ui::GestureConfiguration::GetInstance()
356 ->max_touch_move_in_pixels_for_click() + 1;
358 // Start and complete a back navigation via a gesture.
359 ui::test::EventGenerator
generator(root_window());
360 generator
.GestureScrollSequence(gfx::Point(0, 0),
361 gfx::Point(overscroll_complete_distance
, 0),
362 base::TimeDelta::FromMilliseconds(10),
365 ui::ScopedLayerAnimationSettings
settings(animator
);
366 test_controller
.StartThreadedAnimationsIfNeeded();
368 // The overlay window should now be being animated to the edge of the screen.
369 // |first()overlay_window| is the back window.
370 // This is what the screen should look like. The X indicates where the next
371 // gesture starts for the test.
372 // +---------root_window--------+
373 // |+-back window--+--front window--+
377 // |+--------------+------------|---+
378 // +----------------------------+
382 // |<------------->||
385 // | start distance |
386 // |<-------------->|
387 EXPECT_EQ(GetOverlay()->window_
.get(), first_overlay_window
);
389 // The overlay window is halfway through, start another animation that will
390 // cancel the first one. The event that cancels the animation will go to
391 // the slide window, which will be used as the overlay window when the new
392 // overscroll starts.
393 int second_overscroll_start_distance
= overscroll_complete_distance
+ 1;
394 generator
.GestureScrollSequence(
395 gfx::Point(second_overscroll_start_distance
, 0),
397 second_overscroll_start_distance
+ overscroll_complete_distance
, 0),
398 base::TimeDelta::FromMilliseconds(10), 10);
399 EXPECT_TRUE(GetOverlay()->window_
.get());
400 // The overlay window should be a new window.
401 EXPECT_NE(GetOverlay()->window_
.get(), first_overlay_window
);
403 // Complete the animation.
404 GetOverlay()->window_
->layer()->GetAnimator()->StopAnimating();
405 EXPECT_TRUE(GetOverlay()->window_
.get());
408 contents()->CommitPendingNavigation();
409 ReceivePaintUpdate();
410 EXPECT_FALSE(GetOverlay()->window_
.get());
411 EXPECT_EQ(contents()->GetURL(), first());
414 } // namespace content