cc: Make picture pile base thread safe.
[chromium-blink-merge.git] / content / browser / web_contents / web_contents_impl_browsertest.cc
blob349af0e73e52e535b3dd3f49e249b970241116a2
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.
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"
27 namespace content {
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)
48 class WebContentsImplBrowserTest : public ContentBrowserTest {
49 public:
50 WebContentsImplBrowserTest() {}
52 private:
53 DISALLOW_COPY_AND_ASSIGN(WebContentsImplBrowserTest);
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) {
66 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;
75 WindowedNotificationObserver::Observe(type, source, details);
78 GURL url_;
79 int session_index_;
80 NavigationController* controller_;
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) {
95 // WebContentsObserver:
96 void NavigationEntryCommitted(
97 const LoadCommittedDetails& load_details) override {
98 if (!done_) {
99 done_ = true;
100 shell_->Stop();
101 shell_->LoadURL(url_);
105 Shell* shell_;
106 GURL url_;
107 bool done_;
110 class RenderViewSizeDelegate : public WebContentsDelegate {
111 public:
112 void set_size_insets(const gfx::Size& size_insets) {
113 size_insets_ = size_insets;
116 // WebContentsDelegate:
117 gfx::Size GetSizeForNewRenderView(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;
123 private:
124 gfx::Size size_insets_;
127 class 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) {
135 // WebContentsObserver:
136 void RenderViewCreated(RenderViewHost* rvh) override {
137 rwhv_create_size_ = rvh->GetView()->GetViewBounds().size();
140 void DidStartNavigationToPendingEntry(
141 const GURL& url,
142 NavigationController::ReloadType reload_type) override {
143 ResizeWebContentsView(shell_, wcv_new_size_, false);
146 gfx::Size rwhv_create_size() const { return rwhv_create_size_; }
148 private:
149 Shell* shell_; // Weak ptr.
150 gfx::Size wcv_new_size_;
151 gfx::Size rwhv_create_size_;
154 class LoadingStateChangedDelegate : public WebContentsDelegate {
155 public:
156 LoadingStateChangedDelegate()
157 : loadingStateChangedCount_(0)
158 , loadingStateToDifferentDocumentCount_(0) {
161 // WebContentsDelegate:
162 void LoadingStateChanged(WebContents* contents,
163 bool to_different_document) override {
164 loadingStateChangedCount_++;
165 if (to_different_document)
166 loadingStateToDifferentDocumentCount_++;
169 int loadingStateChangedCount() const { return loadingStateChangedCount_; }
170 int loadingStateToDifferentDocumentCount() const {
171 return loadingStateToDifferentDocumentCount_;
174 private:
175 int loadingStateChangedCount_;
176 int loadingStateToDifferentDocumentCount_;
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
186 // Test that DidStopLoading includes the correct URL in the details.
187 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
188 MAYBE_DidStopLoadingDetails) {
189 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
191 LoadStopNotificationObserver load_observer(
192 &shell()->web_contents()->GetController());
193 NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html"));
194 load_observer.Wait();
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_);
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
210 // Test that DidStopLoading includes the correct URL in the details when a
211 // pending entry is present.
212 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
213 MAYBE_DidStopLoadingDetailsWithPending) {
214 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
215 GURL url("data:text/html,<div>test</div>");
217 // Listen for the first load to stop.
218 LoadStopNotificationObserver load_observer(
219 &shell()->web_contents()->GetController());
220 // Start a new pending navigation as soon as the first load commits.
221 // We will hear a DidStopLoading from the first load as the new load
222 // is started.
223 NavigateOnCommitObserver commit_observer(
224 shell(), embedded_test_server()->GetURL("/title2.html"));
225 NavigateToURL(shell(), url);
226 load_observer.Wait();
228 EXPECT_EQ(url, load_observer.url_);
229 EXPECT_EQ(0, load_observer.session_index_);
230 EXPECT_EQ(&shell()->web_contents()->GetController(),
231 load_observer.controller_);
233 // Test that a renderer-initiated navigation to an invalid URL does not leave
234 // around a pending entry that could be used in a URL spoof. We test this in
235 // a browser test because our unit test framework incorrectly calls
236 // DidStartProvisionalLoadForFrame for in-page navigations.
237 // See http://crbug.com/280512.
238 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
239 ClearNonVisiblePendingOnFail) {
240 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
242 NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html"));
244 // Navigate to an invalid URL and make sure it doesn't leave a pending entry.
245 LoadStopNotificationObserver load_observer1(
246 &shell()->web_contents()->GetController());
247 ASSERT_TRUE(ExecuteScript(shell()->web_contents(),
248 "window.location.href=\"nonexistent:12121\";"));
249 load_observer1.Wait();
250 EXPECT_FALSE(shell()->web_contents()->GetController().GetPendingEntry());
252 LoadStopNotificationObserver load_observer2(
253 &shell()->web_contents()->GetController());
254 ASSERT_TRUE(ExecuteScript(shell()->web_contents(),
255 "window.location.href=\"#foo\";"));
256 load_observer2.Wait();
257 EXPECT_EQ(embedded_test_server()->GetURL("/title1.html#foo"),
258 shell()->web_contents()->GetVisibleURL());
261 // 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().
270 IN_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());
280 scoped_ptr<RenderViewSizeDelegate> delegate(new RenderViewSizeDelegate());
281 shell()->web_contents()->SetDelegate(delegate.get());
282 ASSERT_TRUE(shell()->web_contents()->GetDelegate() == delegate.get());
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()->GetContainerBounds().size(),
288 shell()->web_contents()->GetRenderWidgetHostView()->GetViewBounds().
289 size());
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
310 EXPECT_EQ(exp_wcv_size,
311 shell()->web_contents()->GetContainerBounds().size());
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());
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();
336 EXPECT_EQ(new_size, actual_size);
337 EXPECT_EQ(new_size, shell()->web_contents()->GetContainerBounds().size());
340 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, OpenURLSubframe) {
341 // Navigate to a page with frames and grab a subframe's FrameTreeNode ID.
342 ASSERT_TRUE(test_server()->Start());
343 NavigateToURL(shell(),
344 test_server()->GetURL("files/frame_tree/top.html"));
345 WebContentsImpl* wc = static_cast<WebContentsImpl*>(shell()->web_contents());
346 FrameTreeNode* root = wc->GetFrameTree()->root();
347 ASSERT_EQ(3UL, root->child_count());
348 int64 frame_tree_node_id = root->child_at(0)->frame_tree_node_id();
349 EXPECT_NE(-1, frame_tree_node_id);
351 // Navigate with the subframe's FrameTreeNode ID.
352 const GURL url(test_server()->GetURL("files/title1.html"));
353 OpenURLParams params(url, Referrer(), frame_tree_node_id, CURRENT_TAB,
354 ui::PAGE_TRANSITION_LINK, true);
355 shell()->web_contents()->OpenURL(params);
357 // Make sure the NavigationEntry ends up with the FrameTreeNode ID.
358 NavigationController* controller = &shell()->web_contents()->GetController();
359 EXPECT_TRUE(controller->GetPendingEntry());
360 EXPECT_EQ(frame_tree_node_id,
361 NavigationEntryImpl::FromNavigationEntry(
362 controller->GetPendingEntry())->frame_tree_node_id());
365 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
366 AppendingFrameInWebUIDoesNotCrash) {
367 const GURL kWebUIUrl("chrome://tracing");
368 const char kJSCodeForAppendingFrame[] =
369 "document.body.appendChild(document.createElement('iframe'));";
371 NavigateToURL(shell(), kWebUIUrl);
373 bool js_executed = content::ExecuteScript(shell()->web_contents(),
374 kJSCodeForAppendingFrame);
375 EXPECT_TRUE(js_executed);
378 // Observer class to track the creation of RenderFrameHost objects. It is used
379 // in subsequent tests.
380 class RenderFrameCreatedObserver : public WebContentsObserver {
381 public:
382 RenderFrameCreatedObserver(Shell* shell)
383 : WebContentsObserver(shell->web_contents()),
384 last_rfh_(NULL) {
387 void RenderFrameCreated(RenderFrameHost* render_frame_host) override {
388 last_rfh_ = render_frame_host;
391 RenderFrameHost* last_rfh() const { return last_rfh_; }
393 private:
394 RenderFrameHost* last_rfh_;
396 DISALLOW_COPY_AND_ASSIGN(RenderFrameCreatedObserver);
399 // Test that creation of new RenderFrameHost objects sends the correct object
400 // to the WebContentObservers. See http://crbug.com/347339.
401 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
402 RenderFrameCreatedCorrectProcessForObservers) {
403 std::string foo_com("foo.com");
404 GURL::Replacements replace_host;
405 net::HostPortPair foo_host_port;
406 GURL cross_site_url;
408 // Setup the server to allow serving separate sites, so we can perform
409 // cross-process navigation.
410 host_resolver()->AddRule("*", "127.0.0.1");
411 ASSERT_TRUE(test_server()->Start());
413 foo_host_port = test_server()->host_port_pair();
414 foo_host_port.set_host(foo_com);
416 GURL initial_url(test_server()->GetURL("/title1.html"));
418 cross_site_url = test_server()->GetURL("/title2.html");
419 replace_host.SetHostStr(foo_com);
420 cross_site_url = cross_site_url.ReplaceComponents(replace_host);
422 // Navigate to the initial URL and capture the RenderFrameHost for later
423 // comparison.
424 NavigateToURL(shell(), initial_url);
425 RenderFrameHost* orig_rfh = shell()->web_contents()->GetMainFrame();
427 // Install the observer and navigate cross-site.
428 RenderFrameCreatedObserver observer(shell());
429 NavigateToURL(shell(), cross_site_url);
431 // The observer should've seen a RenderFrameCreated call for the new frame
432 // and not the old one.
433 EXPECT_NE(observer.last_rfh(), orig_rfh);
434 EXPECT_EQ(observer.last_rfh(), shell()->web_contents()->GetMainFrame());
437 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
438 LoadingStateChangedForSameDocumentNavigation) {
439 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
440 scoped_ptr<LoadingStateChangedDelegate> delegate(
441 new LoadingStateChangedDelegate());
442 shell()->web_contents()->SetDelegate(delegate.get());
444 LoadStopNotificationObserver load_observer(
445 &shell()->web_contents()->GetController());
446 TitleWatcher title_watcher(shell()->web_contents(),
447 base::ASCIIToUTF16("pushState"));
448 NavigateToURL(shell(), embedded_test_server()->GetURL("/push_state.html"));
449 load_observer.Wait();
450 base::string16 title = title_watcher.WaitAndGetTitle();
451 ASSERT_EQ(title, base::ASCIIToUTF16("pushState"));
453 // LoadingStateChanged should be called 4 times: start and stop for the
454 // initial load of push_state.html, and start and stop for the "navigation"
455 // triggered by history.pushState(). However, the start notification for the
456 // history.pushState() navigation should set to_different_document to false.
457 EXPECT_EQ("pushState", shell()->web_contents()->GetURL().ref());
458 EXPECT_EQ(4, delegate->loadingStateChangedCount());
459 EXPECT_EQ(3, delegate->loadingStateToDifferentDocumentCount());
462 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
463 RenderViewCreatedForChildWindow) {
464 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
466 NavigateToURL(shell(),
467 embedded_test_server()->GetURL("/title1.html"));
469 WebContentsAddedObserver new_web_contents_observer;
470 ASSERT_TRUE(ExecuteScript(shell()->web_contents(),
471 "var a = document.createElement('a');"
472 "a.href='./title2.html';"
473 "a.target = '_blank';"
474 "document.body.appendChild(a);"
475 "a.click();"));
476 WebContents* new_web_contents = new_web_contents_observer.GetWebContents();
477 WaitForLoadStop(new_web_contents);
478 EXPECT_TRUE(new_web_contents_observer.RenderViewCreatedCalled());
481 struct LoadProgressDelegateAndObserver : public WebContentsDelegate,
482 public WebContentsObserver {
483 LoadProgressDelegateAndObserver(Shell* shell)
484 : WebContentsObserver(shell->web_contents()),
485 did_start_loading(false),
486 did_stop_loading(false) {
487 web_contents()->SetDelegate(this);
490 // WebContentsDelegate:
491 void LoadProgressChanged(WebContents* source, double progress) override {
492 EXPECT_TRUE(did_start_loading);
493 EXPECT_FALSE(did_stop_loading);
494 progresses.push_back(progress);
497 // WebContentsObserver:
498 void DidStartLoading(RenderViewHost* render_view_host) override {
499 EXPECT_FALSE(did_start_loading);
500 EXPECT_EQ(0U, progresses.size());
501 EXPECT_FALSE(did_stop_loading);
502 did_start_loading = true;
505 void DidStopLoading(RenderViewHost* render_view_host) override {
506 EXPECT_TRUE(did_start_loading);
507 EXPECT_GE(progresses.size(), 1U);
508 EXPECT_FALSE(did_stop_loading);
509 did_stop_loading = true;
512 bool did_start_loading;
513 std::vector<double> progresses;
514 bool did_stop_loading;
517 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, LoadProgress) {
518 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
519 scoped_ptr<LoadProgressDelegateAndObserver> delegate(
520 new LoadProgressDelegateAndObserver(shell()));
522 NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html"));
524 const std::vector<double>& progresses = delegate->progresses;
525 // All updates should be in order ...
526 if (std::adjacent_find(progresses.begin(),
527 progresses.end(),
528 std::greater<double>()) != progresses.end()) {
529 ADD_FAILURE() << "Progress values should be in order: "
530 << ::testing::PrintToString(progresses);
533 // ... and the last one should be 1.0, meaning complete.
534 ASSERT_GE(progresses.size(), 1U)
535 << "There should be at least one progress update";
536 EXPECT_EQ(1.0, *progresses.rbegin());
539 struct FirstVisuallyNonEmptyPaintObserver : public WebContentsObserver {
540 FirstVisuallyNonEmptyPaintObserver(Shell* shell)
541 : WebContentsObserver(shell->web_contents()),
542 did_fist_visually_non_empty_paint_(false) {}
544 void DidFirstVisuallyNonEmptyPaint() override {
545 did_fist_visually_non_empty_paint_ = true;
546 on_did_first_visually_non_empty_paint_.Run();
549 void WaitForDidFirstVisuallyNonEmptyPaint() {
550 if (did_fist_visually_non_empty_paint_)
551 return;
552 base::RunLoop run_loop;
553 on_did_first_visually_non_empty_paint_ = run_loop.QuitClosure();
554 run_loop.Run();
557 base::Closure on_did_first_visually_non_empty_paint_;
558 bool did_fist_visually_non_empty_paint_;
561 // See: http://crbug.com/395664
562 #if defined(OS_ANDROID)
563 #define MAYBE_FirstVisuallyNonEmptyPaint DISABLED_FirstVisuallyNonEmptyPaint
564 #else
565 // http://crbug.com/398471
566 #define MAYBE_FirstVisuallyNonEmptyPaint DISABLED_FirstVisuallyNonEmptyPaint
567 #endif
568 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
569 MAYBE_FirstVisuallyNonEmptyPaint) {
570 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
571 scoped_ptr<FirstVisuallyNonEmptyPaintObserver> observer(
572 new FirstVisuallyNonEmptyPaintObserver(shell()));
574 NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html"));
576 observer->WaitForDidFirstVisuallyNonEmptyPaint();
577 ASSERT_TRUE(observer->did_fist_visually_non_empty_paint_);
580 } // namespace content