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/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"
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(TOOLKIT_GTK) || defined(OS_MACOSX)
38 // If |set_start_page| is true, start with blank page to make sure resize
41 NavigateToURL(shell
, GURL("about://blank"));
43 shell
->web_contents()->GetView()->SizeContents(size
);
44 #endif // defined(TOOLKIT_GTK) || defined(OS_MACOSX)
47 class WebContentsImplBrowserTest
: public ContentBrowserTest
{
49 WebContentsImplBrowserTest() {}
52 DISALLOW_COPY_AND_ASSIGN(WebContentsImplBrowserTest
);
55 // Keeps track of data from LoadNotificationDetails so we can later verify that
56 // they are correct, after the LoadNotificationDetails object is deleted.
57 class LoadStopNotificationObserver
: public WindowedNotificationObserver
{
59 LoadStopNotificationObserver(NavigationController
* controller
)
60 : WindowedNotificationObserver(NOTIFICATION_LOAD_STOP
,
61 Source
<NavigationController
>(controller
)),
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
;
74 WindowedNotificationObserver::Observe(type
, source
, details
);
79 NavigationController
* controller_
;
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.
85 class NavigateOnCommitObserver
: public WebContentsObserver
{
87 NavigateOnCommitObserver(Shell
* shell
, GURL url
)
88 : WebContentsObserver(shell
->web_contents()),
94 // WebContentsObserver:
95 virtual void NavigationEntryCommitted(
96 const LoadCommittedDetails
& load_details
) OVERRIDE
{
99 shell_
->LoadURL(url_
);
108 class RenderViewSizeDelegate
: public WebContentsDelegate
{
110 void set_size_insets(const gfx::Size
& size_insets
) {
111 size_insets_
= size_insets
;
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());
123 gfx::Size size_insets_
;
126 class RenderViewSizeObserver
: public WebContentsObserver
{
128 RenderViewSizeObserver(Shell
* shell
, const gfx::Size
& wcv_new_size
)
129 : WebContentsObserver(shell
->web_contents()),
131 wcv_new_size_(wcv_new_size
) {
134 // WebContentsObserver:
135 virtual void RenderViewCreated(RenderViewHost
* rvh
) OVERRIDE
{
136 rwhv_create_size_
= rvh
->GetView()->GetViewBounds().size();
139 virtual void DidStartNavigationToPendingEntry(
141 NavigationController::ReloadType reload_type
) OVERRIDE
{
142 ResizeWebContentsView(shell_
, wcv_new_size_
, false);
145 gfx::Size
rwhv_create_size() const { return rwhv_create_size_
; }
148 Shell
* shell_
; // Weak ptr.
149 gfx::Size wcv_new_size_
;
150 gfx::Size rwhv_create_size_
;
153 class LoadingStateChangedDelegate
: public WebContentsDelegate
{
155 LoadingStateChangedDelegate()
156 : loadingStateChangedCount_(0)
157 , loadingStateToDifferentDocumentCount_(0) {
160 // WebContentsDelgate:
161 virtual void LoadingStateChanged(WebContents
* contents
,
162 bool to_different_document
) OVERRIDE
{
163 loadingStateChangedCount_
++;
164 if (to_different_document
)
165 loadingStateToDifferentDocumentCount_
++;
168 int loadingStateChangedCount() const { return loadingStateChangedCount_
; }
169 int loadingStateToDifferentDocumentCount() const {
170 return loadingStateToDifferentDocumentCount_
;
174 int loadingStateChangedCount_
;
175 int loadingStateToDifferentDocumentCount_
;
178 // See: http://crbug.com/298193
180 #define MAYBE_DidStopLoadingDetails DISABLED_DidStopLoadingDetails
182 #define MAYBE_DidStopLoadingDetails DidStopLoadingDetails
185 // Test that DidStopLoading includes the correct URL in the details.
186 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest
,
187 MAYBE_DidStopLoadingDetails
) {
188 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
190 LoadStopNotificationObserver
load_observer(
191 &shell()->web_contents()->GetController());
192 NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html"));
193 load_observer
.Wait();
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_
);
201 // See: http://crbug.com/298193
203 #define MAYBE_DidStopLoadingDetailsWithPending \
204 DISABLED_DidStopLoadingDetailsWithPending
206 #define MAYBE_DidStopLoadingDetailsWithPending DidStopLoadingDetailsWithPending
209 // Test that DidStopLoading includes the correct URL in the details when a
210 // pending entry is present.
211 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest
,
212 MAYBE_DidStopLoadingDetailsWithPending
) {
213 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
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
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();
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_
);
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.
236 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest
,
237 ClearNonVisiblePendingOnFail
) {
238 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
240 NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html"));
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());
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());
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
267 #define MAYBE_GetSizeForNewRenderView GetSizeForNewRenderView
269 // Test that RenderViewHost is created and updated at the size specified by
270 // WebContentsDelegate::GetSizeForNewRenderView().
271 IN_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());
281 scoped_ptr
<RenderViewSizeDelegate
> delegate(new RenderViewSizeDelegate());
282 shell()->web_contents()->SetDelegate(delegate
.get());
283 ASSERT_TRUE(shell()->web_contents()->GetDelegate() == delegate
.get());
285 // When no size is set, RenderWidgetHostView adopts the size of
287 NavigateToURL(shell(), embedded_test_server()->GetURL("/title2.html"));
288 EXPECT_EQ(shell()->web_contents()->GetView()->GetContainerSize(),
289 shell()->web_contents()->GetRenderWidgetHostView()->GetViewBounds().
292 // When a size is set, RenderWidgetHostView and WebContentsView honor this
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());
301 shell()->web_contents()->GetRenderWidgetHostView()->GetViewBounds().
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
306 gfx::Size
exp_wcv_size(300, 300);
307 #if !defined(OS_MACOSX)
308 exp_wcv_size
.Enlarge(size_insets
.width(), size_insets
.height());
311 EXPECT_EQ(exp_wcv_size
,
312 shell()->web_contents()->GetView()->GetContainerSize());
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());
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());
334 gfx::Size actual_size
= shell()->web_contents()->GetRenderWidgetHostView()->
335 GetViewBounds().size();
337 EXPECT_EQ(new_size
, actual_size
);
338 EXPECT_EQ(new_size
, shell()->web_contents()->GetView()->GetContainerSize());
341 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest
, OpenURLSubframe
) {
343 // Navigate with FrameTreeNode ID 4.
344 const GURL
url("http://foo");
345 OpenURLParams
params(url
, Referrer(), 4, CURRENT_TAB
, PAGE_TRANSITION_LINK
,
347 shell()->web_contents()->OpenURL(params
);
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());
356 // Observer class to track the creation of RenderFrameHost objects. It is used
357 // in subsequent tests.
358 class RenderFrameCreatedObserver
: public WebContentsObserver
{
360 RenderFrameCreatedObserver(Shell
* shell
)
361 : WebContentsObserver(shell
->web_contents()),
365 virtual void RenderFrameCreated(RenderFrameHost
* render_frame_host
) OVERRIDE
{
366 LOG(ERROR
) << "RFCreated: " << render_frame_host
;
367 last_rfh_
= render_frame_host
;
370 RenderFrameHost
* last_rfh() const { return last_rfh_
; }
373 RenderFrameHost
* last_rfh_
;
375 DISALLOW_COPY_AND_ASSIGN(RenderFrameCreatedObserver
);
378 // Test that creation of new RenderFrameHost objects sends the correct object
379 // to the WebContentObservers. See http://crbug.com/347339.
380 IN_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
;
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());
392 foo_host_port
= test_server()->host_port_pair();
393 foo_host_port
.set_host(foo_com
);
395 GURL
initial_url(test_server()->GetURL("/title1.html"));
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
);
401 // Navigate to the initial URL and capture the RenderFrameHost for later
403 NavigateToURL(shell(), initial_url
);
404 RenderFrameHost
* orig_rfh
= shell()->web_contents()->GetMainFrame();
406 // Install the observer and navigate cross-site.
407 RenderFrameCreatedObserver
observer(shell());
408 NavigateToURL(shell(), cross_site_url
);
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());
416 IN_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());
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"));
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());
441 } // namespace content