1 // Copyright (c) 2012 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/command_line.h"
6 #include "base/strings/stringprintf.h"
7 #include "content/browser/frame_host/cross_process_frame_connector.h"
8 #include "content/browser/frame_host/frame_tree.h"
9 #include "content/browser/frame_host/render_frame_proxy_host.h"
10 #include "content/browser/frame_host/render_widget_host_view_child_frame.h"
11 #include "content/browser/renderer_host/render_view_host_impl.h"
12 #include "content/browser/web_contents/web_contents_impl.h"
13 #include "content/public/browser/notification_observer.h"
14 #include "content/public/browser/notification_service.h"
15 #include "content/public/browser/notification_types.h"
16 #include "content/public/browser/web_contents_observer.h"
17 #include "content/public/common/content_switches.h"
18 #include "content/public/test/browser_test_utils.h"
19 #include "content/public/test/content_browser_test.h"
20 #include "content/public/test/content_browser_test_utils.h"
21 #include "content/public/test/test_utils.h"
22 #include "content/shell/browser/shell.h"
23 #include "content/test/content_browser_test_utils_internal.h"
24 #include "net/dns/mock_host_resolver.h"
29 class SitePerProcessWebContentsObserver
: public WebContentsObserver
{
31 explicit SitePerProcessWebContentsObserver(WebContents
* web_contents
)
32 : WebContentsObserver(web_contents
),
33 navigation_succeeded_(false) {}
34 virtual ~SitePerProcessWebContentsObserver() {}
36 virtual void DidStartProvisionalLoadForFrame(
37 RenderFrameHost
* render_frame_host
,
38 const GURL
& validated_url
,
40 bool is_iframe_srcdoc
) OVERRIDE
{
41 navigation_succeeded_
= false;
44 virtual void DidFailProvisionalLoad(
45 RenderFrameHost
* render_frame_host
,
46 const GURL
& validated_url
,
48 const base::string16
& error_description
) OVERRIDE
{
49 navigation_url_
= validated_url
;
50 navigation_succeeded_
= false;
53 virtual void DidCommitProvisionalLoadForFrame(
54 RenderFrameHost
* render_frame_host
,
56 PageTransition transition_type
) OVERRIDE
{
57 navigation_url_
= url
;
58 navigation_succeeded_
= true;
61 const GURL
& navigation_url() const {
62 return navigation_url_
;
65 int navigation_succeeded() const { return navigation_succeeded_
; }
69 bool navigation_succeeded_
;
71 DISALLOW_COPY_AND_ASSIGN(SitePerProcessWebContentsObserver
);
74 class RedirectNotificationObserver
: public NotificationObserver
{
76 // Register to listen for notifications of the given type from either a
77 // specific source, or from all sources if |source| is
78 // NotificationService::AllSources().
79 RedirectNotificationObserver(int notification_type
,
80 const NotificationSource
& source
);
81 virtual ~RedirectNotificationObserver();
83 // Wait until the specified notification occurs. If the notification was
84 // emitted between the construction of this object and this call then it
85 // returns immediately.
88 // Returns NotificationService::AllSources() if we haven't observed a
90 const NotificationSource
& source() const {
94 const NotificationDetails
& details() const {
98 // NotificationObserver:
99 virtual void Observe(int type
,
100 const NotificationSource
& source
,
101 const NotificationDetails
& details
) OVERRIDE
;
107 NotificationRegistrar registrar_
;
109 NotificationSource source_
;
110 NotificationDetails details_
;
111 scoped_refptr
<MessageLoopRunner
> message_loop_runner_
;
113 DISALLOW_COPY_AND_ASSIGN(RedirectNotificationObserver
);
116 RedirectNotificationObserver::RedirectNotificationObserver(
117 int notification_type
,
118 const NotificationSource
& source
)
121 source_(NotificationService::AllSources()) {
122 registrar_
.Add(this, notification_type
, source
);
125 RedirectNotificationObserver::~RedirectNotificationObserver() {}
127 void RedirectNotificationObserver::Wait() {
128 if (seen_
&& seen_twice_
)
132 message_loop_runner_
= new MessageLoopRunner
;
133 message_loop_runner_
->Run();
137 void RedirectNotificationObserver::Observe(
139 const NotificationSource
& source
,
140 const NotificationDetails
& details
) {
148 message_loop_runner_
->Quit();
152 class SitePerProcessBrowserTest
: public ContentBrowserTest
{
154 SitePerProcessBrowserTest() {}
157 // Start at a data URL so each extra navigation creates a navigation entry.
158 // (The first navigation will silently be classified as AUTO_SUBFRAME.)
159 // TODO(creis): This won't be necessary when we can wait for LOAD_STOP.
160 void StartFrameAtDataURL() {
161 std::string data_url_script
=
162 "var iframes = document.getElementById('test');iframes.src="
163 "'data:text/html,dataurl';";
164 ASSERT_TRUE(ExecuteScript(shell()->web_contents(), data_url_script
));
167 bool NavigateIframeToURL(Shell
* window
,
169 std::string iframe_id
) {
170 // TODO(creis): This should wait for LOAD_STOP, but cross-site subframe
171 // navigations generate extra DidStartLoading and DidStopLoading messages.
172 // Until we replace swappedout:// with frame proxies, we need to listen for
173 // something else. For now, we trigger NEW_SUBFRAME navigations and listen
175 std::string script
= base::StringPrintf(
177 "var iframes = document.getElementById('%s');iframes.src='%s';"
179 iframe_id
.c_str(), url
.spec().c_str());
180 WindowedNotificationObserver
load_observer(
181 NOTIFICATION_NAV_ENTRY_COMMITTED
,
182 Source
<NavigationController
>(
183 &window
->web_contents()->GetController()));
184 bool result
= ExecuteScript(window
->web_contents(), script
);
185 load_observer
.Wait();
189 virtual void SetUpCommandLine(CommandLine
* command_line
) OVERRIDE
{
190 command_line
->AppendSwitch(switches::kSitePerProcess
);
194 // Ensure that we can complete a cross-process subframe navigation.
195 // Crashes ChromeOS bot, but the bug is probably present on other platforms
196 // also. http://crbug.com/399775
197 IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest
, DISABLED_CrossSiteIframe
) {
198 host_resolver()->AddRule("*", "127.0.0.1");
199 ASSERT_TRUE(test_server()->Start());
200 GURL
main_url(test_server()->GetURL("files/site_per_process_main.html"));
201 NavigateToURL(shell(), main_url
);
203 // It is safe to obtain the root frame tree node here, as it doesn't change.
204 FrameTreeNode
* root
=
205 static_cast<WebContentsImpl
*>(shell()->web_contents())->
206 GetFrameTree()->root();
208 SitePerProcessWebContentsObserver
observer(shell()->web_contents());
210 // Load same-site page into iframe.
211 FrameTreeNode
* child
= root
->child_at(0);
212 GURL
http_url(test_server()->GetURL("files/title1.html"));
213 NavigateFrameToURL(child
, http_url
);
214 EXPECT_EQ(http_url
, observer
.navigation_url());
215 EXPECT_TRUE(observer
.navigation_succeeded());
217 // There should be only one RenderWidgetHost when there are no
218 // cross-process iframes.
219 std::set
<RenderWidgetHostView
*> views_set
=
220 static_cast<WebContentsImpl
*>(shell()->web_contents())
221 ->GetRenderWidgetHostViewsInTree();
222 EXPECT_EQ(1U, views_set
.size());
224 RenderFrameProxyHost
* proxy_to_parent
=
225 child
->render_manager()->GetRenderFrameProxyHost(
226 shell()->web_contents()->GetSiteInstance());
227 EXPECT_FALSE(proxy_to_parent
);
229 // These must stay in scope with replace_host.
230 GURL::Replacements replace_host
;
231 std::string
foo_com("foo.com");
233 // Load cross-site page into iframe.
234 GURL
cross_site_url(test_server()->GetURL("files/title2.html"));
235 replace_host
.SetHostStr(foo_com
);
236 cross_site_url
= cross_site_url
.ReplaceComponents(replace_host
);
237 NavigateFrameToURL(root
->child_at(0), cross_site_url
);
238 EXPECT_EQ(cross_site_url
, observer
.navigation_url());
239 EXPECT_TRUE(observer
.navigation_succeeded());
241 // Ensure that we have created a new process for the subframe.
242 ASSERT_EQ(1U, root
->child_count());
243 SiteInstance
* site_instance
= child
->current_frame_host()->GetSiteInstance();
244 RenderViewHost
* rvh
= child
->current_frame_host()->render_view_host();
245 RenderProcessHost
* rph
= child
->current_frame_host()->GetProcess();
246 EXPECT_NE(shell()->web_contents()->GetRenderViewHost(), rvh
);
247 EXPECT_NE(shell()->web_contents()->GetSiteInstance(), site_instance
);
248 EXPECT_NE(shell()->web_contents()->GetRenderProcessHost(), rph
);
250 // There should be now two RenderWidgetHosts, one for each process
251 // rendering a frame.
252 std::set
<RenderWidgetHostView
*> views_set
=
253 static_cast<WebContentsImpl
*>(shell()->web_contents())
254 ->GetRenderWidgetHostViewsInTree();
255 EXPECT_EQ(2U, views_set
.size());
257 proxy_to_parent
= child
->render_manager()->GetProxyToParent();
258 EXPECT_TRUE(proxy_to_parent
);
259 EXPECT_TRUE(proxy_to_parent
->cross_process_frame_connector());
262 proxy_to_parent
->cross_process_frame_connector()->get_view_for_testing());
264 // Load another cross-site page into the same iframe.
265 cross_site_url
= test_server()->GetURL("files/title3.html");
266 std::string
bar_com("bar.com");
267 replace_host
.SetHostStr(bar_com
);
268 cross_site_url
= cross_site_url
.ReplaceComponents(replace_host
);
269 NavigateFrameToURL(root
->child_at(0), cross_site_url
);
270 EXPECT_EQ(cross_site_url
, observer
.navigation_url());
271 EXPECT_TRUE(observer
.navigation_succeeded());
273 // Check again that a new process is created and is different from the
274 // top level one and the previous one.
275 ASSERT_EQ(1U, root
->child_count());
276 child
= root
->child_at(0);
277 EXPECT_NE(shell()->web_contents()->GetRenderViewHost(),
278 child
->current_frame_host()->render_view_host());
279 EXPECT_NE(rvh
, child
->current_frame_host()->render_view_host());
280 EXPECT_NE(shell()->web_contents()->GetSiteInstance(),
281 child
->current_frame_host()->GetSiteInstance());
282 EXPECT_NE(site_instance
,
283 child
->current_frame_host()->GetSiteInstance());
284 EXPECT_NE(shell()->web_contents()->GetRenderProcessHost(),
285 child
->current_frame_host()->GetProcess());
286 EXPECT_NE(rph
, child
->current_frame_host()->GetProcess());
288 std::set
<RenderWidgetHostView
*> views_set
=
289 static_cast<WebContentsImpl
*>(shell()->web_contents())
290 ->GetRenderWidgetHostViewsInTree();
291 EXPECT_EQ(2U, views_set
.size());
293 EXPECT_EQ(proxy_to_parent
, child
->render_manager()->GetProxyToParent());
294 EXPECT_TRUE(proxy_to_parent
->cross_process_frame_connector());
296 child
->current_frame_host()->render_view_host()->GetView(),
297 proxy_to_parent
->cross_process_frame_connector()->get_view_for_testing());
300 // Crash a subframe and ensures its children are cleared from the FrameTree.
301 // See http://crbug.com/338508.
302 // TODO(creis): Enable this on Android when we can kill the process there.
303 #if defined(OS_ANDROID)
304 #define MAYBE_CrashSubframe DISABLED_CrashSubframe
306 #define MAYBE_CrashSubframe CrashSubframe
308 IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest
, MAYBE_CrashSubframe
) {
309 host_resolver()->AddRule("*", "127.0.0.1");
310 ASSERT_TRUE(test_server()->Start());
311 GURL
main_url(test_server()->GetURL("files/site_per_process_main.html"));
312 NavigateToURL(shell(), main_url
);
314 StartFrameAtDataURL();
316 // These must stay in scope with replace_host.
317 GURL::Replacements replace_host
;
318 std::string
foo_com("foo.com");
320 // Load cross-site page into iframe.
321 GURL
cross_site_url(test_server()->GetURL("files/title2.html"));
322 replace_host
.SetHostStr(foo_com
);
323 cross_site_url
= cross_site_url
.ReplaceComponents(replace_host
);
324 EXPECT_TRUE(NavigateIframeToURL(shell(), cross_site_url
, "test"));
326 // Check the subframe process.
327 FrameTreeNode
* root
=
328 static_cast<WebContentsImpl
*>(shell()->web_contents())->
329 GetFrameTree()->root();
330 ASSERT_EQ(1U, root
->child_count());
331 FrameTreeNode
* child
= root
->child_at(0);
332 EXPECT_EQ(main_url
, root
->current_url());
333 EXPECT_EQ(cross_site_url
, child
->current_url());
335 // Crash the subframe process.
336 RenderProcessHost
* root_process
= root
->current_frame_host()->GetProcess();
337 RenderProcessHost
* child_process
= child
->current_frame_host()->GetProcess();
339 RenderProcessHostWatcher
crash_observer(
341 RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT
);
342 base::KillProcess(child_process
->GetHandle(), 0, false);
343 crash_observer
.Wait();
346 // Ensure that the child frame still exists but has been cleared.
347 EXPECT_EQ(1U, root
->child_count());
348 EXPECT_EQ(main_url
, root
->current_url());
349 EXPECT_EQ(GURL(), child
->current_url());
351 // Now crash the top-level page to clear the child frame.
353 RenderProcessHostWatcher
crash_observer(
355 RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT
);
356 base::KillProcess(root_process
->GetHandle(), 0, false);
357 crash_observer
.Wait();
359 EXPECT_EQ(0U, root
->child_count());
360 EXPECT_EQ(GURL(), root
->current_url());
363 // TODO(nasko): Disable this test until out-of-process iframes is ready and the
364 // security checks are back in place.
365 // TODO(creis): Replace SpawnedTestServer with host_resolver to get test to run
366 // on Android (http://crbug.com/187570).
367 IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest
,
368 DISABLED_CrossSiteIframeRedirectOnce
) {
369 ASSERT_TRUE(test_server()->Start());
370 net::SpawnedTestServer
https_server(
371 net::SpawnedTestServer::TYPE_HTTPS
,
372 net::SpawnedTestServer::kLocalhost
,
373 base::FilePath(FILE_PATH_LITERAL("content/test/data")));
374 ASSERT_TRUE(https_server
.Start());
376 GURL
main_url(test_server()->GetURL("files/site_per_process_main.html"));
377 GURL
http_url(test_server()->GetURL("files/title1.html"));
378 GURL
https_url(https_server
.GetURL("files/title1.html"));
380 NavigateToURL(shell(), main_url
);
382 SitePerProcessWebContentsObserver
observer(shell()->web_contents());
384 // Load cross-site client-redirect page into Iframe.
385 // Should be blocked.
386 GURL
client_redirect_https_url(https_server
.GetURL(
387 "client-redirect?files/title1.html"));
388 EXPECT_TRUE(NavigateIframeToURL(shell(),
389 client_redirect_https_url
, "test"));
390 // DidFailProvisionalLoad when navigating to client_redirect_https_url.
391 EXPECT_EQ(observer
.navigation_url(), client_redirect_https_url
);
392 EXPECT_FALSE(observer
.navigation_succeeded());
396 // Load cross-site server-redirect page into Iframe,
397 // which redirects to same-site page.
398 GURL
server_redirect_http_url(https_server
.GetURL(
399 "server-redirect?" + http_url
.spec()));
400 EXPECT_TRUE(NavigateIframeToURL(shell(),
401 server_redirect_http_url
, "test"));
402 EXPECT_EQ(observer
.navigation_url(), http_url
);
403 EXPECT_TRUE(observer
.navigation_succeeded());
407 // Load cross-site server-redirect page into Iframe,
408 // which redirects to cross-site page.
409 GURL
server_redirect_http_url(https_server
.GetURL(
410 "server-redirect?files/title1.html"));
411 EXPECT_TRUE(NavigateIframeToURL(shell(),
412 server_redirect_http_url
, "test"));
413 // DidFailProvisionalLoad when navigating to https_url.
414 EXPECT_EQ(observer
.navigation_url(), https_url
);
415 EXPECT_FALSE(observer
.navigation_succeeded());
419 // Load same-site server-redirect page into Iframe,
420 // which redirects to cross-site page.
421 GURL
server_redirect_http_url(test_server()->GetURL(
422 "server-redirect?" + https_url
.spec()));
423 EXPECT_TRUE(NavigateIframeToURL(shell(),
424 server_redirect_http_url
, "test"));
426 EXPECT_EQ(observer
.navigation_url(), https_url
);
427 EXPECT_FALSE(observer
.navigation_succeeded());
431 // Load same-site client-redirect page into Iframe,
432 // which redirects to cross-site page.
433 GURL
client_redirect_http_url(test_server()->GetURL(
434 "client-redirect?" + https_url
.spec()));
436 RedirectNotificationObserver
load_observer2(
437 NOTIFICATION_LOAD_STOP
,
438 Source
<NavigationController
>(
439 &shell()->web_contents()->GetController()));
441 EXPECT_TRUE(NavigateIframeToURL(shell(),
442 client_redirect_http_url
, "test"));
444 // Same-site Client-Redirect Page should be loaded successfully.
445 EXPECT_EQ(observer
.navigation_url(), client_redirect_http_url
);
446 EXPECT_TRUE(observer
.navigation_succeeded());
448 // Redirecting to Cross-site Page should be blocked.
449 load_observer2
.Wait();
450 EXPECT_EQ(observer
.navigation_url(), https_url
);
451 EXPECT_FALSE(observer
.navigation_succeeded());
455 // Load same-site server-redirect page into Iframe,
456 // which redirects to same-site page.
457 GURL
server_redirect_http_url(test_server()->GetURL(
458 "server-redirect?files/title1.html"));
459 EXPECT_TRUE(NavigateIframeToURL(shell(),
460 server_redirect_http_url
, "test"));
461 EXPECT_EQ(observer
.navigation_url(), http_url
);
462 EXPECT_TRUE(observer
.navigation_succeeded());
466 // Load same-site client-redirect page into Iframe,
467 // which redirects to same-site page.
468 GURL
client_redirect_http_url(test_server()->GetURL(
469 "client-redirect?" + http_url
.spec()));
470 RedirectNotificationObserver
load_observer2(
471 NOTIFICATION_LOAD_STOP
,
472 Source
<NavigationController
>(
473 &shell()->web_contents()->GetController()));
475 EXPECT_TRUE(NavigateIframeToURL(shell(),
476 client_redirect_http_url
, "test"));
478 // Same-site Client-Redirect Page should be loaded successfully.
479 EXPECT_EQ(observer
.navigation_url(), client_redirect_http_url
);
480 EXPECT_TRUE(observer
.navigation_succeeded());
482 // Redirecting to Same-site Page should be loaded successfully.
483 load_observer2
.Wait();
484 EXPECT_EQ(observer
.navigation_url(), http_url
);
485 EXPECT_TRUE(observer
.navigation_succeeded());
489 // TODO(nasko): Disable this test until out-of-process iframes is ready and the
490 // security checks are back in place.
491 // TODO(creis): Replace SpawnedTestServer with host_resolver to get test to run
492 // on Android (http://crbug.com/187570).
493 IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest
,
494 DISABLED_CrossSiteIframeRedirectTwice
) {
495 ASSERT_TRUE(test_server()->Start());
496 net::SpawnedTestServer
https_server(
497 net::SpawnedTestServer::TYPE_HTTPS
,
498 net::SpawnedTestServer::kLocalhost
,
499 base::FilePath(FILE_PATH_LITERAL("content/test/data")));
500 ASSERT_TRUE(https_server
.Start());
502 GURL
main_url(test_server()->GetURL("files/site_per_process_main.html"));
503 GURL
http_url(test_server()->GetURL("files/title1.html"));
504 GURL
https_url(https_server
.GetURL("files/title1.html"));
506 NavigateToURL(shell(), main_url
);
508 SitePerProcessWebContentsObserver
observer(shell()->web_contents());
510 // Load client-redirect page pointing to a cross-site client-redirect page,
511 // which eventually redirects back to same-site page.
512 GURL
client_redirect_https_url(https_server
.GetURL(
513 "client-redirect?" + http_url
.spec()));
514 GURL
client_redirect_http_url(test_server()->GetURL(
515 "client-redirect?" + client_redirect_https_url
.spec()));
517 // We should wait until second client redirect get cancelled.
518 RedirectNotificationObserver
load_observer2(
519 NOTIFICATION_LOAD_STOP
,
520 Source
<NavigationController
>(
521 &shell()->web_contents()->GetController()));
523 EXPECT_TRUE(NavigateIframeToURL(shell(), client_redirect_http_url
, "test"));
525 // DidFailProvisionalLoad when navigating to client_redirect_https_url.
526 load_observer2
.Wait();
527 EXPECT_EQ(observer
.navigation_url(), client_redirect_https_url
);
528 EXPECT_FALSE(observer
.navigation_succeeded());
532 // Load server-redirect page pointing to a cross-site server-redirect page,
533 // which eventually redirect back to same-site page.
534 GURL
server_redirect_https_url(https_server
.GetURL(
535 "server-redirect?" + http_url
.spec()));
536 GURL
server_redirect_http_url(test_server()->GetURL(
537 "server-redirect?" + server_redirect_https_url
.spec()));
538 EXPECT_TRUE(NavigateIframeToURL(shell(),
539 server_redirect_http_url
, "test"));
540 EXPECT_EQ(observer
.navigation_url(), http_url
);
541 EXPECT_TRUE(observer
.navigation_succeeded());
545 // Load server-redirect page pointing to a cross-site server-redirect page,
546 // which eventually redirects back to cross-site page.
547 GURL
server_redirect_https_url(https_server
.GetURL(
548 "server-redirect?" + https_url
.spec()));
549 GURL
server_redirect_http_url(test_server()->GetURL(
550 "server-redirect?" + server_redirect_https_url
.spec()));
551 EXPECT_TRUE(NavigateIframeToURL(shell(), server_redirect_http_url
, "test"));
553 // DidFailProvisionalLoad when navigating to https_url.
554 EXPECT_EQ(observer
.navigation_url(), https_url
);
555 EXPECT_FALSE(observer
.navigation_succeeded());
559 // Load server-redirect page pointing to a cross-site client-redirect page,
560 // which eventually redirects back to same-site page.
561 GURL
client_redirect_http_url(https_server
.GetURL(
562 "client-redirect?" + http_url
.spec()));
563 GURL
server_redirect_http_url(test_server()->GetURL(
564 "server-redirect?" + client_redirect_http_url
.spec()));
565 EXPECT_TRUE(NavigateIframeToURL(shell(), server_redirect_http_url
, "test"));
567 // DidFailProvisionalLoad when navigating to client_redirect_http_url.
568 EXPECT_EQ(observer
.navigation_url(), client_redirect_http_url
);
569 EXPECT_FALSE(observer
.navigation_succeeded());
573 } // namespace content