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 "content/browser/site_per_process_browsertest.h"
7 #include "base/command_line.h"
8 #include "base/strings/stringprintf.h"
9 #include "content/browser/frame_host/cross_process_frame_connector.h"
10 #include "content/browser/frame_host/frame_tree.h"
11 #include "content/browser/frame_host/render_frame_proxy_host.h"
12 #include "content/browser/frame_host/render_widget_host_view_child_frame.h"
13 #include "content/browser/renderer_host/render_view_host_impl.h"
14 #include "content/browser/web_contents/web_contents_impl.h"
15 #include "content/public/browser/notification_observer.h"
16 #include "content/public/browser/notification_service.h"
17 #include "content/public/browser/notification_types.h"
18 #include "content/public/browser/web_contents_observer.h"
19 #include "content/public/common/content_switches.h"
20 #include "content/public/test/browser_test_utils.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 "content/test/content_browser_test_utils_internal.h"
25 #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 ui::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();
153 // SitePerProcessBrowserTest
156 SitePerProcessBrowserTest::SitePerProcessBrowserTest() {
159 void SitePerProcessBrowserTest::StartFrameAtDataURL() {
160 std::string data_url_script
=
161 "var iframes = document.getElementById('test');iframes.src="
162 "'data:text/html,dataurl';";
163 ASSERT_TRUE(ExecuteScript(shell()->web_contents(), data_url_script
));
166 bool SitePerProcessBrowserTest::NavigateIframeToURL(Shell
* window
,
168 std::string iframe_id
) {
169 // TODO(creis): This should wait for LOAD_STOP, but cross-site subframe
170 // navigations generate extra DidStartLoading and DidStopLoading messages.
171 // Until we replace swappedout:// with frame proxies, we need to listen for
172 // something else. For now, we trigger NEW_SUBFRAME navigations and listen
174 std::string script
= base::StringPrintf(
176 "var iframes = document.getElementById('%s');iframes.src='%s';"
178 iframe_id
.c_str(), url
.spec().c_str());
179 WindowedNotificationObserver
load_observer(
180 NOTIFICATION_NAV_ENTRY_COMMITTED
,
181 Source
<NavigationController
>(
182 &window
->web_contents()->GetController()));
183 bool result
= ExecuteScript(window
->web_contents(), script
);
184 load_observer
.Wait();
188 void SitePerProcessBrowserTest::SetUpCommandLine(CommandLine
* command_line
) {
189 command_line
->AppendSwitch(switches::kSitePerProcess
);
192 // It fails on ChromeOS and Android, so disabled while investigating.
193 // http://crbug.com/399775
194 #if defined(OS_ANDROID) || defined(OS_CHROMEOS)
195 #define MAYBE_CrossSiteIframe DISABLED_CrossSiteIframe
197 #define MAYBE_CrossSiteIframe CrossSiteIframe
199 IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest
, MAYBE_CrossSiteIframe
) {
200 host_resolver()->AddRule("*", "127.0.0.1");
201 ASSERT_TRUE(test_server()->Start());
202 GURL
main_url(test_server()->GetURL("files/site_per_process_main.html"));
203 NavigateToURL(shell(), main_url
);
205 // It is safe to obtain the root frame tree node here, as it doesn't change.
206 FrameTreeNode
* root
=
207 static_cast<WebContentsImpl
*>(shell()->web_contents())->
208 GetFrameTree()->root();
210 SitePerProcessWebContentsObserver
observer(shell()->web_contents());
212 // Load same-site page into iframe.
213 FrameTreeNode
* child
= root
->child_at(0);
214 GURL
http_url(test_server()->GetURL("files/title1.html"));
215 NavigateFrameToURL(child
, http_url
);
216 EXPECT_EQ(http_url
, observer
.navigation_url());
217 EXPECT_TRUE(observer
.navigation_succeeded());
219 // There should be only one RenderWidgetHost when there are no
220 // cross-process iframes.
221 std::set
<RenderWidgetHostView
*> views_set
=
222 static_cast<WebContentsImpl
*>(shell()->web_contents())
223 ->GetRenderWidgetHostViewsInTree();
224 EXPECT_EQ(1U, views_set
.size());
226 RenderFrameProxyHost
* proxy_to_parent
=
227 child
->render_manager()->GetRenderFrameProxyHost(
228 shell()->web_contents()->GetSiteInstance());
229 EXPECT_FALSE(proxy_to_parent
);
231 // These must stay in scope with replace_host.
232 GURL::Replacements replace_host
;
233 std::string
foo_com("foo.com");
235 // Load cross-site page into iframe.
236 GURL
cross_site_url(test_server()->GetURL("files/title2.html"));
237 replace_host
.SetHostStr(foo_com
);
238 cross_site_url
= cross_site_url
.ReplaceComponents(replace_host
);
239 NavigateFrameToURL(root
->child_at(0), cross_site_url
);
240 EXPECT_EQ(cross_site_url
, observer
.navigation_url());
241 EXPECT_TRUE(observer
.navigation_succeeded());
243 // Ensure that we have created a new process for the subframe.
244 ASSERT_EQ(1U, root
->child_count());
245 SiteInstance
* site_instance
= child
->current_frame_host()->GetSiteInstance();
246 RenderViewHost
* rvh
= child
->current_frame_host()->render_view_host();
247 RenderProcessHost
* rph
= child
->current_frame_host()->GetProcess();
248 EXPECT_NE(shell()->web_contents()->GetRenderViewHost(), rvh
);
249 EXPECT_NE(shell()->web_contents()->GetSiteInstance(), site_instance
);
250 EXPECT_NE(shell()->web_contents()->GetRenderProcessHost(), rph
);
252 // There should be now two RenderWidgetHosts, one for each process
253 // rendering a frame.
254 std::set
<RenderWidgetHostView
*> views_set
=
255 static_cast<WebContentsImpl
*>(shell()->web_contents())
256 ->GetRenderWidgetHostViewsInTree();
257 EXPECT_EQ(2U, views_set
.size());
259 proxy_to_parent
= child
->render_manager()->GetProxyToParent();
260 EXPECT_TRUE(proxy_to_parent
);
261 EXPECT_TRUE(proxy_to_parent
->cross_process_frame_connector());
264 proxy_to_parent
->cross_process_frame_connector()->get_view_for_testing());
266 // Load another cross-site page into the same iframe.
267 cross_site_url
= test_server()->GetURL("files/title3.html");
268 std::string
bar_com("bar.com");
269 replace_host
.SetHostStr(bar_com
);
270 cross_site_url
= cross_site_url
.ReplaceComponents(replace_host
);
271 NavigateFrameToURL(root
->child_at(0), cross_site_url
);
272 EXPECT_EQ(cross_site_url
, observer
.navigation_url());
273 EXPECT_TRUE(observer
.navigation_succeeded());
275 // Check again that a new process is created and is different from the
276 // top level one and the previous one.
277 ASSERT_EQ(1U, root
->child_count());
278 child
= root
->child_at(0);
279 EXPECT_NE(shell()->web_contents()->GetRenderViewHost(),
280 child
->current_frame_host()->render_view_host());
281 EXPECT_NE(rvh
, child
->current_frame_host()->render_view_host());
282 EXPECT_NE(shell()->web_contents()->GetSiteInstance(),
283 child
->current_frame_host()->GetSiteInstance());
284 EXPECT_NE(site_instance
,
285 child
->current_frame_host()->GetSiteInstance());
286 EXPECT_NE(shell()->web_contents()->GetRenderProcessHost(),
287 child
->current_frame_host()->GetProcess());
288 EXPECT_NE(rph
, child
->current_frame_host()->GetProcess());
290 std::set
<RenderWidgetHostView
*> views_set
=
291 static_cast<WebContentsImpl
*>(shell()->web_contents())
292 ->GetRenderWidgetHostViewsInTree();
293 EXPECT_EQ(2U, views_set
.size());
295 EXPECT_EQ(proxy_to_parent
, child
->render_manager()->GetProxyToParent());
296 EXPECT_TRUE(proxy_to_parent
->cross_process_frame_connector());
298 child
->current_frame_host()->render_view_host()->GetView(),
299 proxy_to_parent
->cross_process_frame_connector()->get_view_for_testing());
302 // Crash a subframe and ensures its children are cleared from the FrameTree.
303 // See http://crbug.com/338508.
304 // TODO(creis): Disabled for flakiness; see http://crbug.com/405582.
305 // TODO(creis): Enable this on Android when we can kill the process there.
306 IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest
, DISABLED_CrashSubframe
) {
307 host_resolver()->AddRule("*", "127.0.0.1");
308 ASSERT_TRUE(test_server()->Start());
309 GURL
main_url(test_server()->GetURL("files/site_per_process_main.html"));
310 NavigateToURL(shell(), main_url
);
312 StartFrameAtDataURL();
314 // These must stay in scope with replace_host.
315 GURL::Replacements replace_host
;
316 std::string
foo_com("foo.com");
318 // Load cross-site page into iframe.
319 GURL
cross_site_url(test_server()->GetURL("files/title2.html"));
320 replace_host
.SetHostStr(foo_com
);
321 cross_site_url
= cross_site_url
.ReplaceComponents(replace_host
);
322 EXPECT_TRUE(NavigateIframeToURL(shell(), cross_site_url
, "test"));
324 // Check the subframe process.
325 FrameTreeNode
* root
=
326 static_cast<WebContentsImpl
*>(shell()->web_contents())->
327 GetFrameTree()->root();
328 ASSERT_EQ(1U, root
->child_count());
329 FrameTreeNode
* child
= root
->child_at(0);
330 EXPECT_EQ(main_url
, root
->current_url());
331 EXPECT_EQ(cross_site_url
, child
->current_url());
334 child
->current_frame_host()->render_view_host()->IsRenderViewLive());
335 EXPECT_TRUE(child
->current_frame_host()->IsRenderFrameLive());
337 // Crash the subframe process.
338 RenderProcessHost
* root_process
= root
->current_frame_host()->GetProcess();
339 RenderProcessHost
* child_process
= child
->current_frame_host()->GetProcess();
341 RenderProcessHostWatcher
crash_observer(
343 RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT
);
344 base::KillProcess(child_process
->GetHandle(), 0, false);
345 crash_observer
.Wait();
348 // Ensure that the child frame still exists but has been cleared.
349 EXPECT_EQ(1U, root
->child_count());
350 EXPECT_EQ(main_url
, root
->current_url());
351 EXPECT_EQ(GURL(), child
->current_url());
354 child
->current_frame_host()->render_view_host()->IsRenderViewLive());
355 EXPECT_FALSE(child
->current_frame_host()->IsRenderFrameLive());
356 EXPECT_FALSE(child
->current_frame_host()->render_frame_created_
);
358 // Now crash the top-level page to clear the child frame.
360 RenderProcessHostWatcher
crash_observer(
362 RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT
);
363 base::KillProcess(root_process
->GetHandle(), 0, false);
364 crash_observer
.Wait();
366 EXPECT_EQ(0U, root
->child_count());
367 EXPECT_EQ(GURL(), root
->current_url());
370 // TODO(nasko): Disable this test until out-of-process iframes is ready and the
371 // security checks are back in place.
372 // TODO(creis): Replace SpawnedTestServer with host_resolver to get test to run
373 // on Android (http://crbug.com/187570).
374 IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest
,
375 DISABLED_CrossSiteIframeRedirectOnce
) {
376 ASSERT_TRUE(test_server()->Start());
377 net::SpawnedTestServer
https_server(
378 net::SpawnedTestServer::TYPE_HTTPS
,
379 net::SpawnedTestServer::kLocalhost
,
380 base::FilePath(FILE_PATH_LITERAL("content/test/data")));
381 ASSERT_TRUE(https_server
.Start());
383 GURL
main_url(test_server()->GetURL("files/site_per_process_main.html"));
384 GURL
http_url(test_server()->GetURL("files/title1.html"));
385 GURL
https_url(https_server
.GetURL("files/title1.html"));
387 NavigateToURL(shell(), main_url
);
389 SitePerProcessWebContentsObserver
observer(shell()->web_contents());
391 // Load cross-site client-redirect page into Iframe.
392 // Should be blocked.
393 GURL
client_redirect_https_url(https_server
.GetURL(
394 "client-redirect?files/title1.html"));
395 EXPECT_TRUE(NavigateIframeToURL(shell(),
396 client_redirect_https_url
, "test"));
397 // DidFailProvisionalLoad when navigating to client_redirect_https_url.
398 EXPECT_EQ(observer
.navigation_url(), client_redirect_https_url
);
399 EXPECT_FALSE(observer
.navigation_succeeded());
403 // Load cross-site server-redirect page into Iframe,
404 // which redirects to same-site page.
405 GURL
server_redirect_http_url(https_server
.GetURL(
406 "server-redirect?" + http_url
.spec()));
407 EXPECT_TRUE(NavigateIframeToURL(shell(),
408 server_redirect_http_url
, "test"));
409 EXPECT_EQ(observer
.navigation_url(), http_url
);
410 EXPECT_TRUE(observer
.navigation_succeeded());
414 // Load cross-site server-redirect page into Iframe,
415 // which redirects to cross-site page.
416 GURL
server_redirect_http_url(https_server
.GetURL(
417 "server-redirect?files/title1.html"));
418 EXPECT_TRUE(NavigateIframeToURL(shell(),
419 server_redirect_http_url
, "test"));
420 // DidFailProvisionalLoad when navigating to https_url.
421 EXPECT_EQ(observer
.navigation_url(), https_url
);
422 EXPECT_FALSE(observer
.navigation_succeeded());
426 // Load same-site server-redirect page into Iframe,
427 // which redirects to cross-site page.
428 GURL
server_redirect_http_url(test_server()->GetURL(
429 "server-redirect?" + https_url
.spec()));
430 EXPECT_TRUE(NavigateIframeToURL(shell(),
431 server_redirect_http_url
, "test"));
433 EXPECT_EQ(observer
.navigation_url(), https_url
);
434 EXPECT_FALSE(observer
.navigation_succeeded());
438 // Load same-site client-redirect page into Iframe,
439 // which redirects to cross-site page.
440 GURL
client_redirect_http_url(test_server()->GetURL(
441 "client-redirect?" + https_url
.spec()));
443 RedirectNotificationObserver
load_observer2(
444 NOTIFICATION_LOAD_STOP
,
445 Source
<NavigationController
>(
446 &shell()->web_contents()->GetController()));
448 EXPECT_TRUE(NavigateIframeToURL(shell(),
449 client_redirect_http_url
, "test"));
451 // Same-site Client-Redirect Page should be loaded successfully.
452 EXPECT_EQ(observer
.navigation_url(), client_redirect_http_url
);
453 EXPECT_TRUE(observer
.navigation_succeeded());
455 // Redirecting to Cross-site Page should be blocked.
456 load_observer2
.Wait();
457 EXPECT_EQ(observer
.navigation_url(), https_url
);
458 EXPECT_FALSE(observer
.navigation_succeeded());
462 // Load same-site server-redirect page into Iframe,
463 // which redirects to same-site page.
464 GURL
server_redirect_http_url(test_server()->GetURL(
465 "server-redirect?files/title1.html"));
466 EXPECT_TRUE(NavigateIframeToURL(shell(),
467 server_redirect_http_url
, "test"));
468 EXPECT_EQ(observer
.navigation_url(), http_url
);
469 EXPECT_TRUE(observer
.navigation_succeeded());
473 // Load same-site client-redirect page into Iframe,
474 // which redirects to same-site page.
475 GURL
client_redirect_http_url(test_server()->GetURL(
476 "client-redirect?" + http_url
.spec()));
477 RedirectNotificationObserver
load_observer2(
478 NOTIFICATION_LOAD_STOP
,
479 Source
<NavigationController
>(
480 &shell()->web_contents()->GetController()));
482 EXPECT_TRUE(NavigateIframeToURL(shell(),
483 client_redirect_http_url
, "test"));
485 // Same-site Client-Redirect Page should be loaded successfully.
486 EXPECT_EQ(observer
.navigation_url(), client_redirect_http_url
);
487 EXPECT_TRUE(observer
.navigation_succeeded());
489 // Redirecting to Same-site Page should be loaded successfully.
490 load_observer2
.Wait();
491 EXPECT_EQ(observer
.navigation_url(), http_url
);
492 EXPECT_TRUE(observer
.navigation_succeeded());
496 // TODO(nasko): Disable this test until out-of-process iframes is ready and the
497 // security checks are back in place.
498 // TODO(creis): Replace SpawnedTestServer with host_resolver to get test to run
499 // on Android (http://crbug.com/187570).
500 IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest
,
501 DISABLED_CrossSiteIframeRedirectTwice
) {
502 ASSERT_TRUE(test_server()->Start());
503 net::SpawnedTestServer
https_server(
504 net::SpawnedTestServer::TYPE_HTTPS
,
505 net::SpawnedTestServer::kLocalhost
,
506 base::FilePath(FILE_PATH_LITERAL("content/test/data")));
507 ASSERT_TRUE(https_server
.Start());
509 GURL
main_url(test_server()->GetURL("files/site_per_process_main.html"));
510 GURL
http_url(test_server()->GetURL("files/title1.html"));
511 GURL
https_url(https_server
.GetURL("files/title1.html"));
513 NavigateToURL(shell(), main_url
);
515 SitePerProcessWebContentsObserver
observer(shell()->web_contents());
517 // Load client-redirect page pointing to a cross-site client-redirect page,
518 // which eventually redirects back to same-site page.
519 GURL
client_redirect_https_url(https_server
.GetURL(
520 "client-redirect?" + http_url
.spec()));
521 GURL
client_redirect_http_url(test_server()->GetURL(
522 "client-redirect?" + client_redirect_https_url
.spec()));
524 // We should wait until second client redirect get cancelled.
525 RedirectNotificationObserver
load_observer2(
526 NOTIFICATION_LOAD_STOP
,
527 Source
<NavigationController
>(
528 &shell()->web_contents()->GetController()));
530 EXPECT_TRUE(NavigateIframeToURL(shell(), client_redirect_http_url
, "test"));
532 // DidFailProvisionalLoad when navigating to client_redirect_https_url.
533 load_observer2
.Wait();
534 EXPECT_EQ(observer
.navigation_url(), client_redirect_https_url
);
535 EXPECT_FALSE(observer
.navigation_succeeded());
539 // Load server-redirect page pointing to a cross-site server-redirect page,
540 // which eventually redirect back to same-site page.
541 GURL
server_redirect_https_url(https_server
.GetURL(
542 "server-redirect?" + http_url
.spec()));
543 GURL
server_redirect_http_url(test_server()->GetURL(
544 "server-redirect?" + server_redirect_https_url
.spec()));
545 EXPECT_TRUE(NavigateIframeToURL(shell(),
546 server_redirect_http_url
, "test"));
547 EXPECT_EQ(observer
.navigation_url(), http_url
);
548 EXPECT_TRUE(observer
.navigation_succeeded());
552 // Load server-redirect page pointing to a cross-site server-redirect page,
553 // which eventually redirects back to cross-site page.
554 GURL
server_redirect_https_url(https_server
.GetURL(
555 "server-redirect?" + https_url
.spec()));
556 GURL
server_redirect_http_url(test_server()->GetURL(
557 "server-redirect?" + server_redirect_https_url
.spec()));
558 EXPECT_TRUE(NavigateIframeToURL(shell(), server_redirect_http_url
, "test"));
560 // DidFailProvisionalLoad when navigating to https_url.
561 EXPECT_EQ(observer
.navigation_url(), https_url
);
562 EXPECT_FALSE(observer
.navigation_succeeded());
566 // Load server-redirect page pointing to a cross-site client-redirect page,
567 // which eventually redirects back to same-site page.
568 GURL
client_redirect_http_url(https_server
.GetURL(
569 "client-redirect?" + http_url
.spec()));
570 GURL
server_redirect_http_url(test_server()->GetURL(
571 "server-redirect?" + client_redirect_http_url
.spec()));
572 EXPECT_TRUE(NavigateIframeToURL(shell(), server_redirect_http_url
, "test"));
574 // DidFailProvisionalLoad when navigating to client_redirect_http_url.
575 EXPECT_EQ(observer
.navigation_url(), client_redirect_http_url
);
576 EXPECT_FALSE(observer
.navigation_succeeded());
580 } // namespace content