1 // Copyright 2013 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 "base/strings/utf_string_conversions.h"
6 #include "base/values.h"
7 #include "content/browser/frame_host/navigation_entry_impl.h"
8 #include "content/browser/web_contents/web_contents_impl.h"
9 #include "content/browser/web_contents/web_contents_view.h"
10 #include "content/public/browser/load_notification_details.h"
11 #include "content/public/browser/navigation_controller.h"
12 #include "content/public/browser/notification_details.h"
13 #include "content/public/browser/notification_observer.h"
14 #include "content/public/browser/notification_types.h"
15 #include "content/public/browser/render_view_host.h"
16 #include "content/public/browser/render_widget_host_view.h"
17 #include "content/public/browser/web_contents_observer.h"
18 #include "content/public/common/content_paths.h"
19 #include "content/public/test/browser_test_utils.h"
20 #include "content/public/test/content_browser_test.h"
21 #include "content/public/test/content_browser_test_utils.h"
22 #include "content/public/test/test_utils.h"
23 #include "content/shell/browser/shell.h"
24 #include "net/dns/mock_host_resolver.h"
25 #include "net/test/embedded_test_server/embedded_test_server.h"
29 void ResizeWebContentsView(Shell
* shell
, const gfx::Size
& size
,
30 bool set_start_page
) {
31 // Shell::SizeTo is not implemented on Aura; WebContentsView::SizeContents
32 // works on Win and ChromeOS but not Linux - we need to resize the shell
33 // window on Linux because if we don't, the next layout of the unchanged shell
34 // window will resize WebContentsView back to the previous size.
35 // SizeContents is a hack and should not be relied on.
36 #if defined(OS_MACOSX)
38 // If |set_start_page| is true, start with blank page to make sure resize
41 NavigateToURL(shell
, GURL("about://blank"));
43 static_cast<WebContentsImpl
*>(shell
->web_contents())->GetView()->
45 #endif // defined(OS_MACOSX)
48 class WebContentsImplBrowserTest
: public ContentBrowserTest
{
50 WebContentsImplBrowserTest() {}
53 DISALLOW_COPY_AND_ASSIGN(WebContentsImplBrowserTest
);
56 // Keeps track of data from LoadNotificationDetails so we can later verify that
57 // they are correct, after the LoadNotificationDetails object is deleted.
58 class LoadStopNotificationObserver
: public WindowedNotificationObserver
{
60 LoadStopNotificationObserver(NavigationController
* controller
)
61 : WindowedNotificationObserver(NOTIFICATION_LOAD_STOP
,
62 Source
<NavigationController
>(controller
)),
66 void Observe(int type
,
67 const NotificationSource
& source
,
68 const NotificationDetails
& details
) override
{
69 if (type
== NOTIFICATION_LOAD_STOP
) {
70 const Details
<LoadNotificationDetails
> load_details(details
);
71 url_
= load_details
->url
;
72 session_index_
= load_details
->session_index
;
73 controller_
= load_details
->controller
;
75 WindowedNotificationObserver::Observe(type
, source
, details
);
80 NavigationController
* controller_
;
83 // Starts a new navigation as soon as the current one commits, but does not
84 // wait for it to complete. This allows us to observe DidStopLoading while
85 // a pending entry is present.
86 class NavigateOnCommitObserver
: public WebContentsObserver
{
88 NavigateOnCommitObserver(Shell
* shell
, GURL url
)
89 : WebContentsObserver(shell
->web_contents()),
95 // WebContentsObserver:
96 void NavigationEntryCommitted(
97 const LoadCommittedDetails
& load_details
) override
{
101 shell_
->LoadURL(url_
);
110 class RenderViewSizeDelegate
: public WebContentsDelegate
{
112 void set_size_insets(const gfx::Size
& size_insets
) {
113 size_insets_
= size_insets
;
116 // WebContentsDelegate:
117 gfx::Size
GetSizeForNewRenderView(WebContents
* web_contents
) const override
{
118 gfx::Size
size(web_contents
->GetContainerBounds().size());
119 size
.Enlarge(size_insets_
.width(), size_insets_
.height());
124 gfx::Size size_insets_
;
127 class RenderViewSizeObserver
: public WebContentsObserver
{
129 RenderViewSizeObserver(Shell
* shell
, const gfx::Size
& wcv_new_size
)
130 : WebContentsObserver(shell
->web_contents()),
132 wcv_new_size_(wcv_new_size
) {
135 // WebContentsObserver:
136 void RenderViewCreated(RenderViewHost
* rvh
) override
{
137 rwhv_create_size_
= rvh
->GetView()->GetViewBounds().size();
140 void DidStartNavigationToPendingEntry(
142 NavigationController::ReloadType reload_type
) override
{
143 ResizeWebContentsView(shell_
, wcv_new_size_
, false);
146 gfx::Size
rwhv_create_size() const { return rwhv_create_size_
; }
149 Shell
* shell_
; // Weak ptr.
150 gfx::Size wcv_new_size_
;
151 gfx::Size rwhv_create_size_
;
154 class LoadingStateChangedDelegate
: public WebContentsDelegate
{
156 LoadingStateChangedDelegate()
157 : loadingStateChangedCount_(0)
158 , loadingStateToDifferentDocumentCount_(0) {
161 // WebContentsDelegate:
162 void LoadingStateChanged(WebContents
* contents
,
163 bool to_different_document
) override
{
164 loadingStateChangedCount_
++;
165 if (to_different_document
)
166 loadingStateToDifferentDocumentCount_
++;
169 int loadingStateChangedCount() const { return loadingStateChangedCount_
; }
170 int loadingStateToDifferentDocumentCount() const {
171 return loadingStateToDifferentDocumentCount_
;
175 int loadingStateChangedCount_
;
176 int loadingStateToDifferentDocumentCount_
;
179 // See: http://crbug.com/298193
181 #define MAYBE_DidStopLoadingDetails DISABLED_DidStopLoadingDetails
183 #define MAYBE_DidStopLoadingDetails DidStopLoadingDetails
186 // Test that DidStopLoading includes the correct URL in the details.
187 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest
,
188 MAYBE_DidStopLoadingDetails
) {
189 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
191 LoadStopNotificationObserver
load_observer(
192 &shell()->web_contents()->GetController());
193 NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html"));
194 load_observer
.Wait();
196 EXPECT_EQ("/title1.html", load_observer
.url_
.path());
197 EXPECT_EQ(0, load_observer
.session_index_
);
198 EXPECT_EQ(&shell()->web_contents()->GetController(),
199 load_observer
.controller_
);
202 // See: http://crbug.com/298193
204 #define MAYBE_DidStopLoadingDetailsWithPending \
205 DISABLED_DidStopLoadingDetailsWithPending
207 #define MAYBE_DidStopLoadingDetailsWithPending DidStopLoadingDetailsWithPending
210 // Test that DidStopLoading includes the correct URL in the details when a
211 // pending entry is present.
212 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest
,
213 MAYBE_DidStopLoadingDetailsWithPending
) {
214 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
215 GURL
url("data:text/html,<div>test</div>");
217 // Listen for the first load to stop.
218 LoadStopNotificationObserver
load_observer(
219 &shell()->web_contents()->GetController());
220 // Start a new pending navigation as soon as the first load commits.
221 // We will hear a DidStopLoading from the first load as the new load
223 NavigateOnCommitObserver
commit_observer(
224 shell(), embedded_test_server()->GetURL("/title2.html"));
225 NavigateToURL(shell(), url
);
226 load_observer
.Wait();
228 EXPECT_EQ(url
, load_observer
.url_
);
229 EXPECT_EQ(0, load_observer
.session_index_
);
230 EXPECT_EQ(&shell()->web_contents()->GetController(),
231 load_observer
.controller_
);
233 // Test that a renderer-initiated navigation to an invalid URL does not leave
234 // around a pending entry that could be used in a URL spoof. We test this in
235 // a browser test because our unit test framework incorrectly calls
236 // DidStartProvisionalLoadForFrame for in-page navigations.
237 // See http://crbug.com/280512.
238 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest
,
239 ClearNonVisiblePendingOnFail
) {
240 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
242 NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html"));
244 // Navigate to an invalid URL and make sure it doesn't leave a pending entry.
245 LoadStopNotificationObserver
load_observer1(
246 &shell()->web_contents()->GetController());
247 ASSERT_TRUE(ExecuteScript(shell()->web_contents(),
248 "window.location.href=\"nonexistent:12121\";"));
249 load_observer1
.Wait();
250 EXPECT_FALSE(shell()->web_contents()->GetController().GetPendingEntry());
252 LoadStopNotificationObserver
load_observer2(
253 &shell()->web_contents()->GetController());
254 ASSERT_TRUE(ExecuteScript(shell()->web_contents(),
255 "window.location.href=\"#foo\";"));
256 load_observer2
.Wait();
257 EXPECT_EQ(embedded_test_server()->GetURL("/title1.html#foo"),
258 shell()->web_contents()->GetVisibleURL());
261 // Crashes under ThreadSanitizer, http://crbug.com/356758.
262 #if defined(OS_WIN) || defined(OS_ANDROID) \
263 || defined(THREAD_SANITIZER)
264 #define MAYBE_GetSizeForNewRenderView DISABLED_GetSizeForNewRenderView
266 #define MAYBE_GetSizeForNewRenderView GetSizeForNewRenderView
268 // Test that RenderViewHost is created and updated at the size specified by
269 // WebContentsDelegate::GetSizeForNewRenderView().
270 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest
,
271 MAYBE_GetSizeForNewRenderView
) {
272 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
273 // Create a new server with a different site.
274 net::SpawnedTestServer
https_server(
275 net::SpawnedTestServer::TYPE_HTTPS
,
276 net::SpawnedTestServer::kLocalhost
,
277 base::FilePath(FILE_PATH_LITERAL("content/test/data")));
278 ASSERT_TRUE(https_server
.Start());
280 scoped_ptr
<RenderViewSizeDelegate
> delegate(new RenderViewSizeDelegate());
281 shell()->web_contents()->SetDelegate(delegate
.get());
282 ASSERT_TRUE(shell()->web_contents()->GetDelegate() == delegate
.get());
284 // When no size is set, RenderWidgetHostView adopts the size of
286 NavigateToURL(shell(), embedded_test_server()->GetURL("/title2.html"));
287 EXPECT_EQ(shell()->web_contents()->GetContainerBounds().size(),
288 shell()->web_contents()->GetRenderWidgetHostView()->GetViewBounds().
291 // When a size is set, RenderWidgetHostView and WebContentsView honor this
293 gfx::Size
size(300, 300);
294 gfx::Size
size_insets(10, 15);
295 ResizeWebContentsView(shell(), size
, true);
296 delegate
->set_size_insets(size_insets
);
297 NavigateToURL(shell(), https_server
.GetURL("/"));
298 size
.Enlarge(size_insets
.width(), size_insets
.height());
300 shell()->web_contents()->GetRenderWidgetHostView()->GetViewBounds().
302 // The web_contents size is set by the embedder, and should not depend on the
303 // rwhv size. The behavior is correct on OSX, but incorrect on other
305 gfx::Size
exp_wcv_size(300, 300);
306 #if !defined(OS_MACOSX)
307 exp_wcv_size
.Enlarge(size_insets
.width(), size_insets
.height());
310 EXPECT_EQ(exp_wcv_size
,
311 shell()->web_contents()->GetContainerBounds().size());
313 // If WebContentsView is resized after RenderWidgetHostView is created but
314 // before pending navigation entry is committed, both RenderWidgetHostView and
315 // WebContentsView use the new size of WebContentsView.
316 gfx::Size
init_size(200, 200);
317 gfx::Size
new_size(100, 100);
318 size_insets
= gfx::Size(20, 30);
319 ResizeWebContentsView(shell(), init_size
, true);
320 delegate
->set_size_insets(size_insets
);
321 RenderViewSizeObserver
observer(shell(), new_size
);
322 NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html"));
323 // RenderWidgetHostView is created at specified size.
324 init_size
.Enlarge(size_insets
.width(), size_insets
.height());
325 EXPECT_EQ(init_size
, observer
.rwhv_create_size());
327 // Once again, the behavior is correct on OSX. The embedder explicitly sets
328 // the size to (100,100) during navigation. Both the wcv and the rwhv should
329 // take on that size.
330 #if !defined(OS_MACOSX)
331 new_size
.Enlarge(size_insets
.width(), size_insets
.height());
333 gfx::Size actual_size
= shell()->web_contents()->GetRenderWidgetHostView()->
334 GetViewBounds().size();
336 EXPECT_EQ(new_size
, actual_size
);
337 EXPECT_EQ(new_size
, shell()->web_contents()->GetContainerBounds().size());
340 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest
, OpenURLSubframe
) {
341 // Navigate to a page with frames and grab a subframe's FrameTreeNode ID.
342 ASSERT_TRUE(test_server()->Start());
343 NavigateToURL(shell(),
344 test_server()->GetURL("files/frame_tree/top.html"));
345 WebContentsImpl
* wc
= static_cast<WebContentsImpl
*>(shell()->web_contents());
346 FrameTreeNode
* root
= wc
->GetFrameTree()->root();
347 ASSERT_EQ(3UL, root
->child_count());
348 int64 frame_tree_node_id
= root
->child_at(0)->frame_tree_node_id();
349 EXPECT_NE(-1, frame_tree_node_id
);
351 // Navigate with the subframe's FrameTreeNode ID.
352 const GURL
url(test_server()->GetURL("files/title1.html"));
353 OpenURLParams
params(url
, Referrer(), frame_tree_node_id
, CURRENT_TAB
,
354 ui::PAGE_TRANSITION_LINK
, true);
355 shell()->web_contents()->OpenURL(params
);
357 // Make sure the NavigationEntry ends up with the FrameTreeNode ID.
358 NavigationController
* controller
= &shell()->web_contents()->GetController();
359 EXPECT_TRUE(controller
->GetPendingEntry());
360 EXPECT_EQ(frame_tree_node_id
,
361 NavigationEntryImpl::FromNavigationEntry(
362 controller
->GetPendingEntry())->frame_tree_node_id());
365 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest
,
366 AppendingFrameInWebUIDoesNotCrash
) {
367 const GURL
kWebUIUrl("chrome://tracing");
368 const char kJSCodeForAppendingFrame
[] =
369 "document.body.appendChild(document.createElement('iframe'));";
371 NavigateToURL(shell(), kWebUIUrl
);
373 bool js_executed
= content::ExecuteScript(shell()->web_contents(),
374 kJSCodeForAppendingFrame
);
375 EXPECT_TRUE(js_executed
);
378 // Observer class to track the creation of RenderFrameHost objects. It is used
379 // in subsequent tests.
380 class RenderFrameCreatedObserver
: public WebContentsObserver
{
382 RenderFrameCreatedObserver(Shell
* shell
)
383 : WebContentsObserver(shell
->web_contents()),
387 void RenderFrameCreated(RenderFrameHost
* render_frame_host
) override
{
388 last_rfh_
= render_frame_host
;
391 RenderFrameHost
* last_rfh() const { return last_rfh_
; }
394 RenderFrameHost
* last_rfh_
;
396 DISALLOW_COPY_AND_ASSIGN(RenderFrameCreatedObserver
);
399 // Test that creation of new RenderFrameHost objects sends the correct object
400 // to the WebContentObservers. See http://crbug.com/347339.
401 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest
,
402 RenderFrameCreatedCorrectProcessForObservers
) {
403 std::string
foo_com("foo.com");
404 GURL::Replacements replace_host
;
405 net::HostPortPair foo_host_port
;
408 // Setup the server to allow serving separate sites, so we can perform
409 // cross-process navigation.
410 host_resolver()->AddRule("*", "127.0.0.1");
411 ASSERT_TRUE(test_server()->Start());
413 foo_host_port
= test_server()->host_port_pair();
414 foo_host_port
.set_host(foo_com
);
416 GURL
initial_url(test_server()->GetURL("/title1.html"));
418 cross_site_url
= test_server()->GetURL("/title2.html");
419 replace_host
.SetHostStr(foo_com
);
420 cross_site_url
= cross_site_url
.ReplaceComponents(replace_host
);
422 // Navigate to the initial URL and capture the RenderFrameHost for later
424 NavigateToURL(shell(), initial_url
);
425 RenderFrameHost
* orig_rfh
= shell()->web_contents()->GetMainFrame();
427 // Install the observer and navigate cross-site.
428 RenderFrameCreatedObserver
observer(shell());
429 NavigateToURL(shell(), cross_site_url
);
431 // The observer should've seen a RenderFrameCreated call for the new frame
432 // and not the old one.
433 EXPECT_NE(observer
.last_rfh(), orig_rfh
);
434 EXPECT_EQ(observer
.last_rfh(), shell()->web_contents()->GetMainFrame());
437 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest
,
438 LoadingStateChangedForSameDocumentNavigation
) {
439 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
440 scoped_ptr
<LoadingStateChangedDelegate
> delegate(
441 new LoadingStateChangedDelegate());
442 shell()->web_contents()->SetDelegate(delegate
.get());
444 LoadStopNotificationObserver
load_observer(
445 &shell()->web_contents()->GetController());
446 TitleWatcher
title_watcher(shell()->web_contents(),
447 base::ASCIIToUTF16("pushState"));
448 NavigateToURL(shell(), embedded_test_server()->GetURL("/push_state.html"));
449 load_observer
.Wait();
450 base::string16 title
= title_watcher
.WaitAndGetTitle();
451 ASSERT_EQ(title
, base::ASCIIToUTF16("pushState"));
453 // LoadingStateChanged should be called 4 times: start and stop for the
454 // initial load of push_state.html, and start and stop for the "navigation"
455 // triggered by history.pushState(). However, the start notification for the
456 // history.pushState() navigation should set to_different_document to false.
457 EXPECT_EQ("pushState", shell()->web_contents()->GetURL().ref());
458 EXPECT_EQ(4, delegate
->loadingStateChangedCount());
459 EXPECT_EQ(3, delegate
->loadingStateToDifferentDocumentCount());
462 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest
,
463 RenderViewCreatedForChildWindow
) {
464 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
466 NavigateToURL(shell(),
467 embedded_test_server()->GetURL("/title1.html"));
469 WebContentsAddedObserver new_web_contents_observer
;
470 ASSERT_TRUE(ExecuteScript(shell()->web_contents(),
471 "var a = document.createElement('a');"
472 "a.href='./title2.html';"
473 "a.target = '_blank';"
474 "document.body.appendChild(a);"
476 WebContents
* new_web_contents
= new_web_contents_observer
.GetWebContents();
477 WaitForLoadStop(new_web_contents
);
478 EXPECT_TRUE(new_web_contents_observer
.RenderViewCreatedCalled());
481 struct LoadProgressDelegateAndObserver
: public WebContentsDelegate
,
482 public WebContentsObserver
{
483 LoadProgressDelegateAndObserver(Shell
* shell
)
484 : WebContentsObserver(shell
->web_contents()),
485 did_start_loading(false),
486 did_stop_loading(false) {
487 web_contents()->SetDelegate(this);
490 // WebContentsDelegate:
491 void LoadProgressChanged(WebContents
* source
, double progress
) override
{
492 EXPECT_TRUE(did_start_loading
);
493 EXPECT_FALSE(did_stop_loading
);
494 progresses
.push_back(progress
);
497 // WebContentsObserver:
498 void DidStartLoading(RenderViewHost
* render_view_host
) override
{
499 EXPECT_FALSE(did_start_loading
);
500 EXPECT_EQ(0U, progresses
.size());
501 EXPECT_FALSE(did_stop_loading
);
502 did_start_loading
= true;
505 void DidStopLoading(RenderViewHost
* render_view_host
) override
{
506 EXPECT_TRUE(did_start_loading
);
507 EXPECT_GE(progresses
.size(), 1U);
508 EXPECT_FALSE(did_stop_loading
);
509 did_stop_loading
= true;
512 bool did_start_loading
;
513 std::vector
<double> progresses
;
514 bool did_stop_loading
;
517 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest
, LoadProgress
) {
518 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
519 scoped_ptr
<LoadProgressDelegateAndObserver
> delegate(
520 new LoadProgressDelegateAndObserver(shell()));
522 NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html"));
524 const std::vector
<double>& progresses
= delegate
->progresses
;
525 // All updates should be in order ...
526 if (std::adjacent_find(progresses
.begin(),
528 std::greater
<double>()) != progresses
.end()) {
529 ADD_FAILURE() << "Progress values should be in order: "
530 << ::testing::PrintToString(progresses
);
533 // ... and the last one should be 1.0, meaning complete.
534 ASSERT_GE(progresses
.size(), 1U)
535 << "There should be at least one progress update";
536 EXPECT_EQ(1.0, *progresses
.rbegin());
539 struct FirstVisuallyNonEmptyPaintObserver
: public WebContentsObserver
{
540 FirstVisuallyNonEmptyPaintObserver(Shell
* shell
)
541 : WebContentsObserver(shell
->web_contents()),
542 did_fist_visually_non_empty_paint_(false) {}
544 void DidFirstVisuallyNonEmptyPaint() override
{
545 did_fist_visually_non_empty_paint_
= true;
546 on_did_first_visually_non_empty_paint_
.Run();
549 void WaitForDidFirstVisuallyNonEmptyPaint() {
550 if (did_fist_visually_non_empty_paint_
)
552 base::RunLoop run_loop
;
553 on_did_first_visually_non_empty_paint_
= run_loop
.QuitClosure();
557 base::Closure on_did_first_visually_non_empty_paint_
;
558 bool did_fist_visually_non_empty_paint_
;
561 // See: http://crbug.com/395664
562 #if defined(OS_ANDROID)
563 #define MAYBE_FirstVisuallyNonEmptyPaint DISABLED_FirstVisuallyNonEmptyPaint
565 // http://crbug.com/398471
566 #define MAYBE_FirstVisuallyNonEmptyPaint DISABLED_FirstVisuallyNonEmptyPaint
568 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest
,
569 MAYBE_FirstVisuallyNonEmptyPaint
) {
570 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
571 scoped_ptr
<FirstVisuallyNonEmptyPaintObserver
> observer(
572 new FirstVisuallyNonEmptyPaintObserver(shell()));
574 NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html"));
576 observer
->WaitForDidFirstVisuallyNonEmptyPaint();
577 ASSERT_TRUE(observer
->did_fist_visually_non_empty_paint_
);
580 } // namespace content