web_contents_impl_browsertest.cc revision effb81e5f8246d0db0270817048dc992db66e9fb
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(TOOLKIT_GTK) || 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(TOOLKIT_GTK) || 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// For TOOLKIT_GTK failure, see http://crbug.com/351234. 262// Also crashes under ThreadSanitizer, http://crbug.com/356758. 263#if defined(OS_WIN) || defined(OS_ANDROID) || defined(TOOLKIT_GTK) \ 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()->GetView()->GetContainerSize(), 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()->GetView()->GetContainerSize()); 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()->GetView()->GetContainerSize()); 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 LOG(ERROR) << "RFCreated: " << render_frame_host; 367 last_rfh_ = render_frame_host; 368 } 369 370 RenderFrameHost* last_rfh() const { return last_rfh_; } 371 372 private: 373 RenderFrameHost* last_rfh_; 374 375 DISALLOW_COPY_AND_ASSIGN(RenderFrameCreatedObserver); 376}; 377 378// Test that creation of new RenderFrameHost objects sends the correct object 379// to the WebContentObservers. See http://crbug.com/347339. 380IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, 381 RenderFrameCreatedCorrectProcessForObservers) { 382 std::string foo_com("foo.com"); 383 GURL::Replacements replace_host; 384 net::HostPortPair foo_host_port; 385 GURL cross_site_url; 386 387 // Setup the server to allow serving separate sites, so we can perform 388 // cross-process navigation. 389 host_resolver()->AddRule("*", "127.0.0.1"); 390 ASSERT_TRUE(test_server()->Start()); 391 392 foo_host_port = test_server()->host_port_pair(); 393 foo_host_port.set_host(foo_com); 394 395 GURL initial_url(test_server()->GetURL("/title1.html")); 396 397 cross_site_url = test_server()->GetURL("/title2.html"); 398 replace_host.SetHostStr(foo_com); 399 cross_site_url = cross_site_url.ReplaceComponents(replace_host); 400 401 // Navigate to the initial URL and capture the RenderFrameHost for later 402 // comparison. 403 NavigateToURL(shell(), initial_url); 404 RenderFrameHost* orig_rfh = shell()->web_contents()->GetMainFrame(); 405 406 // Install the observer and navigate cross-site. 407 RenderFrameCreatedObserver observer(shell()); 408 NavigateToURL(shell(), cross_site_url); 409 410 // The observer should've seen a RenderFrameCreated call for the new frame 411 // and not the old one. 412 EXPECT_NE(observer.last_rfh(), orig_rfh); 413 EXPECT_EQ(observer.last_rfh(), shell()->web_contents()->GetMainFrame()); 414} 415 416IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, 417 LoadingStateChangedForSameDocumentNavigation) { 418 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); 419 scoped_ptr<LoadingStateChangedDelegate> delegate( 420 new LoadingStateChangedDelegate()); 421 shell()->web_contents()->SetDelegate(delegate.get()); 422 423 LoadStopNotificationObserver load_observer( 424 &shell()->web_contents()->GetController()); 425 TitleWatcher title_watcher(shell()->web_contents(), 426 base::ASCIIToUTF16("pushState")); 427 NavigateToURL(shell(), embedded_test_server()->GetURL("/push_state.html")); 428 load_observer.Wait(); 429 base::string16 title = title_watcher.WaitAndGetTitle(); 430 ASSERT_EQ(title, base::ASCIIToUTF16("pushState")); 431 432 // LoadingStateChanged should be called 4 times: start and stop for the 433 // initial load of push_state.html, and start and stop for the "navigation" 434 // triggered by history.pushState(). However, the start notification for the 435 // history.pushState() navigation should set to_different_document to false. 436 EXPECT_EQ("pushState", shell()->web_contents()->GetURL().ref()); 437 EXPECT_EQ(4, delegate->loadingStateChangedCount()); 438 EXPECT_EQ(3, delegate->loadingStateToDifferentDocumentCount()); 439} 440 441} // namespace content 442