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 virtual 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 virtual 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 virtual gfx::Size GetSizeForNewRenderView(
118 WebContents* web_contents) const OVERRIDE {
119 gfx::Size size(web_contents->GetContainerBounds().size());
120 size.Enlarge(size_insets_.width(), size_insets_.height());
125 gfx::Size size_insets_;
128 class RenderViewSizeObserver : public WebContentsObserver {
130 RenderViewSizeObserver(Shell* shell, const gfx::Size& wcv_new_size)
131 : WebContentsObserver(shell->web_contents()),
133 wcv_new_size_(wcv_new_size) {
136 // WebContentsObserver:
137 virtual void RenderViewCreated(RenderViewHost* rvh) OVERRIDE {
138 rwhv_create_size_ = rvh->GetView()->GetViewBounds().size();
141 virtual void DidStartNavigationToPendingEntry(
143 NavigationController::ReloadType reload_type) OVERRIDE {
144 ResizeWebContentsView(shell_, wcv_new_size_, false);
147 gfx::Size rwhv_create_size() const { return rwhv_create_size_; }
150 Shell* shell_; // Weak ptr.
151 gfx::Size wcv_new_size_;
152 gfx::Size rwhv_create_size_;
155 class LoadingStateChangedDelegate : public WebContentsDelegate {
157 LoadingStateChangedDelegate()
158 : loadingStateChangedCount_(0)
159 , loadingStateToDifferentDocumentCount_(0) {
162 // WebContentsDelegate:
163 virtual void LoadingStateChanged(WebContents* contents,
164 bool to_different_document) OVERRIDE {
165 loadingStateChangedCount_++;
166 if (to_different_document)
167 loadingStateToDifferentDocumentCount_++;
170 int loadingStateChangedCount() const { return loadingStateChangedCount_; }
171 int loadingStateToDifferentDocumentCount() const {
172 return loadingStateToDifferentDocumentCount_;
176 int loadingStateChangedCount_;
177 int loadingStateToDifferentDocumentCount_;
180 // See: http://crbug.com/298193
182 #define MAYBE_DidStopLoadingDetails DISABLED_DidStopLoadingDetails
184 #define MAYBE_DidStopLoadingDetails DidStopLoadingDetails
187 // Test that DidStopLoading includes the correct URL in the details.
188 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
189 MAYBE_DidStopLoadingDetails) {
190 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
192 LoadStopNotificationObserver load_observer(
193 &shell()->web_contents()->GetController());
194 NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html"));
195 load_observer.Wait();
197 EXPECT_EQ("/title1.html", load_observer.url_.path());
198 EXPECT_EQ(0, load_observer.session_index_);
199 EXPECT_EQ(&shell()->web_contents()->GetController(),
200 load_observer.controller_);
203 // See: http://crbug.com/298193
205 #define MAYBE_DidStopLoadingDetailsWithPending \
206 DISABLED_DidStopLoadingDetailsWithPending
208 #define MAYBE_DidStopLoadingDetailsWithPending DidStopLoadingDetailsWithPending
211 // Test that DidStopLoading includes the correct URL in the details when a
212 // pending entry is present.
213 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
214 MAYBE_DidStopLoadingDetailsWithPending) {
215 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
216 GURL url("data:text/html,<div>test</div>");
218 // Listen for the first load to stop.
219 LoadStopNotificationObserver load_observer(
220 &shell()->web_contents()->GetController());
221 // Start a new pending navigation as soon as the first load commits.
222 // We will hear a DidStopLoading from the first load as the new load
224 NavigateOnCommitObserver commit_observer(
225 shell(), embedded_test_server()->GetURL("/title2.html"));
226 NavigateToURL(shell(), url);
227 load_observer.Wait();
229 EXPECT_EQ(url, load_observer.url_);
230 EXPECT_EQ(0, load_observer.session_index_);
231 EXPECT_EQ(&shell()->web_contents()->GetController(),
232 load_observer.controller_);
234 // Test that a renderer-initiated navigation to an invalid URL does not leave
235 // around a pending entry that could be used in a URL spoof. We test this in
236 // a browser test because our unit test framework incorrectly calls
237 // DidStartProvisionalLoadForFrame for in-page navigations.
238 // See http://crbug.com/280512.
239 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
240 ClearNonVisiblePendingOnFail) {
241 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
243 NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html"));
245 // Navigate to an invalid URL and make sure it doesn't leave a pending entry.
246 LoadStopNotificationObserver load_observer1(
247 &shell()->web_contents()->GetController());
248 ASSERT_TRUE(ExecuteScript(shell()->web_contents(),
249 "window.location.href=\"nonexistent:12121\";"));
250 load_observer1.Wait();
251 EXPECT_FALSE(shell()->web_contents()->GetController().GetPendingEntry());
253 LoadStopNotificationObserver load_observer2(
254 &shell()->web_contents()->GetController());
255 ASSERT_TRUE(ExecuteScript(shell()->web_contents(),
256 "window.location.href=\"#foo\";"));
257 load_observer2.Wait();
258 EXPECT_EQ(embedded_test_server()->GetURL("/title1.html#foo"),
259 shell()->web_contents()->GetVisibleURL());
262 // Crashes under ThreadSanitizer, http://crbug.com/356758.
263 #if defined(OS_WIN) || defined(OS_ANDROID) \
264 || defined(THREAD_SANITIZER)
265 #define MAYBE_GetSizeForNewRenderView DISABLED_GetSizeForNewRenderView
267 #define MAYBE_GetSizeForNewRenderView GetSizeForNewRenderView
269 // Test that RenderViewHost is created and updated at the size specified by
270 // WebContentsDelegate::GetSizeForNewRenderView().
271 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
272 MAYBE_GetSizeForNewRenderView) {
273 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
274 // Create a new server with a different site.
275 net::SpawnedTestServer https_server(
276 net::SpawnedTestServer::TYPE_HTTPS,
277 net::SpawnedTestServer::kLocalhost,
278 base::FilePath(FILE_PATH_LITERAL("content/test/data")));
279 ASSERT_TRUE(https_server.Start());
281 scoped_ptr<RenderViewSizeDelegate> delegate(new RenderViewSizeDelegate());
282 shell()->web_contents()->SetDelegate(delegate.get());
283 ASSERT_TRUE(shell()->web_contents()->GetDelegate() == delegate.get());
285 // When no size is set, RenderWidgetHostView adopts the size of
287 NavigateToURL(shell(), embedded_test_server()->GetURL("/title2.html"));
288 EXPECT_EQ(shell()->web_contents()->GetContainerBounds().size(),
289 shell()->web_contents()->GetRenderWidgetHostView()->GetViewBounds().
292 // When a size is set, RenderWidgetHostView and WebContentsView honor this
294 gfx::Size size(300, 300);
295 gfx::Size size_insets(10, 15);
296 ResizeWebContentsView(shell(), size, true);
297 delegate->set_size_insets(size_insets);
298 NavigateToURL(shell(), https_server.GetURL("/"));
299 size.Enlarge(size_insets.width(), size_insets.height());
301 shell()->web_contents()->GetRenderWidgetHostView()->GetViewBounds().
303 // The web_contents size is set by the embedder, and should not depend on the
304 // rwhv size. The behavior is correct on OSX, but incorrect on other
306 gfx::Size exp_wcv_size(300, 300);
307 #if !defined(OS_MACOSX)
308 exp_wcv_size.Enlarge(size_insets.width(), size_insets.height());
311 EXPECT_EQ(exp_wcv_size,
312 shell()->web_contents()->GetContainerBounds().size());
314 // If WebContentsView is resized after RenderWidgetHostView is created but
315 // before pending navigation entry is committed, both RenderWidgetHostView and
316 // WebContentsView use the new size of WebContentsView.
317 gfx::Size init_size(200, 200);
318 gfx::Size new_size(100, 100);
319 size_insets = gfx::Size(20, 30);
320 ResizeWebContentsView(shell(), init_size, true);
321 delegate->set_size_insets(size_insets);
322 RenderViewSizeObserver observer(shell(), new_size);
323 NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html"));
324 // RenderWidgetHostView is created at specified size.
325 init_size.Enlarge(size_insets.width(), size_insets.height());
326 EXPECT_EQ(init_size, observer.rwhv_create_size());
328 // Once again, the behavior is correct on OSX. The embedder explicitly sets
329 // the size to (100,100) during navigation. Both the wcv and the rwhv should
330 // take on that size.
331 #if !defined(OS_MACOSX)
332 new_size.Enlarge(size_insets.width(), size_insets.height());
334 gfx::Size actual_size = shell()->web_contents()->GetRenderWidgetHostView()->
335 GetViewBounds().size();
337 EXPECT_EQ(new_size, actual_size);
338 EXPECT_EQ(new_size, shell()->web_contents()->GetContainerBounds().size());
341 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, OpenURLSubframe) {
342 // Navigate to a page with frames and grab a subframe's FrameTreeNode ID.
343 ASSERT_TRUE(test_server()->Start());
344 NavigateToURL(shell(),
345 test_server()->GetURL("files/frame_tree/top.html"));
346 WebContentsImpl* wc = static_cast<WebContentsImpl*>(shell()->web_contents());
347 FrameTreeNode* root = wc->GetFrameTree()->root();
348 ASSERT_EQ(3UL, root->child_count());
349 int64 frame_tree_node_id = root->child_at(0)->frame_tree_node_id();
350 EXPECT_NE(-1, frame_tree_node_id);
352 // Navigate with the subframe's FrameTreeNode ID.
353 const GURL url(test_server()->GetURL("files/title1.html"));
354 OpenURLParams params(url, Referrer(), frame_tree_node_id, CURRENT_TAB,
355 ui::PAGE_TRANSITION_LINK, true);
356 shell()->web_contents()->OpenURL(params);
358 // Make sure the NavigationEntry ends up with the FrameTreeNode ID.
359 NavigationController* controller = &shell()->web_contents()->GetController();
360 EXPECT_TRUE(controller->GetPendingEntry());
361 EXPECT_EQ(frame_tree_node_id,
362 NavigationEntryImpl::FromNavigationEntry(
363 controller->GetPendingEntry())->frame_tree_node_id());
366 // Observer class to track the creation of RenderFrameHost objects. It is used
367 // in subsequent tests.
368 class RenderFrameCreatedObserver : public WebContentsObserver {
370 RenderFrameCreatedObserver(Shell* shell)
371 : WebContentsObserver(shell->web_contents()),
375 virtual void RenderFrameCreated(RenderFrameHost* render_frame_host) OVERRIDE {
376 last_rfh_ = render_frame_host;
379 RenderFrameHost* last_rfh() const { return last_rfh_; }
382 RenderFrameHost* last_rfh_;
384 DISALLOW_COPY_AND_ASSIGN(RenderFrameCreatedObserver);
387 // Test that creation of new RenderFrameHost objects sends the correct object
388 // to the WebContentObservers. See http://crbug.com/347339.
389 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
390 RenderFrameCreatedCorrectProcessForObservers) {
391 std::string foo_com("foo.com");
392 GURL::Replacements replace_host;
393 net::HostPortPair foo_host_port;
396 // Setup the server to allow serving separate sites, so we can perform
397 // cross-process navigation.
398 host_resolver()->AddRule("*", "127.0.0.1");
399 ASSERT_TRUE(test_server()->Start());
401 foo_host_port = test_server()->host_port_pair();
402 foo_host_port.set_host(foo_com);
404 GURL initial_url(test_server()->GetURL("/title1.html"));
406 cross_site_url = test_server()->GetURL("/title2.html");
407 replace_host.SetHostStr(foo_com);
408 cross_site_url = cross_site_url.ReplaceComponents(replace_host);
410 // Navigate to the initial URL and capture the RenderFrameHost for later
412 NavigateToURL(shell(), initial_url);
413 RenderFrameHost* orig_rfh = shell()->web_contents()->GetMainFrame();
415 // Install the observer and navigate cross-site.
416 RenderFrameCreatedObserver observer(shell());
417 NavigateToURL(shell(), cross_site_url);
419 // The observer should've seen a RenderFrameCreated call for the new frame
420 // and not the old one.
421 EXPECT_NE(observer.last_rfh(), orig_rfh);
422 EXPECT_EQ(observer.last_rfh(), shell()->web_contents()->GetMainFrame());
425 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
426 LoadingStateChangedForSameDocumentNavigation) {
427 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
428 scoped_ptr<LoadingStateChangedDelegate> delegate(
429 new LoadingStateChangedDelegate());
430 shell()->web_contents()->SetDelegate(delegate.get());
432 LoadStopNotificationObserver load_observer(
433 &shell()->web_contents()->GetController());
434 TitleWatcher title_watcher(shell()->web_contents(),
435 base::ASCIIToUTF16("pushState"));
436 NavigateToURL(shell(), embedded_test_server()->GetURL("/push_state.html"));
437 load_observer.Wait();
438 base::string16 title = title_watcher.WaitAndGetTitle();
439 ASSERT_EQ(title, base::ASCIIToUTF16("pushState"));
441 // LoadingStateChanged should be called 4 times: start and stop for the
442 // initial load of push_state.html, and start and stop for the "navigation"
443 // triggered by history.pushState(). However, the start notification for the
444 // history.pushState() navigation should set to_different_document to false.
445 EXPECT_EQ("pushState", shell()->web_contents()->GetURL().ref());
446 EXPECT_EQ(4, delegate->loadingStateChangedCount());
447 EXPECT_EQ(3, delegate->loadingStateToDifferentDocumentCount());
450 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
451 RenderViewCreatedForChildWindow) {
452 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
454 NavigateToURL(shell(),
455 embedded_test_server()->GetURL("/title1.html"));
457 WebContentsAddedObserver new_web_contents_observer;
458 ASSERT_TRUE(ExecuteScript(shell()->web_contents(),
459 "var a = document.createElement('a');"
460 "a.href='./title2.html';"
461 "a.target = '_blank';"
462 "document.body.appendChild(a);"
464 WebContents* new_web_contents = new_web_contents_observer.GetWebContents();
465 WaitForLoadStop(new_web_contents);
466 EXPECT_TRUE(new_web_contents_observer.RenderViewCreatedCalled());
469 struct LoadProgressDelegateAndObserver : public WebContentsDelegate,
470 public WebContentsObserver {
471 LoadProgressDelegateAndObserver(Shell* shell)
472 : WebContentsObserver(shell->web_contents()),
473 did_start_loading(false),
474 did_stop_loading(false) {
475 web_contents()->SetDelegate(this);
478 // WebContentsDelegate:
479 virtual void LoadProgressChanged(WebContents* source,
480 double progress) OVERRIDE {
481 EXPECT_TRUE(did_start_loading);
482 EXPECT_FALSE(did_stop_loading);
483 progresses.push_back(progress);
486 // WebContentsObserver:
487 virtual void DidStartLoading(RenderViewHost* render_view_host) OVERRIDE {
488 EXPECT_FALSE(did_start_loading);
489 EXPECT_EQ(0U, progresses.size());
490 EXPECT_FALSE(did_stop_loading);
491 did_start_loading = true;
494 virtual void DidStopLoading(RenderViewHost* render_view_host) OVERRIDE {
495 EXPECT_TRUE(did_start_loading);
496 EXPECT_GE(progresses.size(), 1U);
497 EXPECT_FALSE(did_stop_loading);
498 did_stop_loading = true;
501 bool did_start_loading;
502 std::vector<double> progresses;
503 bool did_stop_loading;
506 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, LoadProgress) {
507 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
508 scoped_ptr<LoadProgressDelegateAndObserver> delegate(
509 new LoadProgressDelegateAndObserver(shell()));
511 NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html"));
513 const std::vector<double>& progresses = delegate->progresses;
514 // All updates should be in order ...
515 if (std::adjacent_find(progresses.begin(),
517 std::greater<double>()) != progresses.end()) {
518 ADD_FAILURE() << "Progress values should be in order: "
519 << ::testing::PrintToString(progresses);
522 // ... and the last one should be 1.0, meaning complete.
523 ASSERT_GE(progresses.size(), 1U)
524 << "There should be at least one progress update";
525 EXPECT_EQ(1.0, *progresses.rbegin());
528 struct FirstVisuallyNonEmptyPaintObserver : public WebContentsObserver {
529 FirstVisuallyNonEmptyPaintObserver(Shell* shell)
530 : WebContentsObserver(shell->web_contents()),
531 did_fist_visually_non_empty_paint_(false) {}
533 virtual void DidFirstVisuallyNonEmptyPaint() OVERRIDE {
534 did_fist_visually_non_empty_paint_ = true;
535 on_did_first_visually_non_empty_paint_.Run();
538 void WaitForDidFirstVisuallyNonEmptyPaint() {
539 if (did_fist_visually_non_empty_paint_)
541 base::RunLoop run_loop;
542 on_did_first_visually_non_empty_paint_ = run_loop.QuitClosure();
546 base::Closure on_did_first_visually_non_empty_paint_;
547 bool did_fist_visually_non_empty_paint_;
550 // See: http://crbug.com/395664
551 #if defined(OS_ANDROID)
552 #define MAYBE_FirstVisuallyNonEmptyPaint DISABLED_FirstVisuallyNonEmptyPaint
554 // http://crbug.com/398471
555 #define MAYBE_FirstVisuallyNonEmptyPaint DISABLED_FirstVisuallyNonEmptyPaint
557 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
558 MAYBE_FirstVisuallyNonEmptyPaint) {
559 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
560 scoped_ptr<FirstVisuallyNonEmptyPaintObserver> observer(
561 new FirstVisuallyNonEmptyPaintObserver(shell()));
563 NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html"));
565 observer->WaitForDidFirstVisuallyNonEmptyPaint();
566 ASSERT_TRUE(observer->did_fist_visually_non_empty_paint_);
569 } // namespace content