web_contents_impl_browsertest.cc revision 5c02ac1a9c1b504631c0a3d2b6e737b5d738bae1
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/public/browser/load_notification_details.h" 10#include "content/public/browser/navigation_controller.h" 11#include "content/public/browser/notification_details.h" 12#include "content/public/browser/notification_observer.h" 13#include "content/public/browser/notification_types.h" 14#include "content/public/browser/render_view_host.h" 15#include "content/public/browser/render_widget_host_view.h" 16#include "content/public/browser/web_contents_observer.h" 17#include "content/public/browser/web_contents_view.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 27namespace content { 28 29void 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 shell->web_contents()->GetView()->SizeContents(size); 44#endif // defined(OS_MACOSX) 45} 46 47class WebContentsImplBrowserTest : public ContentBrowserTest { 48 public: 49 WebContentsImplBrowserTest() {} 50 51 private: 52 DISALLOW_COPY_AND_ASSIGN(WebContentsImplBrowserTest); 53}; 54 55// Keeps track of data from LoadNotificationDetails so we can later verify that 56// they are correct, after the LoadNotificationDetails object is deleted. 57class LoadStopNotificationObserver : public WindowedNotificationObserver { 58 public: 59 LoadStopNotificationObserver(NavigationController* controller) 60 : WindowedNotificationObserver(NOTIFICATION_LOAD_STOP, 61 Source<NavigationController>(controller)), 62 session_index_(-1), 63 controller_(NULL) { 64 } 65 virtual void Observe(int type, 66 const NotificationSource& source, 67 const NotificationDetails& details) OVERRIDE { 68 if (type == NOTIFICATION_LOAD_STOP) { 69 const Details<LoadNotificationDetails> load_details(details); 70 url_ = load_details->url; 71 session_index_ = load_details->session_index; 72 controller_ = load_details->controller; 73 } 74 WindowedNotificationObserver::Observe(type, source, details); 75 } 76 77 GURL url_; 78 int session_index_; 79 NavigationController* controller_; 80}; 81 82// Starts a new navigation as soon as the current one commits, but does not 83// wait for it to complete. This allows us to observe DidStopLoading while 84// a pending entry is present. 85class NavigateOnCommitObserver : public WebContentsObserver { 86 public: 87 NavigateOnCommitObserver(Shell* shell, GURL url) 88 : WebContentsObserver(shell->web_contents()), 89 shell_(shell), 90 url_(url), 91 done_(false) { 92 } 93 94 // WebContentsObserver: 95 virtual void NavigationEntryCommitted( 96 const LoadCommittedDetails& load_details) OVERRIDE { 97 if (!done_) { 98 done_ = true; 99 shell_->LoadURL(url_); 100 } 101 } 102 103 Shell* shell_; 104 GURL url_; 105 bool done_; 106}; 107 108class RenderViewSizeDelegate : public WebContentsDelegate { 109 public: 110 void set_size_insets(const gfx::Size& size_insets) { 111 size_insets_ = size_insets; 112 } 113 114 // WebContentsDelegate: 115 virtual gfx::Size GetSizeForNewRenderView( 116 const WebContents* web_contents) const OVERRIDE { 117 gfx::Size size(web_contents->GetView()->GetContainerSize()); 118 size.Enlarge(size_insets_.width(), size_insets_.height()); 119 return size; 120 } 121 122 private: 123 gfx::Size size_insets_; 124}; 125 126class RenderViewSizeObserver : public WebContentsObserver { 127 public: 128 RenderViewSizeObserver(Shell* shell, const gfx::Size& wcv_new_size) 129 : WebContentsObserver(shell->web_contents()), 130 shell_(shell), 131 wcv_new_size_(wcv_new_size) { 132 } 133 134 // WebContentsObserver: 135 virtual void RenderViewCreated(RenderViewHost* rvh) OVERRIDE { 136 rwhv_create_size_ = rvh->GetView()->GetViewBounds().size(); 137 } 138 139 virtual void DidStartNavigationToPendingEntry( 140 const GURL& url, 141 NavigationController::ReloadType reload_type) OVERRIDE { 142 ResizeWebContentsView(shell_, wcv_new_size_, false); 143 } 144 145 gfx::Size rwhv_create_size() const { return rwhv_create_size_; } 146 147 private: 148 Shell* shell_; // Weak ptr. 149 gfx::Size wcv_new_size_; 150 gfx::Size rwhv_create_size_; 151}; 152 153class LoadingStateChangedDelegate : public WebContentsDelegate { 154 public: 155 LoadingStateChangedDelegate() 156 : loadingStateChangedCount_(0) 157 , loadingStateToDifferentDocumentCount_(0) { 158 } 159 160 // WebContentsDelgate: 161 virtual void LoadingStateChanged(WebContents* contents, 162 bool to_different_document) OVERRIDE { 163 loadingStateChangedCount_++; 164 if (to_different_document) 165 loadingStateToDifferentDocumentCount_++; 166 } 167 168 int loadingStateChangedCount() const { return loadingStateChangedCount_; } 169 int loadingStateToDifferentDocumentCount() const { 170 return loadingStateToDifferentDocumentCount_; 171 } 172 173 private: 174 int loadingStateChangedCount_; 175 int loadingStateToDifferentDocumentCount_; 176}; 177 178// See: http://crbug.com/298193 179#if defined(OS_WIN) 180#define MAYBE_DidStopLoadingDetails DISABLED_DidStopLoadingDetails 181#else 182#define MAYBE_DidStopLoadingDetails DidStopLoadingDetails 183#endif 184 185// Test that DidStopLoading includes the correct URL in the details. 186IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, 187 MAYBE_DidStopLoadingDetails) { 188 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); 189 190 LoadStopNotificationObserver load_observer( 191 &shell()->web_contents()->GetController()); 192 NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html")); 193 load_observer.Wait(); 194 195 EXPECT_EQ("/title1.html", load_observer.url_.path()); 196 EXPECT_EQ(0, load_observer.session_index_); 197 EXPECT_EQ(&shell()->web_contents()->GetController(), 198 load_observer.controller_); 199} 200 201// See: http://crbug.com/298193 202#if defined(OS_WIN) 203#define MAYBE_DidStopLoadingDetailsWithPending \ 204 DISABLED_DidStopLoadingDetailsWithPending 205#else 206#define MAYBE_DidStopLoadingDetailsWithPending DidStopLoadingDetailsWithPending 207#endif 208 209// Test that DidStopLoading includes the correct URL in the details when a 210// pending entry is present. 211IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, 212 MAYBE_DidStopLoadingDetailsWithPending) { 213 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); 214 215 // Listen for the first load to stop. 216 LoadStopNotificationObserver load_observer( 217 &shell()->web_contents()->GetController()); 218 // Start a new pending navigation as soon as the first load commits. 219 // We will hear a DidStopLoading from the first load as the new load 220 // is started. 221 NavigateOnCommitObserver commit_observer( 222 shell(), embedded_test_server()->GetURL("/title2.html")); 223 NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html")); 224 load_observer.Wait(); 225 226 EXPECT_EQ("/title1.html", load_observer.url_.path()); 227 EXPECT_EQ(0, load_observer.session_index_); 228 EXPECT_EQ(&shell()->web_contents()->GetController(), 229 load_observer.controller_); 230} 231// Test that a renderer-initiated navigation to an invalid URL does not leave 232// around a pending entry that could be used in a URL spoof. We test this in 233// a browser test because our unit test framework incorrectly calls 234// DidStartProvisionalLoadForFrame for in-page navigations. 235// See http://crbug.com/280512. 236IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, 237 ClearNonVisiblePendingOnFail) { 238 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); 239 240 NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html")); 241 242 // Navigate to an invalid URL and make sure it doesn't leave a pending entry. 243 LoadStopNotificationObserver load_observer1( 244 &shell()->web_contents()->GetController()); 245 ASSERT_TRUE(ExecuteScript(shell()->web_contents(), 246 "window.location.href=\"nonexistent:12121\";")); 247 load_observer1.Wait(); 248 EXPECT_FALSE(shell()->web_contents()->GetController().GetPendingEntry()); 249 250 LoadStopNotificationObserver load_observer2( 251 &shell()->web_contents()->GetController()); 252 ASSERT_TRUE(ExecuteScript(shell()->web_contents(), 253 "window.location.href=\"#foo\";")); 254 load_observer2.Wait(); 255 EXPECT_EQ(embedded_test_server()->GetURL("/title1.html#foo"), 256 shell()->web_contents()->GetVisibleURL()); 257} 258 259// TODO(shrikant): enable this for Windows when issue with 260// force-compositing-mode is resolved (http://crbug.com/281726). 261// Also 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 265#else 266#define MAYBE_GetSizeForNewRenderView GetSizeForNewRenderView 267#endif 268// Test that RenderViewHost is created and updated at the size specified by 269// WebContentsDelegate::GetSizeForNewRenderView(). 270IN_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()); 279 280 scoped_ptr<RenderViewSizeDelegate> delegate(new RenderViewSizeDelegate()); 281 shell()->web_contents()->SetDelegate(delegate.get()); 282 ASSERT_TRUE(shell()->web_contents()->GetDelegate() == delegate.get()); 283 284 // When no size is set, RenderWidgetHostView adopts the size of 285 // WebContentsView. 286 NavigateToURL(shell(), embedded_test_server()->GetURL("/title2.html")); 287 EXPECT_EQ(shell()->web_contents()->GetView()->GetContainerSize(), 288 shell()->web_contents()->GetRenderWidgetHostView()->GetViewBounds(). 289 size()); 290 291 // When a size is set, RenderWidgetHostView and WebContentsView honor this 292 // size. 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()); 299 EXPECT_EQ(size, 300 shell()->web_contents()->GetRenderWidgetHostView()->GetViewBounds(). 301 size()); 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 304 // platforms. 305 gfx::Size exp_wcv_size(300, 300); 306#if !defined(OS_MACOSX) 307 exp_wcv_size.Enlarge(size_insets.width(), size_insets.height()); 308#endif 309 310 EXPECT_EQ(exp_wcv_size, 311 shell()->web_contents()->GetView()->GetContainerSize()); 312 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()); 326 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()); 332#endif 333 gfx::Size actual_size = shell()->web_contents()->GetRenderWidgetHostView()-> 334 GetViewBounds().size(); 335 336 EXPECT_EQ(new_size, actual_size); 337 EXPECT_EQ(new_size, shell()->web_contents()->GetView()->GetContainerSize()); 338} 339 340IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, OpenURLSubframe) { 341 342 // Navigate with FrameTreeNode ID 4. 343 const GURL url("http://foo"); 344 OpenURLParams params(url, Referrer(), 4, CURRENT_TAB, PAGE_TRANSITION_LINK, 345 true); 346 shell()->web_contents()->OpenURL(params); 347 348 // Make sure the NavigationEntry ends up with the FrameTreeNode ID. 349 NavigationController* controller = &shell()->web_contents()->GetController(); 350 EXPECT_TRUE(controller->GetPendingEntry()); 351 EXPECT_EQ(4, NavigationEntryImpl::FromNavigationEntry( 352 controller->GetPendingEntry())->frame_tree_node_id()); 353} 354 355// Observer class to track the creation of RenderFrameHost objects. It is used 356// in subsequent tests. 357class RenderFrameCreatedObserver : public WebContentsObserver { 358 public: 359 RenderFrameCreatedObserver(Shell* shell) 360 : WebContentsObserver(shell->web_contents()), 361 last_rfh_(NULL) { 362 } 363 364 virtual void RenderFrameCreated(RenderFrameHost* render_frame_host) OVERRIDE { 365 last_rfh_ = render_frame_host; 366 } 367 368 RenderFrameHost* last_rfh() const { return last_rfh_; } 369 370 private: 371 RenderFrameHost* last_rfh_; 372 373 DISALLOW_COPY_AND_ASSIGN(RenderFrameCreatedObserver); 374}; 375 376// Test that creation of new RenderFrameHost objects sends the correct object 377// to the WebContentObservers. See http://crbug.com/347339. 378IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, 379 RenderFrameCreatedCorrectProcessForObservers) { 380 std::string foo_com("foo.com"); 381 GURL::Replacements replace_host; 382 net::HostPortPair foo_host_port; 383 GURL cross_site_url; 384 385 // Setup the server to allow serving separate sites, so we can perform 386 // cross-process navigation. 387 host_resolver()->AddRule("*", "127.0.0.1"); 388 ASSERT_TRUE(test_server()->Start()); 389 390 foo_host_port = test_server()->host_port_pair(); 391 foo_host_port.set_host(foo_com); 392 393 GURL initial_url(test_server()->GetURL("/title1.html")); 394 395 cross_site_url = test_server()->GetURL("/title2.html"); 396 replace_host.SetHostStr(foo_com); 397 cross_site_url = cross_site_url.ReplaceComponents(replace_host); 398 399 // Navigate to the initial URL and capture the RenderFrameHost for later 400 // comparison. 401 NavigateToURL(shell(), initial_url); 402 RenderFrameHost* orig_rfh = shell()->web_contents()->GetMainFrame(); 403 404 // Install the observer and navigate cross-site. 405 RenderFrameCreatedObserver observer(shell()); 406 NavigateToURL(shell(), cross_site_url); 407 408 // The observer should've seen a RenderFrameCreated call for the new frame 409 // and not the old one. 410 EXPECT_NE(observer.last_rfh(), orig_rfh); 411 EXPECT_EQ(observer.last_rfh(), shell()->web_contents()->GetMainFrame()); 412} 413 414IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, 415 LoadingStateChangedForSameDocumentNavigation) { 416 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); 417 scoped_ptr<LoadingStateChangedDelegate> delegate( 418 new LoadingStateChangedDelegate()); 419 shell()->web_contents()->SetDelegate(delegate.get()); 420 421 LoadStopNotificationObserver load_observer( 422 &shell()->web_contents()->GetController()); 423 TitleWatcher title_watcher(shell()->web_contents(), 424 base::ASCIIToUTF16("pushState")); 425 NavigateToURL(shell(), embedded_test_server()->GetURL("/push_state.html")); 426 load_observer.Wait(); 427 base::string16 title = title_watcher.WaitAndGetTitle(); 428 ASSERT_EQ(title, base::ASCIIToUTF16("pushState")); 429 430 // LoadingStateChanged should be called 4 times: start and stop for the 431 // initial load of push_state.html, and start and stop for the "navigation" 432 // triggered by history.pushState(). However, the start notification for the 433 // history.pushState() navigation should set to_different_document to false. 434 EXPECT_EQ("pushState", shell()->web_contents()->GetURL().ref()); 435 EXPECT_EQ(4, delegate->loadingStateChangedCount()); 436 EXPECT_EQ(3, delegate->loadingStateToDifferentDocumentCount()); 437} 438 439} // namespace content 440 441