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. 4 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" 26 27 namespace content { 28 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) 37 shell->SizeTo(size); 38 // If |set_start_page| is true, start with blank page to make sure resize 39 // takes effect. 40 if (set_start_page) 41 NavigateToURL(shell, GURL("about://blank")); 42 #else 43 static_cast<WebContentsImpl*>(shell->web_contents())->GetView()-> 44 SizeContents(size); 45 #endif // defined(OS_MACOSX) 46 } 47 48 class WebContentsImplBrowserTest : public ContentBrowserTest { 49 public: 50 WebContentsImplBrowserTest() {} 51 52 private: 53 DISALLOW_COPY_AND_ASSIGN(WebContentsImplBrowserTest); 54 }; 55 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 { 59 public: 60 LoadStopNotificationObserver(NavigationController* controller) 61 : WindowedNotificationObserver(NOTIFICATION_LOAD_STOP, 62 Source<NavigationController>(controller)), 63 session_index_(-1), 64 controller_(NULL) { 65 } 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; 74 } 75 WindowedNotificationObserver::Observe(type, source, details); 76 } 77 78 GURL url_; 79 int session_index_; 80 NavigationController* controller_; 81 }; 82 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 { 87 public: 88 NavigateOnCommitObserver(Shell* shell, GURL url) 89 : WebContentsObserver(shell->web_contents()), 90 shell_(shell), 91 url_(url), 92 done_(false) { 93 } 94 95 // WebContentsObserver: 96 virtual void NavigationEntryCommitted( 97 const LoadCommittedDetails& load_details) OVERRIDE { 98 if (!done_) { 99 done_ = true; 100 shell_->Stop(); 101 shell_->LoadURL(url_); 102 } 103 } 104 105 Shell* shell_; 106 GURL url_; 107 bool done_; 108 }; 109 110 class RenderViewSizeDelegate : public WebContentsDelegate { 111 public: 112 void set_size_insets(const gfx::Size& size_insets) { 113 size_insets_ = size_insets; 114 } 115 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()); 121 return size; 122 } 123 124 private: 125 gfx::Size size_insets_; 126 }; 127 128 class RenderViewSizeObserver : public WebContentsObserver { 129 public: 130 RenderViewSizeObserver(Shell* shell, const gfx::Size& wcv_new_size) 131 : WebContentsObserver(shell->web_contents()), 132 shell_(shell), 133 wcv_new_size_(wcv_new_size) { 134 } 135 136 // WebContentsObserver: 137 virtual void RenderViewCreated(RenderViewHost* rvh) OVERRIDE { 138 rwhv_create_size_ = rvh->GetView()->GetViewBounds().size(); 139 } 140 141 virtual void DidStartNavigationToPendingEntry( 142 const GURL& url, 143 NavigationController::ReloadType reload_type) OVERRIDE { 144 ResizeWebContentsView(shell_, wcv_new_size_, false); 145 } 146 147 gfx::Size rwhv_create_size() const { return rwhv_create_size_; } 148 149 private: 150 Shell* shell_; // Weak ptr. 151 gfx::Size wcv_new_size_; 152 gfx::Size rwhv_create_size_; 153 }; 154 155 class LoadingStateChangedDelegate : public WebContentsDelegate { 156 public: 157 LoadingStateChangedDelegate() 158 : loadingStateChangedCount_(0) 159 , loadingStateToDifferentDocumentCount_(0) { 160 } 161 162 // WebContentsDelegate: 163 virtual void LoadingStateChanged(WebContents* contents, 164 bool to_different_document) OVERRIDE { 165 loadingStateChangedCount_++; 166 if (to_different_document) 167 loadingStateToDifferentDocumentCount_++; 168 } 169 170 int loadingStateChangedCount() const { return loadingStateChangedCount_; } 171 int loadingStateToDifferentDocumentCount() const { 172 return loadingStateToDifferentDocumentCount_; 173 } 174 175 private: 176 int loadingStateChangedCount_; 177 int loadingStateToDifferentDocumentCount_; 178 }; 179 180 // See: http://crbug.com/298193 181 #if defined(OS_WIN) 182 #define MAYBE_DidStopLoadingDetails DISABLED_DidStopLoadingDetails 183 #else 184 #define MAYBE_DidStopLoadingDetails DidStopLoadingDetails 185 #endif 186 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()); 191 192 LoadStopNotificationObserver load_observer( 193 &shell()->web_contents()->GetController()); 194 NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html")); 195 load_observer.Wait(); 196 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_); 201 } 202 203 // See: http://crbug.com/298193 204 #if defined(OS_WIN) 205 #define MAYBE_DidStopLoadingDetailsWithPending \ 206 DISABLED_DidStopLoadingDetailsWithPending 207 #else 208 #define MAYBE_DidStopLoadingDetailsWithPending DidStopLoadingDetailsWithPending 209 #endif 210 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>"); 217 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 223 // is started. 224 NavigateOnCommitObserver commit_observer( 225 shell(), embedded_test_server()->GetURL("/title2.html")); 226 NavigateToURL(shell(), url); 227 load_observer.Wait(); 228 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_); 233 } 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()); 242 243 NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html")); 244 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()); 252 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()); 260 } 261 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 266 #else 267 #define MAYBE_GetSizeForNewRenderView GetSizeForNewRenderView 268 #endif 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()); 280 281 scoped_ptr<RenderViewSizeDelegate> delegate(new RenderViewSizeDelegate()); 282 shell()->web_contents()->SetDelegate(delegate.get()); 283 ASSERT_TRUE(shell()->web_contents()->GetDelegate() == delegate.get()); 284 285 // When no size is set, RenderWidgetHostView adopts the size of 286 // WebContentsView. 287 NavigateToURL(shell(), embedded_test_server()->GetURL("/title2.html")); 288 EXPECT_EQ(shell()->web_contents()->GetContainerBounds().size(), 289 shell()->web_contents()->GetRenderWidgetHostView()->GetViewBounds(). 290 size()); 291 292 // When a size is set, RenderWidgetHostView and WebContentsView honor this 293 // size. 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()); 300 EXPECT_EQ(size, 301 shell()->web_contents()->GetRenderWidgetHostView()->GetViewBounds(). 302 size()); 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 305 // platforms. 306 gfx::Size exp_wcv_size(300, 300); 307 #if !defined(OS_MACOSX) 308 exp_wcv_size.Enlarge(size_insets.width(), size_insets.height()); 309 #endif 310 311 EXPECT_EQ(exp_wcv_size, 312 shell()->web_contents()->GetContainerBounds().size()); 313 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()); 327 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()); 333 #endif 334 gfx::Size actual_size = shell()->web_contents()->GetRenderWidgetHostView()-> 335 GetViewBounds().size(); 336 337 EXPECT_EQ(new_size, actual_size); 338 EXPECT_EQ(new_size, shell()->web_contents()->GetContainerBounds().size()); 339 } 340 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); 351 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); 357 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()); 364 } 365 366 // Observer class to track the creation of RenderFrameHost objects. It is used 367 // in subsequent tests. 368 class RenderFrameCreatedObserver : public WebContentsObserver { 369 public: 370 RenderFrameCreatedObserver(Shell* shell) 371 : WebContentsObserver(shell->web_contents()), 372 last_rfh_(NULL) { 373 } 374 375 virtual void RenderFrameCreated(RenderFrameHost* render_frame_host) OVERRIDE { 376 last_rfh_ = render_frame_host; 377 } 378 379 RenderFrameHost* last_rfh() const { return last_rfh_; } 380 381 private: 382 RenderFrameHost* last_rfh_; 383 384 DISALLOW_COPY_AND_ASSIGN(RenderFrameCreatedObserver); 385 }; 386 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; 394 GURL cross_site_url; 395 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()); 400 401 foo_host_port = test_server()->host_port_pair(); 402 foo_host_port.set_host(foo_com); 403 404 GURL initial_url(test_server()->GetURL("/title1.html")); 405 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); 409 410 // Navigate to the initial URL and capture the RenderFrameHost for later 411 // comparison. 412 NavigateToURL(shell(), initial_url); 413 RenderFrameHost* orig_rfh = shell()->web_contents()->GetMainFrame(); 414 415 // Install the observer and navigate cross-site. 416 RenderFrameCreatedObserver observer(shell()); 417 NavigateToURL(shell(), cross_site_url); 418 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()); 423 } 424 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()); 431 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")); 440 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()); 448 } 449 450 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, 451 RenderViewCreatedForChildWindow) { 452 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); 453 454 NavigateToURL(shell(), 455 embedded_test_server()->GetURL("/title1.html")); 456 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);" 463 "a.click();")); 464 WebContents* new_web_contents = new_web_contents_observer.GetWebContents(); 465 WaitForLoadStop(new_web_contents); 466 EXPECT_TRUE(new_web_contents_observer.RenderViewCreatedCalled()); 467 } 468 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); 476 } 477 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); 484 } 485 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; 492 } 493 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; 499 } 500 501 bool did_start_loading; 502 std::vector<double> progresses; 503 bool did_stop_loading; 504 }; 505 506 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, LoadProgress) { 507 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); 508 scoped_ptr<LoadProgressDelegateAndObserver> delegate( 509 new LoadProgressDelegateAndObserver(shell())); 510 511 NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html")); 512 513 const std::vector<double>& progresses = delegate->progresses; 514 // All updates should be in order ... 515 if (std::adjacent_find(progresses.begin(), 516 progresses.end(), 517 std::greater<double>()) != progresses.end()) { 518 ADD_FAILURE() << "Progress values should be in order: " 519 << ::testing::PrintToString(progresses); 520 } 521 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()); 526 } 527 528 struct FirstVisuallyNonEmptyPaintObserver : public WebContentsObserver { 529 FirstVisuallyNonEmptyPaintObserver(Shell* shell) 530 : WebContentsObserver(shell->web_contents()), 531 did_fist_visually_non_empty_paint_(false) {} 532 533 virtual void DidFirstVisuallyNonEmptyPaint() OVERRIDE { 534 did_fist_visually_non_empty_paint_ = true; 535 on_did_first_visually_non_empty_paint_.Run(); 536 } 537 538 void WaitForDidFirstVisuallyNonEmptyPaint() { 539 if (did_fist_visually_non_empty_paint_) 540 return; 541 base::RunLoop run_loop; 542 on_did_first_visually_non_empty_paint_ = run_loop.QuitClosure(); 543 run_loop.Run(); 544 } 545 546 base::Closure on_did_first_visually_non_empty_paint_; 547 bool did_fist_visually_non_empty_paint_; 548 }; 549 550 // See: http://crbug.com/395664 551 #if defined(OS_ANDROID) 552 #define MAYBE_FirstVisuallyNonEmptyPaint DISABLED_FirstVisuallyNonEmptyPaint 553 #else 554 // http://crbug.com/398471 555 #define MAYBE_FirstVisuallyNonEmptyPaint DISABLED_FirstVisuallyNonEmptyPaint 556 #endif 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())); 562 563 NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html")); 564 565 observer->WaitForDidFirstVisuallyNonEmptyPaint(); 566 ASSERT_TRUE(observer->did_fist_visually_non_empty_paint_); 567 } 568 569 } // namespace content 570 571