web_contents_impl_browsertest.cc revision 010d83a9304c5a91596085d917d248abff47903a
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 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 static_cast<WebContentsImpl*>(shell->web_contents())->GetView()-> 44 SizeContents(size); 45#endif // defined(OS_MACOSX) 46} 47 48class 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. 58class 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. 86class 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_->LoadURL(url_); 101 } 102 } 103 104 Shell* shell_; 105 GURL url_; 106 bool done_; 107}; 108 109class RenderViewSizeDelegate : public WebContentsDelegate { 110 public: 111 void set_size_insets(const gfx::Size& size_insets) { 112 size_insets_ = size_insets; 113 } 114 115 // WebContentsDelegate: 116 virtual gfx::Size GetSizeForNewRenderView( 117 WebContents* web_contents) const OVERRIDE { 118 gfx::Size size(web_contents->GetContainerBounds().size()); 119 size.Enlarge(size_insets_.width(), size_insets_.height()); 120 return size; 121 } 122 123 private: 124 gfx::Size size_insets_; 125}; 126 127class RenderViewSizeObserver : public WebContentsObserver { 128 public: 129 RenderViewSizeObserver(Shell* shell, const gfx::Size& wcv_new_size) 130 : WebContentsObserver(shell->web_contents()), 131 shell_(shell), 132 wcv_new_size_(wcv_new_size) { 133 } 134 135 // WebContentsObserver: 136 virtual void RenderViewCreated(RenderViewHost* rvh) OVERRIDE { 137 rwhv_create_size_ = rvh->GetView()->GetViewBounds().size(); 138 } 139 140 virtual void DidStartNavigationToPendingEntry( 141 const GURL& url, 142 NavigationController::ReloadType reload_type) OVERRIDE { 143 ResizeWebContentsView(shell_, wcv_new_size_, false); 144 } 145 146 gfx::Size rwhv_create_size() const { return rwhv_create_size_; } 147 148 private: 149 Shell* shell_; // Weak ptr. 150 gfx::Size wcv_new_size_; 151 gfx::Size rwhv_create_size_; 152}; 153 154class LoadingStateChangedDelegate : public WebContentsDelegate { 155 public: 156 LoadingStateChangedDelegate() 157 : loadingStateChangedCount_(0) 158 , loadingStateToDifferentDocumentCount_(0) { 159 } 160 161 // WebContentsDelgate: 162 virtual void LoadingStateChanged(WebContents* contents, 163 bool to_different_document) OVERRIDE { 164 loadingStateChangedCount_++; 165 if (to_different_document) 166 loadingStateToDifferentDocumentCount_++; 167 } 168 169 int loadingStateChangedCount() const { return loadingStateChangedCount_; } 170 int loadingStateToDifferentDocumentCount() const { 171 return loadingStateToDifferentDocumentCount_; 172 } 173 174 private: 175 int loadingStateChangedCount_; 176 int loadingStateToDifferentDocumentCount_; 177}; 178 179// See: http://crbug.com/298193 180#if defined(OS_WIN) 181#define MAYBE_DidStopLoadingDetails DISABLED_DidStopLoadingDetails 182#else 183#define MAYBE_DidStopLoadingDetails DidStopLoadingDetails 184#endif 185 186// Test that DidStopLoading includes the correct URL in the details. 187IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, 188 MAYBE_DidStopLoadingDetails) { 189 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); 190 191 LoadStopNotificationObserver load_observer( 192 &shell()->web_contents()->GetController()); 193 NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html")); 194 load_observer.Wait(); 195 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_); 200} 201 202// See: http://crbug.com/298193 203#if defined(OS_WIN) 204#define MAYBE_DidStopLoadingDetailsWithPending \ 205 DISABLED_DidStopLoadingDetailsWithPending 206#else 207#define MAYBE_DidStopLoadingDetailsWithPending DidStopLoadingDetailsWithPending 208#endif 209 210// Test that DidStopLoading includes the correct URL in the details when a 211// pending entry is present. 212IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, 213 MAYBE_DidStopLoadingDetailsWithPending) { 214 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); 215 216 // Listen for the first load to stop. 217 LoadStopNotificationObserver load_observer( 218 &shell()->web_contents()->GetController()); 219 // Start a new pending navigation as soon as the first load commits. 220 // We will hear a DidStopLoading from the first load as the new load 221 // is started. 222 NavigateOnCommitObserver commit_observer( 223 shell(), embedded_test_server()->GetURL("/title2.html")); 224 NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html")); 225 load_observer.Wait(); 226 227 EXPECT_EQ("/title1.html", load_observer.url_.path()); 228 EXPECT_EQ(0, load_observer.session_index_); 229 EXPECT_EQ(&shell()->web_contents()->GetController(), 230 load_observer.controller_); 231} 232// Test that a renderer-initiated navigation to an invalid URL does not leave 233// around a pending entry that could be used in a URL spoof. We test this in 234// a browser test because our unit test framework incorrectly calls 235// DidStartProvisionalLoadForFrame for in-page navigations. 236// See http://crbug.com/280512. 237IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, 238 ClearNonVisiblePendingOnFail) { 239 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); 240 241 NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html")); 242 243 // Navigate to an invalid URL and make sure it doesn't leave a pending entry. 244 LoadStopNotificationObserver load_observer1( 245 &shell()->web_contents()->GetController()); 246 ASSERT_TRUE(ExecuteScript(shell()->web_contents(), 247 "window.location.href=\"nonexistent:12121\";")); 248 load_observer1.Wait(); 249 EXPECT_FALSE(shell()->web_contents()->GetController().GetPendingEntry()); 250 251 LoadStopNotificationObserver load_observer2( 252 &shell()->web_contents()->GetController()); 253 ASSERT_TRUE(ExecuteScript(shell()->web_contents(), 254 "window.location.href=\"#foo\";")); 255 load_observer2.Wait(); 256 EXPECT_EQ(embedded_test_server()->GetURL("/title1.html#foo"), 257 shell()->web_contents()->GetVisibleURL()); 258} 259 260// TODO(shrikant): enable this for Windows when issue with 261// force-compositing-mode is resolved (http://crbug.com/281726). 262// Also 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(). 271IN_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 341IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, OpenURLSubframe) { 342 343 // Navigate with FrameTreeNode ID 4. 344 const GURL url("http://foo"); 345 OpenURLParams params(url, Referrer(), 4, CURRENT_TAB, PAGE_TRANSITION_LINK, 346 true); 347 shell()->web_contents()->OpenURL(params); 348 349 // Make sure the NavigationEntry ends up with the FrameTreeNode ID. 350 NavigationController* controller = &shell()->web_contents()->GetController(); 351 EXPECT_TRUE(controller->GetPendingEntry()); 352 EXPECT_EQ(4, NavigationEntryImpl::FromNavigationEntry( 353 controller->GetPendingEntry())->frame_tree_node_id()); 354} 355 356// Observer class to track the creation of RenderFrameHost objects. It is used 357// in subsequent tests. 358class RenderFrameCreatedObserver : public WebContentsObserver { 359 public: 360 RenderFrameCreatedObserver(Shell* shell) 361 : WebContentsObserver(shell->web_contents()), 362 last_rfh_(NULL) { 363 } 364 365 virtual void RenderFrameCreated(RenderFrameHost* render_frame_host) OVERRIDE { 366 last_rfh_ = render_frame_host; 367 } 368 369 RenderFrameHost* last_rfh() const { return last_rfh_; } 370 371 private: 372 RenderFrameHost* last_rfh_; 373 374 DISALLOW_COPY_AND_ASSIGN(RenderFrameCreatedObserver); 375}; 376 377// Test that creation of new RenderFrameHost objects sends the correct object 378// to the WebContentObservers. See http://crbug.com/347339. 379IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, 380 RenderFrameCreatedCorrectProcessForObservers) { 381 std::string foo_com("foo.com"); 382 GURL::Replacements replace_host; 383 net::HostPortPair foo_host_port; 384 GURL cross_site_url; 385 386 // Setup the server to allow serving separate sites, so we can perform 387 // cross-process navigation. 388 host_resolver()->AddRule("*", "127.0.0.1"); 389 ASSERT_TRUE(test_server()->Start()); 390 391 foo_host_port = test_server()->host_port_pair(); 392 foo_host_port.set_host(foo_com); 393 394 GURL initial_url(test_server()->GetURL("/title1.html")); 395 396 cross_site_url = test_server()->GetURL("/title2.html"); 397 replace_host.SetHostStr(foo_com); 398 cross_site_url = cross_site_url.ReplaceComponents(replace_host); 399 400 // Navigate to the initial URL and capture the RenderFrameHost for later 401 // comparison. 402 NavigateToURL(shell(), initial_url); 403 RenderFrameHost* orig_rfh = shell()->web_contents()->GetMainFrame(); 404 405 // Install the observer and navigate cross-site. 406 RenderFrameCreatedObserver observer(shell()); 407 NavigateToURL(shell(), cross_site_url); 408 409 // The observer should've seen a RenderFrameCreated call for the new frame 410 // and not the old one. 411 EXPECT_NE(observer.last_rfh(), orig_rfh); 412 EXPECT_EQ(observer.last_rfh(), shell()->web_contents()->GetMainFrame()); 413} 414 415IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, 416 LoadingStateChangedForSameDocumentNavigation) { 417 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); 418 scoped_ptr<LoadingStateChangedDelegate> delegate( 419 new LoadingStateChangedDelegate()); 420 shell()->web_contents()->SetDelegate(delegate.get()); 421 422 LoadStopNotificationObserver load_observer( 423 &shell()->web_contents()->GetController()); 424 TitleWatcher title_watcher(shell()->web_contents(), 425 base::ASCIIToUTF16("pushState")); 426 NavigateToURL(shell(), embedded_test_server()->GetURL("/push_state.html")); 427 load_observer.Wait(); 428 base::string16 title = title_watcher.WaitAndGetTitle(); 429 ASSERT_EQ(title, base::ASCIIToUTF16("pushState")); 430 431 // LoadingStateChanged should be called 4 times: start and stop for the 432 // initial load of push_state.html, and start and stop for the "navigation" 433 // triggered by history.pushState(). However, the start notification for the 434 // history.pushState() navigation should set to_different_document to false. 435 EXPECT_EQ("pushState", shell()->web_contents()->GetURL().ref()); 436 EXPECT_EQ(4, delegate->loadingStateChangedCount()); 437 EXPECT_EQ(3, delegate->loadingStateToDifferentDocumentCount()); 438} 439 440} // namespace content 441 442