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): Disabled for flakiness; see http://crbug.com/405582.
303 // TODO(creis): Enable this on Android when we can kill the process there.
304 IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest
, DISABLED_CrashSubframe
) {
305 host_resolver()->AddRule("*", "127.0.0.1");
306 ASSERT_TRUE(test_server()->Start());
307 GURL
main_url(test_server()->GetURL("files/site_per_process_main.html"));
308 NavigateToURL(shell(), main_url
);
310 StartFrameAtDataURL();
312 // These must stay in scope with replace_host.
313 GURL::Replacements replace_host
;
314 std::string
foo_com("foo.com");
316 // Load cross-site page into iframe.
317 GURL
cross_site_url(test_server()->GetURL("files/title2.html"));
318 replace_host
.SetHostStr(foo_com
);
319 cross_site_url
= cross_site_url
.ReplaceComponents(replace_host
);
320 EXPECT_TRUE(NavigateIframeToURL(shell(), cross_site_url
, "test"));
322 // Check the subframe process.
323 FrameTreeNode
* root
=
324 static_cast<WebContentsImpl
*>(shell()->web_contents())->
325 GetFrameTree()->root();
326 ASSERT_EQ(1U, root
->child_count());
327 FrameTreeNode
* child
= root
->child_at(0);
328 EXPECT_EQ(main_url
, root
->current_url());
329 EXPECT_EQ(cross_site_url
, child
->current_url());
331 // Crash the subframe process.
332 RenderProcessHost
* root_process
= root
->current_frame_host()->GetProcess();
333 RenderProcessHost
* child_process
= child
->current_frame_host()->GetProcess();
335 RenderProcessHostWatcher
crash_observer(
337 RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT
);
338 base::KillProcess(child_process
->GetHandle(), 0, false);
339 crash_observer
.Wait();
342 // Ensure that the child frame still exists but has been cleared.
343 EXPECT_EQ(1U, root
->child_count());
344 EXPECT_EQ(main_url
, root
->current_url());
345 EXPECT_EQ(GURL(), child
->current_url());
347 // Now crash the top-level page to clear the child frame.
349 RenderProcessHostWatcher
crash_observer(
351 RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT
);
352 base::KillProcess(root_process
->GetHandle(), 0, false);
353 crash_observer
.Wait();
355 EXPECT_EQ(0U, root
->child_count());
356 EXPECT_EQ(GURL(), root
->current_url());
359 // TODO(nasko): Disable this test until out-of-process iframes is ready and the
360 // security checks are back in place.
361 // TODO(creis): Replace SpawnedTestServer with host_resolver to get test to run
362 // on Android (http://crbug.com/187570).
363 IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest
,
364 DISABLED_CrossSiteIframeRedirectOnce
) {
365 ASSERT_TRUE(test_server()->Start());
366 net::SpawnedTestServer
https_server(
367 net::SpawnedTestServer::TYPE_HTTPS
,
368 net::SpawnedTestServer::kLocalhost
,
369 base::FilePath(FILE_PATH_LITERAL("content/test/data")));
370 ASSERT_TRUE(https_server
.Start());
372 GURL
main_url(test_server()->GetURL("files/site_per_process_main.html"));
373 GURL
http_url(test_server()->GetURL("files/title1.html"));
374 GURL
https_url(https_server
.GetURL("files/title1.html"));
376 NavigateToURL(shell(), main_url
);
378 SitePerProcessWebContentsObserver
observer(shell()->web_contents());
380 // Load cross-site client-redirect page into Iframe.
381 // Should be blocked.
382 GURL
client_redirect_https_url(https_server
.GetURL(
383 "client-redirect?files/title1.html"));
384 EXPECT_TRUE(NavigateIframeToURL(shell(),
385 client_redirect_https_url
, "test"));
386 // DidFailProvisionalLoad when navigating to client_redirect_https_url.
387 EXPECT_EQ(observer
.navigation_url(), client_redirect_https_url
);
388 EXPECT_FALSE(observer
.navigation_succeeded());
392 // Load cross-site server-redirect page into Iframe,
393 // which redirects to same-site page.
394 GURL
server_redirect_http_url(https_server
.GetURL(
395 "server-redirect?" + http_url
.spec()));
396 EXPECT_TRUE(NavigateIframeToURL(shell(),
397 server_redirect_http_url
, "test"));
398 EXPECT_EQ(observer
.navigation_url(), http_url
);
399 EXPECT_TRUE(observer
.navigation_succeeded());
403 // Load cross-site server-redirect page into Iframe,
404 // which redirects to cross-site page.
405 GURL
server_redirect_http_url(https_server
.GetURL(
406 "server-redirect?files/title1.html"));
407 EXPECT_TRUE(NavigateIframeToURL(shell(),
408 server_redirect_http_url
, "test"));
409 // DidFailProvisionalLoad when navigating to https_url.
410 EXPECT_EQ(observer
.navigation_url(), https_url
);
411 EXPECT_FALSE(observer
.navigation_succeeded());
415 // Load same-site server-redirect page into Iframe,
416 // which redirects to cross-site page.
417 GURL
server_redirect_http_url(test_server()->GetURL(
418 "server-redirect?" + https_url
.spec()));
419 EXPECT_TRUE(NavigateIframeToURL(shell(),
420 server_redirect_http_url
, "test"));
422 EXPECT_EQ(observer
.navigation_url(), https_url
);
423 EXPECT_FALSE(observer
.navigation_succeeded());
427 // Load same-site client-redirect page into Iframe,
428 // which redirects to cross-site page.
429 GURL
client_redirect_http_url(test_server()->GetURL(
430 "client-redirect?" + https_url
.spec()));
432 RedirectNotificationObserver
load_observer2(
433 NOTIFICATION_LOAD_STOP
,
434 Source
<NavigationController
>(
435 &shell()->web_contents()->GetController()));
437 EXPECT_TRUE(NavigateIframeToURL(shell(),
438 client_redirect_http_url
, "test"));
440 // Same-site Client-Redirect Page should be loaded successfully.
441 EXPECT_EQ(observer
.navigation_url(), client_redirect_http_url
);
442 EXPECT_TRUE(observer
.navigation_succeeded());
444 // Redirecting to Cross-site Page should be blocked.
445 load_observer2
.Wait();
446 EXPECT_EQ(observer
.navigation_url(), https_url
);
447 EXPECT_FALSE(observer
.navigation_succeeded());
451 // Load same-site server-redirect page into Iframe,
452 // which redirects to same-site page.
453 GURL
server_redirect_http_url(test_server()->GetURL(
454 "server-redirect?files/title1.html"));
455 EXPECT_TRUE(NavigateIframeToURL(shell(),
456 server_redirect_http_url
, "test"));
457 EXPECT_EQ(observer
.navigation_url(), http_url
);
458 EXPECT_TRUE(observer
.navigation_succeeded());
462 // Load same-site client-redirect page into Iframe,
463 // which redirects to same-site page.
464 GURL
client_redirect_http_url(test_server()->GetURL(
465 "client-redirect?" + http_url
.spec()));
466 RedirectNotificationObserver
load_observer2(
467 NOTIFICATION_LOAD_STOP
,
468 Source
<NavigationController
>(
469 &shell()->web_contents()->GetController()));
471 EXPECT_TRUE(NavigateIframeToURL(shell(),
472 client_redirect_http_url
, "test"));
474 // Same-site Client-Redirect Page should be loaded successfully.
475 EXPECT_EQ(observer
.navigation_url(), client_redirect_http_url
);
476 EXPECT_TRUE(observer
.navigation_succeeded());
478 // Redirecting to Same-site Page should be loaded successfully.
479 load_observer2
.Wait();
480 EXPECT_EQ(observer
.navigation_url(), http_url
);
481 EXPECT_TRUE(observer
.navigation_succeeded());
485 // TODO(nasko): Disable this test until out-of-process iframes is ready and the
486 // security checks are back in place.
487 // TODO(creis): Replace SpawnedTestServer with host_resolver to get test to run
488 // on Android (http://crbug.com/187570).
489 IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest
,
490 DISABLED_CrossSiteIframeRedirectTwice
) {
491 ASSERT_TRUE(test_server()->Start());
492 net::SpawnedTestServer
https_server(
493 net::SpawnedTestServer::TYPE_HTTPS
,
494 net::SpawnedTestServer::kLocalhost
,
495 base::FilePath(FILE_PATH_LITERAL("content/test/data")));
496 ASSERT_TRUE(https_server
.Start());
498 GURL
main_url(test_server()->GetURL("files/site_per_process_main.html"));
499 GURL
http_url(test_server()->GetURL("files/title1.html"));
500 GURL
https_url(https_server
.GetURL("files/title1.html"));
502 NavigateToURL(shell(), main_url
);
504 SitePerProcessWebContentsObserver
observer(shell()->web_contents());
506 // Load client-redirect page pointing to a cross-site client-redirect page,
507 // which eventually redirects back to same-site page.
508 GURL
client_redirect_https_url(https_server
.GetURL(
509 "client-redirect?" + http_url
.spec()));
510 GURL
client_redirect_http_url(test_server()->GetURL(
511 "client-redirect?" + client_redirect_https_url
.spec()));
513 // We should wait until second client redirect get cancelled.
514 RedirectNotificationObserver
load_observer2(
515 NOTIFICATION_LOAD_STOP
,
516 Source
<NavigationController
>(
517 &shell()->web_contents()->GetController()));
519 EXPECT_TRUE(NavigateIframeToURL(shell(), client_redirect_http_url
, "test"));
521 // DidFailProvisionalLoad when navigating to client_redirect_https_url.
522 load_observer2
.Wait();
523 EXPECT_EQ(observer
.navigation_url(), client_redirect_https_url
);
524 EXPECT_FALSE(observer
.navigation_succeeded());
528 // Load server-redirect page pointing to a cross-site server-redirect page,
529 // which eventually redirect back to same-site page.
530 GURL
server_redirect_https_url(https_server
.GetURL(
531 "server-redirect?" + http_url
.spec()));
532 GURL
server_redirect_http_url(test_server()->GetURL(
533 "server-redirect?" + server_redirect_https_url
.spec()));
534 EXPECT_TRUE(NavigateIframeToURL(shell(),
535 server_redirect_http_url
, "test"));
536 EXPECT_EQ(observer
.navigation_url(), http_url
);
537 EXPECT_TRUE(observer
.navigation_succeeded());
541 // Load server-redirect page pointing to a cross-site server-redirect page,
542 // which eventually redirects back to cross-site page.
543 GURL
server_redirect_https_url(https_server
.GetURL(
544 "server-redirect?" + https_url
.spec()));
545 GURL
server_redirect_http_url(test_server()->GetURL(
546 "server-redirect?" + server_redirect_https_url
.spec()));
547 EXPECT_TRUE(NavigateIframeToURL(shell(), server_redirect_http_url
, "test"));
549 // DidFailProvisionalLoad when navigating to https_url.
550 EXPECT_EQ(observer
.navigation_url(), https_url
);
551 EXPECT_FALSE(observer
.navigation_succeeded());
555 // Load server-redirect page pointing to a cross-site client-redirect page,
556 // which eventually redirects back to same-site page.
557 GURL
client_redirect_http_url(https_server
.GetURL(
558 "client-redirect?" + http_url
.spec()));
559 GURL
server_redirect_http_url(test_server()->GetURL(
560 "server-redirect?" + client_redirect_http_url
.spec()));
561 EXPECT_TRUE(NavigateIframeToURL(shell(), server_redirect_http_url
, "test"));
563 // DidFailProvisionalLoad when navigating to client_redirect_http_url.
564 EXPECT_EQ(observer
.navigation_url(), client_redirect_http_url
);
565 EXPECT_FALSE(observer
.navigation_succeeded());
569 } // namespace content