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 // TODO(shrikant): enable this for Windows when issue with 263 // force-compositing-mode is resolved (http://crbug.com/281726). 264 // Also crashes under ThreadSanitizer, http://crbug.com/356758. 265 #if defined(OS_WIN) || defined(OS_ANDROID) \ 266 || defined(THREAD_SANITIZER) 267 #define MAYBE_GetSizeForNewRenderView DISABLED_GetSizeForNewRenderView 268 #else 269 #define MAYBE_GetSizeForNewRenderView GetSizeForNewRenderView 270 #endif 271 // Test that RenderViewHost is created and updated at the size specified by 272 // WebContentsDelegate::GetSizeForNewRenderView(). 273 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, 274 MAYBE_GetSizeForNewRenderView) { 275 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); 276 // Create a new server with a different site. 277 net::SpawnedTestServer https_server( 278 net::SpawnedTestServer::TYPE_HTTPS, 279 net::SpawnedTestServer::kLocalhost, 280 base::FilePath(FILE_PATH_LITERAL("content/test/data"))); 281 ASSERT_TRUE(https_server.Start()); 282 283 scoped_ptr<RenderViewSizeDelegate> delegate(new RenderViewSizeDelegate()); 284 shell()->web_contents()->SetDelegate(delegate.get()); 285 ASSERT_TRUE(shell()->web_contents()->GetDelegate() == delegate.get()); 286 287 // When no size is set, RenderWidgetHostView adopts the size of 288 // WebContentsView. 289 NavigateToURL(shell(), embedded_test_server()->GetURL("/title2.html")); 290 EXPECT_EQ(shell()->web_contents()->GetContainerBounds().size(), 291 shell()->web_contents()->GetRenderWidgetHostView()->GetViewBounds(). 292 size()); 293 294 // When a size is set, RenderWidgetHostView and WebContentsView honor this 295 // size. 296 gfx::Size size(300, 300); 297 gfx::Size size_insets(10, 15); 298 ResizeWebContentsView(shell(), size, true); 299 delegate->set_size_insets(size_insets); 300 NavigateToURL(shell(), https_server.GetURL("/")); 301 size.Enlarge(size_insets.width(), size_insets.height()); 302 EXPECT_EQ(size, 303 shell()->web_contents()->GetRenderWidgetHostView()->GetViewBounds(). 304 size()); 305 // The web_contents size is set by the embedder, and should not depend on the 306 // rwhv size. The behavior is correct on OSX, but incorrect on other 307 // platforms. 308 gfx::Size exp_wcv_size(300, 300); 309 #if !defined(OS_MACOSX) 310 exp_wcv_size.Enlarge(size_insets.width(), size_insets.height()); 311 #endif 312 313 EXPECT_EQ(exp_wcv_size, 314 shell()->web_contents()->GetContainerBounds().size()); 315 316 // If WebContentsView is resized after RenderWidgetHostView is created but 317 // before pending navigation entry is committed, both RenderWidgetHostView and 318 // WebContentsView use the new size of WebContentsView. 319 gfx::Size init_size(200, 200); 320 gfx::Size new_size(100, 100); 321 size_insets = gfx::Size(20, 30); 322 ResizeWebContentsView(shell(), init_size, true); 323 delegate->set_size_insets(size_insets); 324 RenderViewSizeObserver observer(shell(), new_size); 325 NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html")); 326 // RenderWidgetHostView is created at specified size. 327 init_size.Enlarge(size_insets.width(), size_insets.height()); 328 EXPECT_EQ(init_size, observer.rwhv_create_size()); 329 330 // Once again, the behavior is correct on OSX. The embedder explicitly sets 331 // the size to (100,100) during navigation. Both the wcv and the rwhv should 332 // take on that size. 333 #if !defined(OS_MACOSX) 334 new_size.Enlarge(size_insets.width(), size_insets.height()); 335 #endif 336 gfx::Size actual_size = shell()->web_contents()->GetRenderWidgetHostView()-> 337 GetViewBounds().size(); 338 339 EXPECT_EQ(new_size, actual_size); 340 EXPECT_EQ(new_size, shell()->web_contents()->GetContainerBounds().size()); 341 } 342 343 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, OpenURLSubframe) { 344 345 // Navigate with FrameTreeNode ID 4. 346 const GURL url("http://foo"); 347 OpenURLParams params(url, Referrer(), 4, CURRENT_TAB, PAGE_TRANSITION_LINK, 348 true); 349 shell()->web_contents()->OpenURL(params); 350 351 // Make sure the NavigationEntry ends up with the FrameTreeNode ID. 352 NavigationController* controller = &shell()->web_contents()->GetController(); 353 EXPECT_TRUE(controller->GetPendingEntry()); 354 EXPECT_EQ(4, NavigationEntryImpl::FromNavigationEntry( 355 controller->GetPendingEntry())->frame_tree_node_id()); 356 } 357 358 // Observer class to track the creation of RenderFrameHost objects. It is used 359 // in subsequent tests. 360 class RenderFrameCreatedObserver : public WebContentsObserver { 361 public: 362 RenderFrameCreatedObserver(Shell* shell) 363 : WebContentsObserver(shell->web_contents()), 364 last_rfh_(NULL) { 365 } 366 367 virtual void RenderFrameCreated(RenderFrameHost* render_frame_host) OVERRIDE { 368 last_rfh_ = render_frame_host; 369 } 370 371 RenderFrameHost* last_rfh() const { return last_rfh_; } 372 373 private: 374 RenderFrameHost* last_rfh_; 375 376 DISALLOW_COPY_AND_ASSIGN(RenderFrameCreatedObserver); 377 }; 378 379 // Test that creation of new RenderFrameHost objects sends the correct object 380 // to the WebContentObservers. See http://crbug.com/347339. 381 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, 382 RenderFrameCreatedCorrectProcessForObservers) { 383 std::string foo_com("foo.com"); 384 GURL::Replacements replace_host; 385 net::HostPortPair foo_host_port; 386 GURL cross_site_url; 387 388 // Setup the server to allow serving separate sites, so we can perform 389 // cross-process navigation. 390 host_resolver()->AddRule("*", "127.0.0.1"); 391 ASSERT_TRUE(test_server()->Start()); 392 393 foo_host_port = test_server()->host_port_pair(); 394 foo_host_port.set_host(foo_com); 395 396 GURL initial_url(test_server()->GetURL("/title1.html")); 397 398 cross_site_url = test_server()->GetURL("/title2.html"); 399 replace_host.SetHostStr(foo_com); 400 cross_site_url = cross_site_url.ReplaceComponents(replace_host); 401 402 // Navigate to the initial URL and capture the RenderFrameHost for later 403 // comparison. 404 NavigateToURL(shell(), initial_url); 405 RenderFrameHost* orig_rfh = shell()->web_contents()->GetMainFrame(); 406 407 // Install the observer and navigate cross-site. 408 RenderFrameCreatedObserver observer(shell()); 409 NavigateToURL(shell(), cross_site_url); 410 411 // The observer should've seen a RenderFrameCreated call for the new frame 412 // and not the old one. 413 EXPECT_NE(observer.last_rfh(), orig_rfh); 414 EXPECT_EQ(observer.last_rfh(), shell()->web_contents()->GetMainFrame()); 415 } 416 417 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, 418 LoadingStateChangedForSameDocumentNavigation) { 419 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); 420 scoped_ptr<LoadingStateChangedDelegate> delegate( 421 new LoadingStateChangedDelegate()); 422 shell()->web_contents()->SetDelegate(delegate.get()); 423 424 LoadStopNotificationObserver load_observer( 425 &shell()->web_contents()->GetController()); 426 TitleWatcher title_watcher(shell()->web_contents(), 427 base::ASCIIToUTF16("pushState")); 428 NavigateToURL(shell(), embedded_test_server()->GetURL("/push_state.html")); 429 load_observer.Wait(); 430 base::string16 title = title_watcher.WaitAndGetTitle(); 431 ASSERT_EQ(title, base::ASCIIToUTF16("pushState")); 432 433 // LoadingStateChanged should be called 4 times: start and stop for the 434 // initial load of push_state.html, and start and stop for the "navigation" 435 // triggered by history.pushState(). However, the start notification for the 436 // history.pushState() navigation should set to_different_document to false. 437 EXPECT_EQ("pushState", shell()->web_contents()->GetURL().ref()); 438 EXPECT_EQ(4, delegate->loadingStateChangedCount()); 439 EXPECT_EQ(3, delegate->loadingStateToDifferentDocumentCount()); 440 } 441 442 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, 443 RenderViewCreatedForChildWindow) { 444 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); 445 446 NavigateToURL(shell(), 447 embedded_test_server()->GetURL("/title1.html")); 448 449 WebContentsAddedObserver new_web_contents_observer; 450 ASSERT_TRUE(ExecuteScript(shell()->web_contents(), 451 "var a = document.createElement('a');" 452 "a.href='./title2.html';" 453 "a.target = '_blank';" 454 "document.body.appendChild(a);" 455 "a.click();")); 456 WebContents* new_web_contents = new_web_contents_observer.GetWebContents(); 457 WaitForLoadStop(new_web_contents); 458 EXPECT_TRUE(new_web_contents_observer.RenderViewCreatedCalled()); 459 } 460 461 struct LoadProgressDelegateAndObserver : public WebContentsDelegate, 462 public WebContentsObserver { 463 LoadProgressDelegateAndObserver(Shell* shell) 464 : WebContentsObserver(shell->web_contents()), 465 did_start_loading(false), 466 did_stop_loading(false) { 467 web_contents()->SetDelegate(this); 468 } 469 470 // WebContentsDelegate: 471 virtual void LoadProgressChanged(WebContents* source, 472 double progress) OVERRIDE { 473 EXPECT_TRUE(did_start_loading); 474 EXPECT_FALSE(did_stop_loading); 475 progresses.push_back(progress); 476 } 477 478 // WebContentsObserver: 479 virtual void DidStartLoading(RenderViewHost* render_view_host) OVERRIDE { 480 EXPECT_FALSE(did_start_loading); 481 EXPECT_EQ(0U, progresses.size()); 482 EXPECT_FALSE(did_stop_loading); 483 did_start_loading = true; 484 } 485 486 virtual void DidStopLoading(RenderViewHost* render_view_host) OVERRIDE { 487 EXPECT_TRUE(did_start_loading); 488 EXPECT_GE(progresses.size(), 1U); 489 EXPECT_FALSE(did_stop_loading); 490 did_stop_loading = true; 491 } 492 493 bool did_start_loading; 494 std::vector<double> progresses; 495 bool did_stop_loading; 496 }; 497 498 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, LoadProgress) { 499 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); 500 scoped_ptr<LoadProgressDelegateAndObserver> delegate( 501 new LoadProgressDelegateAndObserver(shell())); 502 503 NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html")); 504 505 const std::vector<double>& progresses = delegate->progresses; 506 // All updates should be in order ... 507 if (std::adjacent_find(progresses.begin(), 508 progresses.end(), 509 std::greater<double>()) != progresses.end()) { 510 ADD_FAILURE() << "Progress values should be in order: " 511 << ::testing::PrintToString(progresses); 512 } 513 514 // ... and the last one should be 1.0, meaning complete. 515 ASSERT_GE(progresses.size(), 1U) 516 << "There should be at least one progress update"; 517 EXPECT_EQ(1.0, *progresses.rbegin()); 518 } 519 520 } // namespace content 521 522