blob: 838d33c642383b336b5944d1706f0ffbc9f8589d [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/command_line.h"
#include "content/browser/child_process_security_policy_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/test_frame_navigation_observer.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/test_utils.h"
#include "content/shell/browser/shell.h"
#include "content/test/content_browser_test_utils_internal.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "url/gurl.h"
namespace content {
class IsolatedOriginTest : public ContentBrowserTest {
public:
IsolatedOriginTest() {}
~IsolatedOriginTest() override {}
void SetUpCommandLine(base::CommandLine* command_line) override {
ASSERT_TRUE(embedded_test_server()->InitializeAndListen());
std::string origin_list =
embedded_test_server()->GetURL("isolated.foo.com", "/").spec() + "," +
embedded_test_server()->GetURL("isolated.bar.com", "/").spec();
command_line->AppendSwitchASCII(switches::kIsolateOrigins, origin_list);
}
void SetUpOnMainThread() override {
host_resolver()->AddRule("*", "127.0.0.1");
embedded_test_server()->StartAcceptingConnections();
}
WebContentsImpl* web_contents() const {
return static_cast<WebContentsImpl*>(shell()->web_contents());
}
};
// Check that navigating a main frame from an non-isolated origin to an
// isolated origin and vice versa swaps processes and uses a new SiteInstance,
// both for browser-initiated and renderer-initiated navigations.
IN_PROC_BROWSER_TEST_F(IsolatedOriginTest, MainFrameNavigation) {
GURL unisolated_url(
embedded_test_server()->GetURL("www.foo.com", "/title1.html"));
GURL isolated_url(
embedded_test_server()->GetURL("isolated.foo.com", "/title2.html"));
EXPECT_TRUE(NavigateToURL(shell(), unisolated_url));
// Open a same-site popup to keep the www.foo.com process alive.
Shell* popup = OpenPopup(shell(), GURL(url::kAboutBlankURL), "foo");
SiteInstance* unisolated_instance =
popup->web_contents()->GetMainFrame()->GetSiteInstance();
RenderProcessHost* unisolated_process =
popup->web_contents()->GetMainFrame()->GetProcess();
// Perform a browser-initiated navigation to an isolated origin and ensure
// that this ends up in a new process and SiteInstance for isolated.foo.com.
EXPECT_TRUE(NavigateToURL(shell(), isolated_url));
scoped_refptr<SiteInstance> isolated_instance =
web_contents()->GetSiteInstance();
EXPECT_NE(isolated_instance, unisolated_instance);
EXPECT_NE(web_contents()->GetMainFrame()->GetProcess(), unisolated_process);
// The site URL for isolated.foo.com should be the full origin rather than
// scheme and eTLD+1.
EXPECT_EQ(isolated_url.GetOrigin(), isolated_instance->GetSiteURL());
// Now perform a renderer-initiated navigation to an unisolated origin,
// www.foo.com. This should end up in the |popup|'s process.
{
TestNavigationObserver observer(web_contents());
EXPECT_TRUE(ExecuteScript(
web_contents(), "location.href = '" + unisolated_url.spec() + "'"));
observer.Wait();
}
EXPECT_EQ(unisolated_instance, web_contents()->GetSiteInstance());
EXPECT_EQ(unisolated_process, web_contents()->GetMainFrame()->GetProcess());
// Go to isolated.foo.com again, this time with a renderer-initiated
// navigation from the unisolated www.foo.com.
{
TestNavigationObserver observer(web_contents());
EXPECT_TRUE(ExecuteScript(web_contents(),
"location.href = '" + isolated_url.spec() + "'"));
observer.Wait();
}
EXPECT_EQ(isolated_instance, web_contents()->GetSiteInstance());
EXPECT_NE(unisolated_process, web_contents()->GetMainFrame()->GetProcess());
// Go back to www.foo.com: this should end up in the unisolated process.
{
TestNavigationObserver back_observer(web_contents());
web_contents()->GetController().GoBack();
back_observer.Wait();
}
EXPECT_EQ(unisolated_instance, web_contents()->GetSiteInstance());
EXPECT_EQ(unisolated_process, web_contents()->GetMainFrame()->GetProcess());
// Go back again. This should go to isolated.foo.com in an isolated process.
{
TestNavigationObserver back_observer(web_contents());
web_contents()->GetController().GoBack();
back_observer.Wait();
}
EXPECT_EQ(isolated_instance, web_contents()->GetSiteInstance());
EXPECT_NE(unisolated_process, web_contents()->GetMainFrame()->GetProcess());
// Do a renderer-initiated navigation from isolated.foo.com to another
// isolated origin and ensure there is a different isolated process.
GURL second_isolated_url(
embedded_test_server()->GetURL("isolated.bar.com", "/title3.html"));
{
TestNavigationObserver observer(web_contents());
EXPECT_TRUE(
ExecuteScript(web_contents(),
"location.href = '" + second_isolated_url.spec() + "'"));
observer.Wait();
}
EXPECT_EQ(second_isolated_url.GetOrigin(),
web_contents()->GetSiteInstance()->GetSiteURL());
EXPECT_NE(isolated_instance, web_contents()->GetSiteInstance());
EXPECT_NE(unisolated_instance, web_contents()->GetSiteInstance());
}
// Check that opening a popup for an isolated origin puts it into a new process
// and its own SiteInstance.
IN_PROC_BROWSER_TEST_F(IsolatedOriginTest, Popup) {
GURL unisolated_url(
embedded_test_server()->GetURL("foo.com", "/title1.html"));
GURL isolated_url(
embedded_test_server()->GetURL("isolated.foo.com", "/title2.html"));
EXPECT_TRUE(NavigateToURL(shell(), unisolated_url));
// Open a popup to a URL with an isolated origin and ensure that there was a
// process swap.
Shell* popup = OpenPopup(shell(), isolated_url, "foo");
EXPECT_NE(shell()->web_contents()->GetSiteInstance(),
popup->web_contents()->GetSiteInstance());
// The popup's site URL should match the full isolated origin.
EXPECT_EQ(isolated_url.GetOrigin(),
popup->web_contents()->GetSiteInstance()->GetSiteURL());
// Now open a second popup from an isolated origin to a URL with an
// unisolated origin and ensure that there was another process swap.
Shell* popup2 = OpenPopup(popup, unisolated_url, "bar");
EXPECT_EQ(shell()->web_contents()->GetSiteInstance(),
popup2->web_contents()->GetSiteInstance());
EXPECT_NE(popup->web_contents()->GetSiteInstance(),
popup2->web_contents()->GetSiteInstance());
}
// Check that navigating a subframe to an isolated origin puts the subframe
// into an OOPIF and its own SiteInstance. Also check that the isolated
// frame's subframes also end up in correct SiteInstance.
IN_PROC_BROWSER_TEST_F(IsolatedOriginTest, Subframe) {
GURL top_url(
embedded_test_server()->GetURL("www.foo.com", "/page_with_iframe.html"));
EXPECT_TRUE(NavigateToURL(shell(), top_url));
GURL isolated_url(embedded_test_server()->GetURL("isolated.foo.com",
"/page_with_iframe.html"));
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
FrameTreeNode* child = root->child_at(0);
NavigateIframeToURL(web_contents(), "test_iframe", isolated_url);
EXPECT_EQ(child->current_url(), isolated_url);
// Verify that the child frame is an OOPIF with a different SiteInstance.
EXPECT_NE(web_contents()->GetSiteInstance(),
child->current_frame_host()->GetSiteInstance());
EXPECT_TRUE(child->current_frame_host()->IsCrossProcessSubframe());
EXPECT_EQ(isolated_url.GetOrigin(),
child->current_frame_host()->GetSiteInstance()->GetSiteURL());
// Verify that the isolated frame's subframe (which starts out at a relative
// path) is kept in the isolated parent's SiteInstance.
FrameTreeNode* grandchild = child->child_at(0);
EXPECT_EQ(child->current_frame_host()->GetSiteInstance(),
grandchild->current_frame_host()->GetSiteInstance());
// Navigating the grandchild to www.foo.com should put it into the top
// frame's SiteInstance.
GURL non_isolated_url(
embedded_test_server()->GetURL("www.foo.com", "/title3.html"));
TestFrameNavigationObserver observer(grandchild);
EXPECT_TRUE(ExecuteScript(
grandchild, "location.href = '" + non_isolated_url.spec() + "';"));
observer.Wait();
EXPECT_EQ(non_isolated_url, grandchild->current_url());
EXPECT_EQ(root->current_frame_host()->GetSiteInstance(),
grandchild->current_frame_host()->GetSiteInstance());
EXPECT_NE(child->current_frame_host()->GetSiteInstance(),
grandchild->current_frame_host()->GetSiteInstance());
}
// Check that when an non-isolated origin foo.com embeds a subframe from an
// isolated origin, which then navigates to a non-isolated origin bar.com,
// bar.com goes back to the main frame's SiteInstance. See
// https://crbug.com/711006.
IN_PROC_BROWSER_TEST_F(IsolatedOriginTest,
NoOOPIFWhenIsolatedOriginNavigatesToNonIsolatedOrigin) {
if (AreAllSitesIsolatedForTesting())
return;
GURL top_url(
embedded_test_server()->GetURL("www.foo.com", "/page_with_iframe.html"));
EXPECT_TRUE(NavigateToURL(shell(), top_url));
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
FrameTreeNode* child = root->child_at(0);
GURL isolated_url(embedded_test_server()->GetURL("isolated.foo.com",
"/page_with_iframe.html"));
NavigateIframeToURL(web_contents(), "test_iframe", isolated_url);
EXPECT_EQ(isolated_url, child->current_url());
// Verify that the child frame is an OOPIF with a different SiteInstance.
EXPECT_NE(web_contents()->GetSiteInstance(),
child->current_frame_host()->GetSiteInstance());
EXPECT_TRUE(child->current_frame_host()->IsCrossProcessSubframe());
EXPECT_EQ(isolated_url.GetOrigin(),
child->current_frame_host()->GetSiteInstance()->GetSiteURL());
// Navigate the child frame cross-site, but to a non-isolated origin. When
// not in --site-per-process, this should bring the subframe back into the
// main frame's SiteInstance.
GURL bar_url(embedded_test_server()->GetURL("bar.com", "/title1.html"));
auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
EXPECT_FALSE(policy->IsIsolatedOrigin(url::Origin(bar_url)));
NavigateIframeToURL(web_contents(), "test_iframe", bar_url);
EXPECT_EQ(web_contents()->GetSiteInstance(),
child->current_frame_host()->GetSiteInstance());
EXPECT_FALSE(child->current_frame_host()->IsCrossProcessSubframe());
}
// Check that a new isolated origin subframe will attempt to reuse an existing
// process for that isolated origin, even across BrowsingInstances. Also check
// that main frame navigations to an isolated origin keep using the default
// process model and do not reuse existing processes.
IN_PROC_BROWSER_TEST_F(IsolatedOriginTest, SubframeReusesExistingProcess) {
GURL top_url(
embedded_test_server()->GetURL("www.foo.com", "/page_with_iframe.html"));
EXPECT_TRUE(NavigateToURL(shell(), top_url));
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
FrameTreeNode* child = root->child_at(0);
// Open an unrelated tab in a separate BrowsingInstance, and navigate it to
// to an isolated origin. This SiteInstance should have a default process
// reuse policy - only subframes attempt process reuse.
GURL isolated_url(embedded_test_server()->GetURL("isolated.foo.com",
"/page_with_iframe.html"));
Shell* second_shell = CreateBrowser();
EXPECT_TRUE(NavigateToURL(second_shell, isolated_url));
scoped_refptr<SiteInstanceImpl> second_shell_instance =
static_cast<SiteInstanceImpl*>(
second_shell->web_contents()->GetMainFrame()->GetSiteInstance());
EXPECT_FALSE(second_shell_instance->IsRelatedSiteInstance(
root->current_frame_host()->GetSiteInstance()));
RenderProcessHost* isolated_process = second_shell_instance->GetProcess();
EXPECT_EQ(SiteInstanceImpl::ProcessReusePolicy::DEFAULT,
second_shell_instance->process_reuse_policy());
// Now navigate the first tab's subframe to an isolated origin. See that it
// reuses the existing |isolated_process|.
NavigateIframeToURL(web_contents(), "test_iframe", isolated_url);
EXPECT_EQ(isolated_url, child->current_url());
EXPECT_EQ(isolated_process, child->current_frame_host()->GetProcess());
EXPECT_EQ(
SiteInstanceImpl::ProcessReusePolicy::REUSE_PENDING_OR_COMMITTED_SITE,
child->current_frame_host()->GetSiteInstance()->process_reuse_policy());
EXPECT_TRUE(child->current_frame_host()->IsCrossProcessSubframe());
EXPECT_EQ(isolated_url.GetOrigin(),
child->current_frame_host()->GetSiteInstance()->GetSiteURL());
// The subframe's SiteInstance should still be different from second_shell's
// SiteInstance, and they should be in separate BrowsingInstances.
EXPECT_NE(second_shell_instance,
child->current_frame_host()->GetSiteInstance());
EXPECT_FALSE(second_shell_instance->IsRelatedSiteInstance(
child->current_frame_host()->GetSiteInstance()));
// Navigate the second tab to a normal URL with a same-site subframe. This
// leaves only the first tab's subframe in the isolated origin process.
EXPECT_TRUE(NavigateToURL(second_shell, top_url));
EXPECT_NE(isolated_process,
second_shell->web_contents()->GetMainFrame()->GetProcess());
// Navigate the second tab's subframe to an isolated origin, and check that
// this new subframe reuses the isolated process of the subframe in the first
// tab, even though the two are in separate BrowsingInstances.
NavigateIframeToURL(second_shell->web_contents(), "test_iframe",
isolated_url);
FrameTreeNode* second_subframe =
static_cast<WebContentsImpl*>(second_shell->web_contents())
->GetFrameTree()
->root()
->child_at(0);
EXPECT_EQ(isolated_process,
second_subframe->current_frame_host()->GetProcess());
EXPECT_NE(child->current_frame_host()->GetSiteInstance(),
second_subframe->current_frame_host()->GetSiteInstance());
// Open a third, unrelated tab, navigate it to an isolated origin, and check
// that its main frame doesn't share a process with the existing isolated
// subframes.
Shell* third_shell = CreateBrowser();
EXPECT_TRUE(NavigateToURL(third_shell, isolated_url));
SiteInstanceImpl* third_shell_instance = static_cast<SiteInstanceImpl*>(
third_shell->web_contents()->GetMainFrame()->GetSiteInstance());
EXPECT_NE(third_shell_instance,
second_subframe->current_frame_host()->GetSiteInstance());
EXPECT_NE(third_shell_instance,
child->current_frame_host()->GetSiteInstance());
EXPECT_NE(third_shell_instance->GetProcess(), isolated_process);
}
// Check that isolated origins can access cookies. This requires cookie checks
// on the IO thread to be aware of isolated origins.
IN_PROC_BROWSER_TEST_F(IsolatedOriginTest, Cookies) {
GURL isolated_url(
embedded_test_server()->GetURL("isolated.foo.com", "/title2.html"));
EXPECT_TRUE(NavigateToURL(shell(), isolated_url));
EXPECT_TRUE(ExecuteScript(web_contents(), "document.cookie = 'foo=bar';"));
std::string cookie;
EXPECT_TRUE(ExecuteScriptAndExtractString(
web_contents(), "window.domAutomationController.send(document.cookie);",
&cookie));
EXPECT_EQ("foo=bar", cookie);
}
// Check that isolated origins won't be placed into processes for other sites
// when over the process limit.
IN_PROC_BROWSER_TEST_F(IsolatedOriginTest, ProcessLimit) {
// Set the process limit to 1.
RenderProcessHost::SetMaxRendererProcessCount(1);
// Navigate to an unisolated foo.com URL with an iframe.
GURL foo_url(
embedded_test_server()->GetURL("www.foo.com", "/page_with_iframe.html"));
EXPECT_TRUE(NavigateToURL(shell(), foo_url));
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
RenderProcessHost* foo_process = root->current_frame_host()->GetProcess();
FrameTreeNode* child = root->child_at(0);
// Navigate iframe to an isolated origin.
GURL isolated_foo_url(
embedded_test_server()->GetURL("isolated.foo.com", "/title2.html"));
NavigateIframeToURL(web_contents(), "test_iframe", isolated_foo_url);
// Ensure that the subframe was rendered in a new process.
EXPECT_NE(child->current_frame_host()->GetProcess(), foo_process);
// Sanity-check IsSuitableHost values for the current processes.
BrowserContext* browser_context = web_contents()->GetBrowserContext();
auto is_suitable_host = [browser_context](RenderProcessHost* process,
GURL url) {
return RenderProcessHostImpl::IsSuitableHost(
process, browser_context,
SiteInstance::GetSiteForURL(browser_context, url));
};
EXPECT_TRUE(is_suitable_host(foo_process, foo_url));
EXPECT_FALSE(is_suitable_host(foo_process, isolated_foo_url));
EXPECT_TRUE(is_suitable_host(child->current_frame_host()->GetProcess(),
isolated_foo_url));
EXPECT_FALSE(
is_suitable_host(child->current_frame_host()->GetProcess(), foo_url));
// Open a new, unrelated tab and navigate it to isolated.foo.com. This
// should use a new, unrelated SiteInstance that reuses the existing isolated
// origin process from first tab's subframe.
Shell* new_shell = CreateBrowser();
EXPECT_TRUE(NavigateToURL(new_shell, isolated_foo_url));
scoped_refptr<SiteInstance> isolated_foo_instance(
new_shell->web_contents()->GetMainFrame()->GetSiteInstance());
RenderProcessHost* isolated_foo_process = isolated_foo_instance->GetProcess();
EXPECT_NE(child->current_frame_host()->GetSiteInstance(),
isolated_foo_instance);
EXPECT_FALSE(isolated_foo_instance->IsRelatedSiteInstance(
child->current_frame_host()->GetSiteInstance()));
// TODO(alexmos): with --site-per-process, this won't currently reuse the
// subframe process, because the new SiteInstance will initialize its
// process while it still has no site (during CreateBrowser()), and since
// dedicated processes can't currently be reused for a SiteInstance with no
// site, this creates a new process. The subsequent navigation to
// |isolated_foo_url| stays in that new process without consulting whether it
// can now reuse a different process. This should be fixed; see
// https://crbug.com/513036. Without --site-per-process, this works because
// the site-less SiteInstance is allowed to reuse the first tab's foo.com
// process (which isn't dedicated), and then it swaps to the isolated.foo.com
// process during navigation.
if (!AreAllSitesIsolatedForTesting())
EXPECT_EQ(child->current_frame_host()->GetProcess(), isolated_foo_process);
// Navigate iframe on the first tab to a non-isolated site. This should swap
// processes so that it does not reuse the isolated origin's process.
NavigateIframeToURL(
web_contents(), "test_iframe",
embedded_test_server()->GetURL("www.foo.com", "/title1.html"));
EXPECT_EQ(foo_process, child->current_frame_host()->GetProcess());
EXPECT_NE(isolated_foo_process, child->current_frame_host()->GetProcess());
// Navigate iframe back to isolated origin. See that it reuses the
// |new_shell| process.
NavigateIframeToURL(web_contents(), "test_iframe", isolated_foo_url);
EXPECT_NE(foo_process, child->current_frame_host()->GetProcess());
EXPECT_EQ(isolated_foo_process, child->current_frame_host()->GetProcess());
// Navigate iframe to a different isolated origin. Ensure that this creates
// a third process.
GURL isolated_bar_url(
embedded_test_server()->GetURL("isolated.bar.com", "/title3.html"));
NavigateIframeToURL(web_contents(), "test_iframe", isolated_bar_url);
RenderProcessHost* isolated_bar_process =
child->current_frame_host()->GetProcess();
EXPECT_NE(foo_process, isolated_bar_process);
EXPECT_NE(isolated_foo_process, isolated_bar_process);
// The new process should only be suitable to host isolated.bar.com, not
// regular web URLs or other isolated origins.
EXPECT_TRUE(is_suitable_host(isolated_bar_process, isolated_bar_url));
EXPECT_FALSE(is_suitable_host(isolated_bar_process, foo_url));
EXPECT_FALSE(is_suitable_host(isolated_bar_process, isolated_foo_url));
// Navigate second tab (currently at isolated.foo.com) to the
// second isolated origin, and see that it switches processes.
EXPECT_TRUE(NavigateToURL(new_shell, isolated_bar_url));
EXPECT_NE(foo_process,
new_shell->web_contents()->GetMainFrame()->GetProcess());
EXPECT_NE(isolated_foo_process,
new_shell->web_contents()->GetMainFrame()->GetProcess());
EXPECT_EQ(isolated_bar_process,
new_shell->web_contents()->GetMainFrame()->GetProcess());
// Navigate second tab to a non-isolated URL and see that it goes back into
// the www.foo.com process, and that it does not share processes with any
// isolated origins.
EXPECT_TRUE(NavigateToURL(new_shell, foo_url));
EXPECT_EQ(foo_process,
new_shell->web_contents()->GetMainFrame()->GetProcess());
EXPECT_NE(isolated_foo_process,
new_shell->web_contents()->GetMainFrame()->GetProcess());
EXPECT_NE(isolated_bar_process,
new_shell->web_contents()->GetMainFrame()->GetProcess());
}
// Check that subdomains on an isolated origin (e.g., bar.isolated.foo.com)
// also end up in the isolated origin's SiteInstance.
IN_PROC_BROWSER_TEST_F(IsolatedOriginTest, IsolatedOriginWithSubdomain) {
// Start on a page with an isolated origin with a same-site iframe.
GURL isolated_url(embedded_test_server()->GetURL("isolated.foo.com",
"/page_with_iframe.html"));
EXPECT_TRUE(NavigateToURL(shell(), isolated_url));
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
FrameTreeNode* child = root->child_at(0);
scoped_refptr<SiteInstance> isolated_instance =
web_contents()->GetSiteInstance();
// Navigate iframe to the isolated origin's subdomain.
GURL isolated_subdomain_url(
embedded_test_server()->GetURL("bar.isolated.foo.com", "/title1.html"));
NavigateIframeToURL(web_contents(), "test_iframe", isolated_subdomain_url);
EXPECT_EQ(child->current_url(), isolated_subdomain_url);
EXPECT_EQ(isolated_instance, child->current_frame_host()->GetSiteInstance());
EXPECT_FALSE(child->current_frame_host()->IsCrossProcessSubframe());
EXPECT_EQ(isolated_url.GetOrigin(),
child->current_frame_host()->GetSiteInstance()->GetSiteURL());
// Now try navigating the main frame (renderer-initiated) to the isolated
// origin's subdomain. This should not swap processes.
TestNavigationObserver observer(web_contents());
EXPECT_TRUE(
ExecuteScript(web_contents(),
"location.href = '" + isolated_subdomain_url.spec() + "'"));
observer.Wait();
EXPECT_EQ(isolated_instance, web_contents()->GetSiteInstance());
}
} // namespace content