blob: ba767a3adf3b569fab2b60cc281dab132ef5b4a9 [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/network/public/cpp/document_isolation_policy.h"
#include "base/command_line.h"
#include "base/strings/escape.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind.h"
#include "base/test/gtest_util.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/with_feature_override.h"
#include "build/build_config.h"
#include "content/browser/process_lock.h"
#include "content/browser/renderer_host/navigation_request.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/renderer_host/render_process_host_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/common/content_navigation_policy.h"
#include "content/public/browser/site_isolation_policy.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/url_constants.h"
#include "content/public/test/back_forward_cache_util.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_content_browser_client.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/content_mock_cert_verifier.h"
#include "content/public/test/prerender_test_util.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/url_loader_interceptor.h"
#include "content/shell/browser/shell.h"
#include "content/test/content_browser_test_utils_internal.h"
#include "content/test/render_document_feature.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/default_handlers.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "net/test/embedded_test_server/request_handler_util.h"
#include "services/network/public/cpp/cross_origin_embedder_policy.h"
#include "services/network/public/cpp/features.h"
#include "testing/gmock/include/gmock/gmock.h"
using ::testing::HasSubstr;
namespace content {
namespace {
network::DocumentIsolationPolicy DipIsolateAndRequireCorp() {
network::DocumentIsolationPolicy dip;
dip.value =
network::mojom::DocumentIsolationPolicyValue::kIsolateAndRequireCorp;
return dip;
}
network::DocumentIsolationPolicy DipIsolateAndCredentialless() {
network::DocumentIsolationPolicy dip;
dip.value =
network::mojom::DocumentIsolationPolicyValue::kIsolateAndCredentialless;
return dip;
}
network::DocumentIsolationPolicy DipNone() {
return network::DocumentIsolationPolicy();
}
std::unique_ptr<net::test_server::HttpResponse> ServeDipOnSecondNavigation(
unsigned int& navigation_counter,
const net::test_server::HttpRequest& request) {
++navigation_counter;
auto http_response = std::make_unique<net::test_server::BasicHttpResponse>();
http_response->set_code(net::HttpStatusCode::HTTP_OK);
http_response->AddCustomHeader("Cache-Control", "no-store, must-revalidate");
if (navigation_counter > 1) {
http_response->AddCustomHeader("Document-Isolation-Policy",
"isolate-and-require-corp");
}
return http_response;
}
class DocumentIsolationPolicyBrowserTest
: public ContentBrowserTest,
public ::testing::WithParamInterface<
std::tuple<std::string, bool, bool>> {
public:
DocumentIsolationPolicyBrowserTest()
: prerender_helper_(base::BindRepeating(
&DocumentIsolationPolicyBrowserTest::prerender_web_contents,
base::Unretained(this))),
https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {
// Enable DIP and disable speculative RFH creation deferral. Currently,
// speculative RFH creation deferral makes it impossible to check that a
// speculative RFH is not created at the start of the navigation. This is
// needed to check that we do not create an extra process when navigating
// between two same-origin documents with DIP. Once RenderDocument ships, a
// speculative RFH will always be created, we'll then check that it is in
// the same SiteInstance as the current one. Then, we'll be able to
// re-enable the deferred speculative RFH creation, and just wait for the
// deferred creation of the speculative RenderDocument.
feature_list_.InitWithFeatures(
{network::features::kDocumentIsolationPolicy},
{features::kDeferSpeculativeRFHCreation, features::kSharedArrayBuffer});
// Enable RenderDocument:
InitAndEnableRenderDocumentFeature(&feature_list_for_render_document_,
std::get<0>(GetParam()));
// Enable BackForwardCache:
if (IsBackForwardCacheEnabled()) {
feature_list_for_back_forward_cache_.InitWithFeaturesAndParameters(
GetDefaultEnabledBackForwardCacheFeaturesForTesting(
/*ignore_outstanding_network_request=*/false),
GetDefaultDisabledBackForwardCacheFeaturesForTesting());
} else {
feature_list_for_back_forward_cache_.InitWithFeatures(
{}, {features::kBackForwardCache});
}
}
// Provides meaningful param names instead of /0, /1, ...
static std::string DescribeParams(
const testing::TestParamInfo<ParamType>& info) {
auto [render_document_level, enable_back_forward_cache, require_corp] =
info.param;
return base::StringPrintf(
"%s_%s_%s",
GetRenderDocumentLevelNameForTestParams(render_document_level).c_str(),
enable_back_forward_cache ? "BFCacheEnabled" : "BFCacheDisabled",
require_corp ? "RequireCorp" : "Credentialless");
}
bool IsBackForwardCacheEnabled() { return std::get<1>(GetParam()); }
net::EmbeddedTestServer* https_server() { return &https_server_; }
test::PrerenderTestHelper& prerender_helper() { return prerender_helper_; }
protected:
WebContentsImpl* web_contents() const {
return static_cast<WebContentsImpl*>(shell()->web_contents());
}
RenderFrameHostImpl* current_frame_host() {
return web_contents()->GetPrimaryMainFrame();
}
void SetUpOnMainThread() override {
ContentBrowserTest::SetUpOnMainThread();
mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
host_resolver()->AddRule("*", "127.0.0.1");
ASSERT_TRUE(embedded_test_server()->Start());
https_server()->ServeFilesFromSourceDirectory(GetTestDataFilePath());
SetupCrossSiteRedirector(https_server());
net::test_server::RegisterDefaultHandlers(&https_server_);
AddRedirectOnSecondNavigationHandler(&https_server_);
unsigned int navigation_counter = 0;
https_server_.RegisterDefaultHandler(base::BindRepeating(
&net::test_server::HandlePrefixedRequest,
"/serve-dip-on-second-navigation",
base::BindRepeating(&ServeDipOnSecondNavigation,
base::OwnedRef(navigation_counter))));
prerender_helper().RegisterServerRequestMonitor(&https_server_);
ASSERT_TRUE(https_server()->Start());
}
GURL GetDocumentIsolationPolicyURL(
const std::string& host,
const std::optional<std::string>& additional_header = std::nullopt) {
std::string headers = "/set-header?";
if (std::get<2>(GetParam())) {
// Isolate-and-require-corp version of the test.
headers += "document-isolation-policy: isolate-and-require-corp";
} else {
// Isolate-and-credentialless version of the test.
headers += "document-isolation-policy: isolate-and-credentialless";
}
if (additional_header.has_value()) {
headers += "&" + additional_header.value();
}
return https_server()->GetURL(host, headers);
}
network::DocumentIsolationPolicy GetDocumentIsolationPolicy() {
// Isolate-and-require-corp version of the test.
if (std::get<2>(GetParam())) {
return DipIsolateAndRequireCorp();
}
// Isolate-and-credentialless version of the test.
return DipIsolateAndCredentialless();
}
private:
void SetUpCommandLine(base::CommandLine* command_line) override {
mock_cert_verifier_.SetUpCommandLine(command_line);
// Enable strict SiteIsolation. Currently DIP only supports strict
// SiteIsolation so force it in tests.
IsolateAllSitesForTesting(command_line);
}
void SetUpInProcessBrowserTestFixture() override {
ContentBrowserTest::SetUpInProcessBrowserTestFixture();
mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
}
void TearDownInProcessBrowserTestFixture() override {
ContentBrowserTest::TearDownInProcessBrowserTestFixture();
mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
}
// Variation of web_contents(), that returns a WebContents* instead of a
// WebContentsImpl*, required to bind the prerender_helper_ in the
// constructor.
WebContents* prerender_web_contents() { return shell()->web_contents(); }
content::ContentMockCertVerifier mock_cert_verifier_;
// This needs to be before ScopedFeatureLists, because it contains one
// internally and the destruction order matters.
test::PrerenderTestHelper prerender_helper_;
base::test::ScopedFeatureList feature_list_;
base::test::ScopedFeatureList feature_list_for_render_document_;
base::test::ScopedFeatureList feature_list_for_back_forward_cache_;
net::EmbeddedTestServer https_server_;
};
class DocumentIsolationPolicyWithoutFeatureBrowserTest
: public ContentBrowserTest,
public ::testing::WithParamInterface<
std::tuple<std::string, bool, bool>> {
public:
DocumentIsolationPolicyWithoutFeatureBrowserTest()
: https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {
// Disable the DocumentIsolationPolicy feature.
feature_list_.InitWithFeatures(
{}, {network::features::kDocumentIsolationPolicy});
// Enable RenderDocument:
InitAndEnableRenderDocumentFeature(&feature_list_for_render_document_,
std::get<0>(GetParam()));
// Enable BackForwardCache:
if (IsBackForwardCacheEnabled()) {
feature_list_for_back_forward_cache_.InitWithFeaturesAndParameters(
GetDefaultEnabledBackForwardCacheFeaturesForTesting(
/*ignore_outstanding_network_request=*/false),
GetDefaultDisabledBackForwardCacheFeaturesForTesting());
} else {
feature_list_for_back_forward_cache_.InitWithFeatures(
{}, {features::kBackForwardCache});
}
}
// Provides meaningful param names instead of /0, /1, ...
static std::string DescribeParams(
const testing::TestParamInfo<ParamType>& info) {
auto [render_document_level, enable_back_forward_cache, require_corp] =
info.param;
return base::StringPrintf(
"%s_%s_%s",
GetRenderDocumentLevelNameForTestParams(render_document_level).c_str(),
enable_back_forward_cache ? "BFCacheEnabled" : "BFCacheDisabled",
require_corp ? "RequireCorp" : "Credentialless");
}
net::EmbeddedTestServer* https_server() { return &https_server_; }
protected:
RenderFrameHostImpl* current_frame_host() {
return static_cast<WebContentsImpl*>(shell()->web_contents())
->GetPrimaryMainFrame();
}
GURL GetDocumentIsolationPolicyURL(const std::string& host) {
// Isolate-and-require-corp version of the test.
if (std::get<2>(GetParam())) {
return https_server()->GetURL(
host,
"/set-header?document-isolation-policy: isolate-and-require-corp");
}
// Isolate-and-credentialless version of the test.
return https_server()->GetURL(
host,
"/set-header?document-isolation-policy: isolate-and-credentialless");
}
void SetUpOnMainThread() override {
ContentBrowserTest::SetUpOnMainThread();
mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
host_resolver()->AddRule("*", "127.0.0.1");
ASSERT_TRUE(embedded_test_server()->Start());
https_server()->ServeFilesFromSourceDirectory(GetTestDataFilePath());
SetupCrossSiteRedirector(https_server());
net::test_server::RegisterDefaultHandlers(&https_server_);
ASSERT_TRUE(https_server()->Start());
}
private:
void SetUpCommandLine(base::CommandLine* command_line) override {
ContentBrowserTest::SetUpCommandLine(command_line);
mock_cert_verifier_.SetUpCommandLine(command_line);
}
void SetUpInProcessBrowserTestFixture() override {
ContentBrowserTest::SetUpInProcessBrowserTestFixture();
mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
}
void TearDownInProcessBrowserTestFixture() override {
ContentBrowserTest::TearDownInProcessBrowserTestFixture();
mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
}
content::ContentMockCertVerifier mock_cert_verifier_;
base::test::ScopedFeatureList feature_list_;
base::test::ScopedFeatureList feature_list_for_render_document_;
base::test::ScopedFeatureList feature_list_for_back_forward_cache_;
net::EmbeddedTestServer https_server_;
};
class DocumentIsolationPolicyWithoutSiteIsolationBrowserTest
: public DocumentIsolationPolicyBrowserTest {
public:
DocumentIsolationPolicyWithoutSiteIsolationBrowserTest() = default;
private:
void SetUpCommandLine(base::CommandLine* command_line) override {
ContentBrowserTest::SetUpCommandLine(command_line);
mock_cert_verifier_.SetUpCommandLine(command_line);
// Disable SiteIsolation.
command_line->AppendSwitch(switches::kDisableSiteIsolation);
}
content::ContentMockCertVerifier mock_cert_verifier_;
};
} // namespace
// Checks that a Document-Isolation-Policy header is ignored if the
// DocumentIsolationPolicy feature flag is not enabled.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyWithoutFeatureBrowserTest,
DIP_Disabled) {
GURL starting_page = GetDocumentIsolationPolicyURL("a.test");
EXPECT_TRUE(NavigateToURL(shell(), starting_page));
EXPECT_EQ(current_frame_host()
->policy_container_host()
->policies()
.document_isolation_policy,
DipNone());
}
// Checks that a Document-Isolation-Policy header is ignored if SiteIsolation is
// not enabled.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyWithoutSiteIsolationBrowserTest,
DIP_Disabled) {
GURL starting_page = GetDocumentIsolationPolicyURL("a.test");
EXPECT_TRUE(NavigateToURL(shell(), starting_page));
EXPECT_EQ(current_frame_host()
->policy_container_host()
->policies()
.document_isolation_policy,
DipNone());
}
// Checks that DocumentIsolationPolicy is properly inherited from its creator by
// the about:blank document in a new popup.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest,
NewPopup_InheritsDIP) {
GURL starting_page = GetDocumentIsolationPolicyURL("a.test");
GURL no_dip(https_server()->GetURL("a.test", "/empty.html"));
EXPECT_TRUE(NavigateToURL(shell(), starting_page));
RenderFrameHostImpl* main_rfh = current_frame_host();
// Open a popup.
ShellAddedObserver shell_observer;
EXPECT_TRUE(ExecJs(main_rfh, "window.open('about:blank')"));
auto* popup_webcontents =
static_cast<WebContentsImpl*>(shell_observer.GetShell()->web_contents());
RenderFrameHostImpl* popup_rfh = popup_webcontents->GetPrimaryMainFrame();
EXPECT_EQ(
main_rfh->policy_container_host()->policies().document_isolation_policy,
GetDocumentIsolationPolicy());
EXPECT_EQ(
popup_rfh->policy_container_host()->policies().document_isolation_policy,
GetDocumentIsolationPolicy());
// Navigate the popup to a page without DIP. It should not longer have a
// DocumentIsolationPolicy.
ASSERT_TRUE(NavigateToURL(popup_webcontents, no_dip));
popup_rfh = popup_webcontents->GetPrimaryMainFrame();
EXPECT_EQ(
popup_rfh->policy_container_host()->policies().document_isolation_policy,
DipNone());
// Now add an iframe without DIP which will open a popup.
ASSERT_TRUE(ExecJs(main_rfh, R"(
const frame = document.createElement('iframe');
frame.src = '/empty.html';
document.body.appendChild(frame);
)"));
EXPECT_TRUE(WaitForLoadStop(web_contents()));
ShellAddedObserver shell_observer_2;
RenderFrameHostImpl* iframe_rfh = main_rfh->child_at(0)->current_frame_host();
EXPECT_TRUE(ExecJs(iframe_rfh, "window.open('about:blank')"));
RenderFrameHostImpl* popup_rfh_2 =
static_cast<WebContentsImpl*>(shell_observer_2.GetShell()->web_contents())
->GetPrimaryMainFrame();
EXPECT_EQ(
iframe_rfh->policy_container_host()->policies().document_isolation_policy,
DipNone());
EXPECT_EQ(popup_rfh_2->policy_container_host()
->policies()
.document_isolation_policy,
DipNone());
}
// Checks that a navigation to a Blob URL inherits the DocumentIsolationPolicy
// of its creator.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest, BlobInheritsDIP) {
GURL starting_page = GetDocumentIsolationPolicyURL("a.test");
EXPECT_TRUE(NavigateToURL(shell(), starting_page));
// Create and open blob.
ShellAddedObserver shell_observer;
ASSERT_TRUE(ExecJs(current_frame_host(), R"(
const blob = new Blob(['foo'], {type : 'text/html'});
const url = URL.createObjectURL(blob);
window.open(url);
)"));
EXPECT_TRUE(WaitForLoadStop(shell_observer.GetShell()->web_contents()));
RenderFrameHostImpl* popup_rfh =
static_cast<WebContentsImpl*>(shell_observer.GetShell()->web_contents())
->GetPrimaryMainFrame();
// DIP inherited from Blob creator
EXPECT_EQ(
popup_rfh->policy_container_host()->policies().document_isolation_policy,
GetDocumentIsolationPolicy());
}
// Checks that an about:blank iframe inherits its DocumentIsolationPolicy from
// its creator.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest,
AboutBlankInheritsDip) {
GURL starting_page = GetDocumentIsolationPolicyURL("a.test");
EXPECT_TRUE(NavigateToURL(shell(), starting_page));
EXPECT_EQ(current_frame_host()
->policy_container_host()
->policies()
.document_isolation_policy,
GetDocumentIsolationPolicy());
// Add an about:blank iframe.
EXPECT_TRUE(ExecJs(current_frame_host(),
"g_iframe = document.createElement('iframe');"
"g_iframe.src = 'about:blank';"
"document.body.appendChild(g_iframe);"));
WaitForLoadStop(web_contents());
RenderFrameHostImpl* iframe_rfh =
current_frame_host()->child_at(0)->current_frame_host();
// Document-Isolation-Policy should have been inherited.
EXPECT_EQ(
iframe_rfh->policy_container_host()->policies().document_isolation_policy,
GetDocumentIsolationPolicy());
}
// Checks that an iframe can enable DocumentIsolationPolicy even if its parent
// does not, and that it will be placed in an appropriate SiteInstance.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest, IframeCanSetDip) {
GURL starting_page(
https_server()->GetURL("a.com", "/cross_site_iframe_factory.html?a(b)"));
GURL iframe_navigation_url = GetDocumentIsolationPolicyURL("b.com");
EXPECT_TRUE(NavigateToURL(shell(), starting_page));
RenderFrameHostImpl* main_rfh = current_frame_host();
FrameTreeNode* iframe_ftn = main_rfh->child_at(0);
RenderFrameHostImpl* iframe_rfh = iframe_ftn->current_frame_host();
scoped_refptr<SiteInstanceImpl> non_dip_iframe_site_instance =
iframe_rfh->GetSiteInstance();
// The iframe should not have a DocumentIsolationPolicy.
EXPECT_EQ(
iframe_rfh->policy_container_host()->policies().document_isolation_policy,
DipNone());
// Navigate the iframe same-origin to a document with DIP header. The
// header should be taken into account.
EXPECT_TRUE(NavigateToURLFromRenderer(iframe_ftn, iframe_navigation_url));
iframe_rfh = iframe_ftn->current_frame_host();
// The navigation should have used a different SiteInstance from the one
// previously used as the DocumentIsolationPolicy do not match, even if the
// navigation is same-origin.
EXPECT_EQ(iframe_rfh->GetLastCommittedURL(), iframe_navigation_url);
EXPECT_NE(iframe_rfh->GetSiteInstance(), non_dip_iframe_site_instance);
EXPECT_EQ(
iframe_rfh->policy_container_host()->policies().document_isolation_policy,
GetDocumentIsolationPolicy());
}
// Checks that navigations are placed in the appropriate renderer process
// depending on their DocumentIsolationPolicy, even if the current renderer
// process is crashed or crashes during the navigation. In particular, this
// tests the navigation from a page without DIP to a page with DIP.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest,
NonDipPageCrashIntoDip) {
GURL non_dip_page(https_server()->GetURL("a.test", "/title1.html"));
GURL dip_page = GetDocumentIsolationPolicyURL("a.test");
// Test a crash before the navigation.
{
// Navigate to a non dip page.
EXPECT_TRUE(NavigateToURL(shell(), non_dip_page));
scoped_refptr<SiteInstance> initial_site_instance(
current_frame_host()->GetSiteInstance());
// Simulate the renderer process crashing.
RenderProcessHost* process = initial_site_instance->GetProcess();
ASSERT_TRUE(process);
std::unique_ptr<RenderProcessHostWatcher> crash_observer(
new RenderProcessHostWatcher(
process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT));
process->Shutdown(0);
crash_observer->Wait();
crash_observer.reset();
// Navigate to a DIP page.
EXPECT_TRUE(NavigateToURL(shell(), dip_page));
EXPECT_EQ(current_frame_host()
->policy_container_host()
->policies()
.document_isolation_policy,
GetDocumentIsolationPolicy());
}
// Test a crash during the navigation.
{
// Navigate to a non dip page.
EXPECT_TRUE(NavigateToURL(shell(), non_dip_page));
scoped_refptr<SiteInstance> initial_site_instance(
current_frame_host()->GetSiteInstance());
// Start navigating to a DIP page.
TestNavigationManager dip_navigation(web_contents(), dip_page);
shell()->LoadURL(dip_page);
EXPECT_TRUE(dip_navigation.WaitForRequestStart());
// Simulate the renderer process crashing.
RenderProcessHost* process = initial_site_instance->GetProcess();
ASSERT_TRUE(process);
std::unique_ptr<RenderProcessHostWatcher> crash_observer(
new RenderProcessHostWatcher(
process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT));
process->Shutdown(0);
crash_observer->Wait();
crash_observer.reset();
// Finish the navigation to the DIP page.
ASSERT_TRUE(dip_navigation.WaitForNavigationFinished());
// The navigation will fail if we create speculative RFH when the navigation
// started (instead of only when the response started), because the renderer
// process will crash and trigger deletion of the speculative RFH and the
// navigation using that speculative RFH. BFCache forces a BrowsingInstance
// swap (even in this same-site case), hence it also necessitates a
// speculative RFH.
// TODO(crbug.com/40261276): If the final RenderFrameHost picked for
// the navigation doesn't use the same process as the crashed process, we
// can crash the process after the final RenderFrameHost has been picked
// instead, and the navigation will commit normally.
if (ShouldCreateNewHostForAllFrames() || IsBackForwardCacheEnabled()) {
EXPECT_FALSE(dip_navigation.was_committed());
return;
}
EXPECT_TRUE(dip_navigation.was_successful());
EXPECT_EQ(current_frame_host()
->policy_container_host()
->policies()
.document_isolation_policy,
GetDocumentIsolationPolicy());
}
}
// Checks that navigations are placed in the appropriate renderer process
// depending on their DocumentIsolationPolicy, even if the current renderer
// process is crashed or crashes during the navigation. In particular, this
// tests the navigation from a page with DIP to a page without DIP.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest,
DipPageCrashIntoNonDip) {
GURL dip_page = GetDocumentIsolationPolicyURL("a.test");
GURL non_dip_page(https_server()->GetURL("a.test", "/empty.html"));
// Test a crash before the navigation.
{
// Navigate to a DIP page.
EXPECT_TRUE(NavigateToURL(shell(), dip_page));
scoped_refptr<SiteInstance> initial_site_instance(
current_frame_host()->GetSiteInstance());
// Simulate the renderer process crashing.
RenderProcessHost* process = initial_site_instance->GetProcess();
ASSERT_TRUE(process);
std::unique_ptr<RenderProcessHostWatcher> crash_observer(
new RenderProcessHostWatcher(
process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT));
process->Shutdown(0);
crash_observer->Wait();
crash_observer.reset();
// Navigate to a non DIP page.
EXPECT_TRUE(NavigateToURL(shell(), non_dip_page));
EXPECT_EQ(current_frame_host()
->policy_container_host()
->policies()
.document_isolation_policy,
DipNone());
}
// Test a crash during the navigation.
{
// Navigate to a DIP page.
EXPECT_TRUE(NavigateToURL(shell(), dip_page));
scoped_refptr<SiteInstance> initial_site_instance(
current_frame_host()->GetSiteInstance());
// Start navigating to a non DIP page.
TestNavigationManager non_dip_navigation(web_contents(), non_dip_page);
shell()->LoadURL(non_dip_page);
EXPECT_TRUE(non_dip_navigation.WaitForRequestStart());
// Simulate the renderer process crashing.
RenderProcessHost* process = initial_site_instance->GetProcess();
ASSERT_TRUE(process);
std::unique_ptr<RenderProcessHostWatcher> crash_observer(
new RenderProcessHostWatcher(
process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT));
process->Shutdown(0);
crash_observer->Wait();
crash_observer.reset();
// Finish the navigation to the non DIP page.
ASSERT_TRUE(non_dip_navigation.WaitForNavigationFinished());
// The navigation will fail if we create speculative RFH when the navigation
// started (instead of only when the response started), because the renderer
// process will crash and trigger deletion of the speculative RFH and the
// navigation using that speculative RFH. BFCache forces a BrowsingInstance
// swap (even in this same-site case), hence it also necessitates a
// speculative RFH.
// TODO(crbug.com/40261276): If the final RenderFrameHost picked for
// the navigation doesn't use the same process as the crashed process, we
// can crash the process after the final RenderFrameHost has been picked
// instead, and the navigation will commit normally.
if (ShouldCreateNewHostForAllFrames() || IsBackForwardCacheEnabled()) {
EXPECT_FALSE(non_dip_navigation.was_committed());
EXPECT_EQ(current_frame_host()
->policy_container_host()
->policies()
.document_isolation_policy,
GetDocumentIsolationPolicy());
return;
}
EXPECT_TRUE(non_dip_navigation.was_successful());
EXPECT_EQ(current_frame_host()
->policy_container_host()
->policies()
.document_isolation_policy,
DipNone());
}
// Test a crash during the navigation commit.
{
// Navigate to a DIP page.
EXPECT_TRUE(NavigateToURL(shell(), dip_page));
scoped_refptr<SiteInstance> initial_site_instance(
current_frame_host()->GetSiteInstance());
// Start navigating to a non DIP page.
TestNavigationManager non_dip_navigation(web_contents(), non_dip_page);
shell()->LoadURL(non_dip_page);
EXPECT_TRUE(non_dip_navigation.WaitForResponse());
// Simulate the renderer process crashing.
RenderProcessHost* process = initial_site_instance->GetProcess();
ASSERT_TRUE(process);
std::unique_ptr<RenderProcessHostWatcher> crash_observer(
new RenderProcessHostWatcher(
process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT));
process->Shutdown(0);
crash_observer->Wait();
crash_observer.reset();
// Finish the navigation to the non DIP page.
ASSERT_TRUE(non_dip_navigation.WaitForNavigationFinished());
// The navigation will fail if we create speculative RFH when the navigation
// started (instead of only when the response started), because the renderer
// process will crash and trigger deletion of the speculative RFH and the
// navigation using that speculative RFH. BFCache forces a BrowsingInstance
// swap (even in this same-site case), hence it also necessitates a
// speculative RFH.
// TODO(crbug.com/40261276): If the final RenderFrameHost picked for the
// navigation doesn't use the same process the navigation should not stop
// but go on and commit normally
if (ShouldCreateNewHostForAllFrames() || IsBackForwardCacheEnabled()) {
EXPECT_FALSE(non_dip_navigation.was_committed());
EXPECT_EQ(current_frame_host()
->policy_container_host()
->policies()
.document_isolation_policy,
GetDocumentIsolationPolicy());
return;
}
EXPECT_TRUE(non_dip_navigation.was_successful());
EXPECT_EQ(current_frame_host()
->policy_container_host()
->policies()
.document_isolation_policy,
DipNone());
}
}
// Checks that navigations are placed in the appropriate renderer process
// depending on their DocumentIsolationPolicy, even if the current renderer
// process is crashed or crashes during the navigation. In particular, this
// tests the navigation between two pages with DIP, including a reload of a
// crashed page.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest,
DipPageCrashIntoDip) {
GURL dip_page = GetDocumentIsolationPolicyURL("a.test");
// Test a crash before the navigation.
{
// Navigate to a DIP page.
EXPECT_TRUE(NavigateToURL(shell(), dip_page));
scoped_refptr<SiteInstance> initial_site_instance(
current_frame_host()->GetSiteInstance());
EXPECT_EQ(current_frame_host()
->policy_container_host()
->policies()
.document_isolation_policy,
GetDocumentIsolationPolicy());
// Simulate the renderer process crashing.
RenderProcessHost* process = initial_site_instance->GetProcess();
ASSERT_TRUE(process);
std::unique_ptr<RenderProcessHostWatcher> crash_observer(
new RenderProcessHostWatcher(
process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT));
process->Shutdown(0);
crash_observer->Wait();
crash_observer.reset();
// Reload the DIP page.
ReloadBlockUntilNavigationsComplete(shell(), 1);
EXPECT_TRUE(current_frame_host()->GetSiteInstance()->IsRelatedSiteInstance(
initial_site_instance.get()));
EXPECT_EQ(current_frame_host()
->policy_container_host()
->policies()
.document_isolation_policy,
GetDocumentIsolationPolicy());
}
// Test a crash during the navigation.
{
// Navigate to a DIP page.
EXPECT_TRUE(NavigateToURL(shell(), dip_page));
scoped_refptr<SiteInstance> initial_site_instance(
current_frame_host()->GetSiteInstance());
// Start navigating to a DIP page.
TestNavigationManager dip_navigation(web_contents(), dip_page);
shell()->LoadURL(dip_page);
EXPECT_TRUE(dip_navigation.WaitForRequestStart());
// Simulate the renderer process crashing.
RenderProcessHost* process = initial_site_instance->GetProcess();
ASSERT_TRUE(process);
std::unique_ptr<RenderProcessHostWatcher> crash_observer(
new RenderProcessHostWatcher(
process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT));
process->Shutdown(0);
crash_observer->Wait();
crash_observer.reset();
// Finish the navigation to the DIP page.
ASSERT_TRUE(dip_navigation.WaitForNavigationFinished());
EXPECT_EQ(current_frame_host()
->policy_container_host()
->policies()
.document_isolation_policy,
GetDocumentIsolationPolicy());
}
}
// Checks that a navigation to a document with DocumentIsolationPolicy will be
// placed in a separate process even if the process limit has been reached.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest,
IsolateInNewProcessDespiteLimitReached) {
// Set a process limit of 1 for testing.
RenderProcessHostImpl::SetMaxRendererProcessCount(1);
// Navigate to a starting page.
GURL starting_page(https_server()->GetURL("a.test", "/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), starting_page));
scoped_refptr<SiteInstance> initial_site_instance(
current_frame_host()->GetSiteInstance());
// Open a popup with DocumentIsolationPolicy set.
GURL url_openee = GetDocumentIsolationPolicyURL("a.test");
auto* popup_webcontents =
OpenPopup(current_frame_host(), url_openee, "popup")->web_contents();
EXPECT_TRUE(WaitForLoadStop(popup_webcontents));
// The page and its popup should be in different processes even though the
// process limit was reached.
EXPECT_NE(initial_site_instance,
popup_webcontents->GetPrimaryMainFrame()->GetSiteInstance());
EXPECT_NE(current_frame_host()->GetProcess(),
popup_webcontents->GetPrimaryMainFrame()->GetProcess());
}
// Checks that a process hosting a document with DocumentIsolationPolicy is not
// reused for documents without DocumentIsolationPolicy, even if the process
// limit has been reached.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest,
NoProcessReuseForDIPProcesses) {
// Set a process limit of 1 for testing.
RenderProcessHostImpl::SetMaxRendererProcessCount(1);
// Navigate to a starting page with DocumentIsolationPolicy set.
GURL starting_page = GetDocumentIsolationPolicyURL("a.test");
EXPECT_TRUE(NavigateToURL(shell(), starting_page));
scoped_refptr<SiteInstance> initial_site_instance(
current_frame_host()->GetSiteInstance());
// Create a new shell.
Shell* new_shell = CreateBrowser();
// Navigate it to a same-origin page without DIP.
GURL non_dip_url = https_server()->GetURL("a.test", "/title1.html");
EXPECT_TRUE(NavigateToURL(new_shell, non_dip_url));
// The original page and the page in the new shell should be in different
// processes even though the process limit was reached.
EXPECT_NE(
initial_site_instance,
new_shell->web_contents()->GetPrimaryMainFrame()->GetSiteInstance());
EXPECT_NE(current_frame_host()->GetProcess(),
new_shell->web_contents()->GetPrimaryMainFrame()->GetProcess());
}
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest,
SpeculativeRfhsAndDip) {
GURL non_dip_page(https_server()->GetURL("a.test", "/title1.html"));
GURL dip_page = GetDocumentIsolationPolicyURL("a.test");
// Non-DIP into DIP.
{
SCOPED_TRACE("Non-DIP to DIP");
// Start on a non DIP page.
EXPECT_TRUE(NavigateToURL(shell(), non_dip_page));
scoped_refptr<SiteInstance> initial_site_instance(
current_frame_host()->GetSiteInstance());
// Navigate to a DIP page.
TestNavigationManager dip_navigation(web_contents(), dip_page);
shell()->LoadURL(dip_page);
EXPECT_TRUE(dip_navigation.WaitForRequestStart());
auto* speculative_rfh = web_contents()
->GetPrimaryFrameTree()
.root()
->render_manager()
->speculative_frame_host();
if (CanSameSiteMainFrameNavigationsChangeRenderFrameHosts()) {
// When ProactivelySwapBrowsingInstance or RenderDocument is enabled on
// same-site main-frame navigations, the navigation will result in a new
// RFH, so it will create a pending RFH.
EXPECT_TRUE(speculative_rfh);
} else {
EXPECT_FALSE(speculative_rfh);
}
ASSERT_TRUE(dip_navigation.WaitForNavigationFinished());
// Even if the origin of the documents is the same, because their
// DocumentIsolationPolicies do not match, the navigation is classified as a
// cross-site browser-initiated request and the browser triggers a
// speculative BrowsingInstance swap.
EXPECT_FALSE(current_frame_host()->GetSiteInstance()->IsRelatedSiteInstance(
initial_site_instance.get()));
EXPECT_EQ(current_frame_host()
->policy_container_host()
->policies()
.document_isolation_policy,
GetDocumentIsolationPolicy());
}
// DIP into non-DIP.
{
SCOPED_TRACE("DIP to non-DIP");
// Start on a DIP page.
EXPECT_TRUE(NavigateToURL(shell(), dip_page));
scoped_refptr<SiteInstance> initial_site_instance(
current_frame_host()->GetSiteInstance());
// Navigate to a non DIP page.
TestNavigationManager non_dip_navigation(web_contents(), non_dip_page);
shell()->LoadURL(non_dip_page);
EXPECT_TRUE(non_dip_navigation.WaitForRequestStart());
auto* speculative_rfh = web_contents()
->GetPrimaryFrameTree()
.root()
->render_manager()
->speculative_frame_host();
if (CanSameSiteMainFrameNavigationsChangeRenderFrameHosts()) {
// When ProactivelySwapBrowsingInstance or RenderDocument is enabled on
// same-site main-frame navigations, the navigation will result in a new
// RFH, so it will create a pending RFH.
EXPECT_TRUE(speculative_rfh);
} else {
EXPECT_FALSE(speculative_rfh);
}
ASSERT_TRUE(non_dip_navigation.WaitForNavigationFinished());
// Even if the origin of the documents is the same, because their
// DocumentIsolationPolicies do not match, the navigation is classified as a
// cross-site browser-initiated request and the browser triggers a
// speculative BrowsingInstance swap.
EXPECT_FALSE(current_frame_host()->GetSiteInstance()->IsRelatedSiteInstance(
initial_site_instance.get()));
EXPECT_EQ(current_frame_host()
->policy_container_host()
->policies()
.document_isolation_policy,
DipNone());
}
// DIP into DIP.
{
SCOPED_TRACE("DIP to DIP");
// Start on a DIP page.
EXPECT_TRUE(NavigateToURL(shell(), dip_page));
scoped_refptr<SiteInstance> initial_site_instance(
current_frame_host()->GetSiteInstance());
// Navigate to a DIP page.
TestNavigationManager dip_navigation(web_contents(), dip_page);
shell()->LoadURL(dip_page);
EXPECT_TRUE(dip_navigation.WaitForRequestStart());
auto* speculative_rfh = web_contents()
->GetPrimaryFrameTree()
.root()
->render_manager()
->speculative_frame_host();
if (WillSameSiteNavigationChangeRenderFrameHosts(true, true)) {
// When RenderDocument is enabled, a speculative RFH will always be
// created. ProactivelySwapBrowsingInstance will not create one in this
// case because we are navigating to the same URL as the existing
// document.
EXPECT_TRUE(speculative_rfh);
} else {
EXPECT_FALSE(speculative_rfh);
}
ASSERT_TRUE(dip_navigation.WaitForNavigationFinished());
EXPECT_TRUE(current_frame_host()->GetSiteInstance()->IsRelatedSiteInstance(
initial_site_instance.get()));
EXPECT_EQ(current_frame_host()
->policy_container_host()
->policies()
.document_isolation_policy,
GetDocumentIsolationPolicy());
}
}
// A test to make sure that loading a page with DIP creates an origin-keyed
// AgentClusterKey in SiteInstance's SiteInfo.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest, DipOriginKeyed) {
GURL isolated_page = GetDocumentIsolationPolicyURL("a.test");
EXPECT_TRUE(NavigateToURL(shell(), isolated_page));
SiteInstanceImpl* current_si = current_frame_host()->GetSiteInstance();
EXPECT_TRUE(current_si->IsCrossOriginIsolated());
EXPECT_TRUE(current_si->GetSiteInfo().agent_cluster_key().IsOriginKeyed());
// While the AgentClusterKey is origin-keyed, this should not impact the OAC
// status of the SiteInfo.
if (SiteIsolationPolicy::AreOriginKeyedProcessesEnabledByDefault()) {
EXPECT_EQ(AgentClusterKey::OACStatus::kOriginKeyedByDefault,
current_si->GetSiteInfo().oac_status());
} else {
EXPECT_EQ(AgentClusterKey::OACStatus::kSiteKeyedByDefault,
current_si->GetSiteInfo().oac_status());
}
}
// A test to make sure that loading a page with DIP creates a SiteInfo with an
// AgentClusterKey that has the origin of the DIP document, not its SiteURL.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest,
DipAgentClusterKeyUsesOrigin) {
GURL isolated_page = GetDocumentIsolationPolicyURL("a.b.test");
url::Origin origin = url::Origin::Create(isolated_page);
EXPECT_TRUE(NavigateToURL(shell(), isolated_page));
SiteInstanceImpl* current_si = current_frame_host()->GetSiteInstance();
EXPECT_TRUE(current_si->IsCrossOriginIsolated());
EXPECT_TRUE(current_si->GetSiteInfo().agent_cluster_key().IsOriginKeyed());
EXPECT_EQ(origin, current_si->GetSiteInfo().agent_cluster_key().GetOrigin());
}
// Tests that main frame navigations are correctly assigned cross-origin
// isolated SiteInstances based on their DocumentIsolationPolicy.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest,
CrossOriginIsolatedSiteInstance_MainFrame) {
GURL isolated_page = GetDocumentIsolationPolicyURL("a.test");
GURL isolated_page_b = GetDocumentIsolationPolicyURL("cdn.a.test");
GURL non_isolated_page(https_server()->GetURL("a.test", "/title1.html"));
// Navigation from/to cross-origin isolated pages.
// Initial non cross-origin isolated page.
{
EXPECT_TRUE(NavigateToURL(shell(), non_isolated_page));
SiteInstanceImpl* current_si = current_frame_host()->GetSiteInstance();
EXPECT_FALSE(current_si->IsCrossOriginIsolated());
}
// Navigation to a cross-origin isolated page.
{
scoped_refptr<SiteInstanceImpl> previous_si =
current_frame_host()->GetSiteInstance();
EXPECT_TRUE(NavigateToURL(shell(), isolated_page));
SiteInstanceImpl* current_si = current_frame_host()->GetSiteInstance();
EXPECT_TRUE(current_si->IsCrossOriginIsolated());
EXPECT_NE(current_si->GetProcess(),
previous_si->GetOrCreateProcessForTesting());
// The navigation triggers a speculative BrowsingInstance swap because it is
// browser-initiated and end up being cross-site due to the DIP mismatch.
EXPECT_FALSE(current_si->IsRelatedSiteInstance(previous_si.get()));
}
// Navigation to a non cross-origin isolated page.
{
scoped_refptr<SiteInstanceImpl> previous_si =
current_frame_host()->GetSiteInstance();
EXPECT_TRUE(NavigateToURL(shell(), non_isolated_page));
SiteInstanceImpl* current_si = current_frame_host()->GetSiteInstance();
EXPECT_FALSE(current_si->IsCrossOriginIsolated());
EXPECT_NE(current_si->GetProcess(),
previous_si->GetOrCreateProcessForTesting());
// The navigation triggers a speculative BrowsingInstance swap because it is
// browser-initiated and end up being cross-site due to the DIP mismatch.
EXPECT_FALSE(current_si->IsRelatedSiteInstance(previous_si.get()));
}
// Back navigation from a a non cross-origin isolated page to a cross-origin
// isolated page.
{
scoped_refptr<SiteInstanceImpl> previous_si =
current_frame_host()->GetSiteInstance();
web_contents()->GetController().GoBack();
EXPECT_TRUE(WaitForLoadStop(web_contents()));
SiteInstanceImpl* current_si = current_frame_host()->GetSiteInstance();
EXPECT_TRUE(current_si->IsCrossOriginIsolated());
EXPECT_NE(current_si->GetProcess(),
previous_si->GetOrCreateProcessForTesting());
// The navigation triggers a speculative BrowsingInstance swap because it is
// browser-initiated and end up being cross-site due to the DIP mismatch.
EXPECT_FALSE(current_si->IsRelatedSiteInstance(previous_si.get()));
}
// Back navigation to the non cross-origin isolated initial page.
{
scoped_refptr<SiteInstanceImpl> previous_si =
current_frame_host()->GetSiteInstance();
web_contents()->GetController().GoBack();
EXPECT_TRUE(WaitForLoadStop(web_contents()));
SiteInstanceImpl* current_si = current_frame_host()->GetSiteInstance();
EXPECT_FALSE(current_si->IsCrossOriginIsolated());
EXPECT_NE(current_si->GetProcess(),
previous_si->GetOrCreateProcessForTesting());
// The navigation triggers a speculative BrowsingInstance swap because it is
// browser-initiated and end up being cross-site due to the DIP mismatch.
EXPECT_FALSE(current_si->IsRelatedSiteInstance(previous_si.get()));
}
// Cross origin navigation in between two cross-origin isolated pages.
{
EXPECT_TRUE(NavigateToURL(shell(), isolated_page));
scoped_refptr<SiteInstanceImpl> site_instance_1 =
current_frame_host()->GetSiteInstance();
EXPECT_TRUE(NavigateToURL(shell(), isolated_page_b));
SiteInstanceImpl* site_instance_2 = current_frame_host()->GetSiteInstance();
EXPECT_TRUE(site_instance_1->IsCrossOriginIsolated());
EXPECT_TRUE(site_instance_2->IsCrossOriginIsolated());
EXPECT_NE(site_instance_1->GetOrCreateProcessForTesting(),
site_instance_2->GetProcess());
// The navigation triggers a speculative BrowsingInstance swap because it is
// browser-initiated and cross-site.
EXPECT_FALSE(site_instance_1->IsRelatedSiteInstance(site_instance_2));
}
}
// Tests that renderer-initiated main frames navigations are assigned a
// cross-origin isolated SiteInstance based on their DocumentIsolationPolicy.
IN_PROC_BROWSER_TEST_P(
DocumentIsolationPolicyBrowserTest,
CrossOriginIsolatedSiteInstance_MainFrameRendererInitiated) {
GURL isolated_page = GetDocumentIsolationPolicyURL("a.test");
GURL isolated_page_b = GetDocumentIsolationPolicyURL("cdn.a.test");
GURL non_isolated_page(https_server()->GetURL("a.test", "/title1.html"));
// Navigation from/to cross-origin isolated pages.
// Initial non cross-origin isolated page.
{
EXPECT_TRUE(NavigateToURL(shell(), non_isolated_page));
SiteInstanceImpl* current_si = current_frame_host()->GetSiteInstance();
EXPECT_FALSE(current_si->IsCrossOriginIsolated());
}
// Navigation to a cross-origin isolated page.
{
scoped_refptr<SiteInstanceImpl> previous_si =
current_frame_host()->GetSiteInstance();
EXPECT_TRUE(NavigateToURLFromRenderer(shell(), isolated_page));
SiteInstanceImpl* current_si = current_frame_host()->GetSiteInstance();
EXPECT_TRUE(current_si->IsCrossOriginIsolated());
EXPECT_NE(current_si->GetProcess(),
previous_si->GetOrCreateProcessForTesting());
// When BfCache is enabled, a pro-active BrowsingInstance swap happens.
if (IsBackForwardCacheEnabled()) {
EXPECT_FALSE(current_si->IsRelatedSiteInstance(previous_si.get()));
} else {
EXPECT_TRUE(current_si->IsRelatedSiteInstance(previous_si.get()));
}
}
// Navigation to the same cross-origin isolated page.
{
scoped_refptr<SiteInstanceImpl> previous_si =
current_frame_host()->GetSiteInstance();
EXPECT_TRUE(NavigateToURLFromRenderer(shell(), isolated_page));
SiteInstanceImpl* current_si = current_frame_host()->GetSiteInstance();
EXPECT_TRUE(current_si->IsCrossOriginIsolated());
EXPECT_EQ(current_si, previous_si);
}
// Navigation to a non cross-origin isolated page.
{
scoped_refptr<SiteInstanceImpl> previous_si =
current_frame_host()->GetSiteInstance();
EXPECT_TRUE(NavigateToURLFromRenderer(shell(), non_isolated_page));
SiteInstanceImpl* current_si = current_frame_host()->GetSiteInstance();
EXPECT_FALSE(current_si->IsCrossOriginIsolated());
EXPECT_NE(current_si->GetProcess(),
previous_si->GetOrCreateProcessForTesting());
// When BfCache is enabled, a pro-active BrowsingInstance swap happens.
if (IsBackForwardCacheEnabled()) {
EXPECT_FALSE(current_si->IsRelatedSiteInstance(previous_si.get()));
} else {
EXPECT_TRUE(current_si->IsRelatedSiteInstance(previous_si.get()));
}
}
// Navigate back to a cross-origin isolated page.
{
scoped_refptr<SiteInstanceImpl> previous_si =
current_frame_host()->GetSiteInstance();
web_contents()->GetController().GoBack();
ASSERT_TRUE(WaitForLoadStop(web_contents()));
SiteInstanceImpl* current_si = current_frame_host()->GetSiteInstance();
EXPECT_TRUE(current_si->IsCrossOriginIsolated());
EXPECT_NE(current_si->GetProcess(),
previous_si->GetOrCreateProcessForTesting());
// When BfCache is enabled, a pro-active BrowsingInstance swap happens.
if (IsBackForwardCacheEnabled()) {
EXPECT_FALSE(current_si->IsRelatedSiteInstance(previous_si.get()));
} else {
EXPECT_TRUE(current_si->IsRelatedSiteInstance(previous_si.get()));
}
}
// Cross origin navigation in between two cross-origin isolated pages.
{
EXPECT_TRUE(NavigateToURLFromRenderer(shell(), isolated_page));
scoped_refptr<SiteInstanceImpl> site_instance_1 =
current_frame_host()->GetSiteInstance();
EXPECT_TRUE(NavigateToURLFromRenderer(shell(), isolated_page_b));
SiteInstanceImpl* site_instance_2 = current_frame_host()->GetSiteInstance();
EXPECT_TRUE(site_instance_1->IsCrossOriginIsolated());
EXPECT_TRUE(site_instance_2->IsCrossOriginIsolated());
EXPECT_NE(site_instance_1->GetOrCreateProcessForTesting(),
site_instance_2->GetProcess());
// When BfCache is enabled, a pro-active BrowsingInstance swap happens.
if (IsBackForwardCacheEnabled()) {
EXPECT_FALSE(
site_instance_2->IsRelatedSiteInstance(site_instance_1.get()));
} else {
EXPECT_TRUE(
site_instance_2->IsRelatedSiteInstance(site_instance_1.get()));
}
}
}
// Tests that iframe navigations are assigned a cross-origin isolated
// SiteInstance based on their DocumentIsolationPolicy.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest,
CrossOriginIsolatedSiteInstance_IFrame) {
GURL isolated_page = GetDocumentIsolationPolicyURL("a.test");
GURL isolated_page_b = GetDocumentIsolationPolicyURL("cdn.a.test");
GURL non_isolated_page(https_server()->GetURL("a.test", "/title1.html"));
// Initial cross-origin isolated page.
EXPECT_TRUE(NavigateToURL(shell(), isolated_page));
SiteInstanceImpl* main_si = current_frame_host()->GetSiteInstance();
EXPECT_TRUE(main_si->IsCrossOriginIsolated());
// Same origin cross-origin isolated iframe.
TestNavigationManager coi_iframe_navigation(web_contents(), isolated_page);
EXPECT_TRUE(
ExecJs(web_contents(),
JsReplace("const iframe = document.createElement('iframe'); "
"iframe.src = $1; "
"document.body.appendChild(iframe);",
isolated_page)));
ASSERT_TRUE(coi_iframe_navigation.WaitForNavigationFinished());
EXPECT_TRUE(coi_iframe_navigation.was_successful());
RenderFrameHostImpl* coi_iframe_rfh =
current_frame_host()->child_at(0)->current_frame_host();
SiteInstanceImpl* coi_iframe_si = coi_iframe_rfh->GetSiteInstance();
EXPECT_EQ(coi_iframe_si, main_si);
// Same origin non cross-origin isolated iframe.
TestNavigationManager non_coi_iframe_navigation(web_contents(),
non_isolated_page);
EXPECT_TRUE(
ExecJs(web_contents(),
JsReplace("const iframe = document.createElement('iframe'); "
"iframe.src = $1; "
"document.body.appendChild(iframe);",
non_isolated_page)));
ASSERT_TRUE(non_coi_iframe_navigation.WaitForNavigationFinished());
EXPECT_TRUE(non_coi_iframe_navigation.was_successful());
RenderFrameHostImpl* non_coi_iframe_rfh =
current_frame_host()->child_at(1)->current_frame_host();
SiteInstanceImpl* non_coi_iframe_si = non_coi_iframe_rfh->GetSiteInstance();
EXPECT_FALSE(non_coi_iframe_si->IsCrossOriginIsolated());
EXPECT_TRUE(non_coi_iframe_si->IsRelatedSiteInstance(main_si));
EXPECT_NE(non_coi_iframe_si, main_si);
EXPECT_NE(non_coi_iframe_si->GetProcess(), main_si->GetProcess());
EXPECT_NE(non_coi_iframe_si, coi_iframe_si);
EXPECT_NE(non_coi_iframe_si->GetProcess(), coi_iframe_si->GetProcess());
// Cross origin iframe.
TestNavigationManager cross_origin_iframe_navigation(web_contents(),
isolated_page_b);
EXPECT_TRUE(
ExecJs(web_contents(),
JsReplace("const iframe = document.createElement('iframe'); "
"iframe.src = $1; "
"document.body.appendChild(iframe);",
isolated_page_b)));
ASSERT_TRUE(cross_origin_iframe_navigation.WaitForNavigationFinished());
EXPECT_TRUE(cross_origin_iframe_navigation.was_successful());
RenderFrameHostImpl* iframe_rfh =
current_frame_host()->child_at(2)->current_frame_host();
SiteInstanceImpl* cross_origin_iframe_si = iframe_rfh->GetSiteInstance();
EXPECT_TRUE(cross_origin_iframe_si->IsCrossOriginIsolated());
EXPECT_TRUE(cross_origin_iframe_si->IsRelatedSiteInstance(main_si));
EXPECT_NE(cross_origin_iframe_si, main_si);
EXPECT_NE(cross_origin_iframe_si->GetProcess(), main_si->GetProcess());
EXPECT_NE(cross_origin_iframe_si, coi_iframe_si);
EXPECT_NE(cross_origin_iframe_si->GetProcess(), coi_iframe_si->GetProcess());
EXPECT_NE(cross_origin_iframe_si, non_coi_iframe_si);
EXPECT_NE(cross_origin_iframe_si->GetProcess(),
non_coi_iframe_si->GetProcess());
// Navigate to a non cross-origin isolated page with a cross-origin isolated
// iframe.
{
scoped_refptr<SiteInstanceImpl> previous_si =
current_frame_host()->GetSiteInstance();
EXPECT_TRUE(NavigateToURLFromRenderer(shell(), non_isolated_page));
main_si = current_frame_host()->GetSiteInstance();
EXPECT_FALSE(main_si->IsCrossOriginIsolated());
EXPECT_NE(main_si->GetProcess(),
previous_si->GetOrCreateProcessForTesting());
// When BfCache is enabled, a pro-active BrowsingInstance swap happens.
if (IsBackForwardCacheEnabled()) {
EXPECT_FALSE(main_si->IsRelatedSiteInstance(previous_si.get()));
} else {
EXPECT_TRUE(main_si->IsRelatedSiteInstance(previous_si.get()));
}
TestNavigationManager same_origin_iframe_navigation(web_contents(),
isolated_page);
EXPECT_TRUE(
ExecJs(web_contents(),
JsReplace("const iframe = document.createElement('iframe'); "
"iframe.src = $1; "
"document.body.appendChild(iframe);",
isolated_page)));
ASSERT_TRUE(same_origin_iframe_navigation.WaitForNavigationFinished());
EXPECT_TRUE(same_origin_iframe_navigation.was_successful());
iframe_rfh = current_frame_host()->child_at(0)->current_frame_host();
SiteInstanceImpl* iframe_si = iframe_rfh->GetSiteInstance();
EXPECT_TRUE(iframe_si->IsCrossOriginIsolated());
EXPECT_TRUE(iframe_si->IsRelatedSiteInstance(main_si));
EXPECT_NE(iframe_si, main_si);
EXPECT_NE(iframe_si->GetProcess(), main_si->GetProcess());
}
}
// Tests that navigations in popups are correctly assigned a cross-origin
// isolated SiteInstance based on their DocumentIsolationPolicy.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest,
CrossOriginIsolatedSiteInstance_Popup) {
GURL isolated_page = GetDocumentIsolationPolicyURL("a.test");
GURL isolated_page_b = GetDocumentIsolationPolicyURL("cdn.a.test");
GURL non_isolated_page(
embedded_test_server()->GetURL("a.test", "/title1.html"));
// Initial cross-origin isolated page.
EXPECT_TRUE(NavigateToURL(shell(), isolated_page));
SiteInstanceImpl* main_si = current_frame_host()->GetSiteInstance();
EXPECT_TRUE(main_si->IsCrossOriginIsolated());
// Open a non isolated popup.
{
RenderFrameHostImpl* popup_rfh =
static_cast<WebContentsImpl*>(
OpenPopup(current_frame_host(), non_isolated_page, "")
->web_contents())
->GetPrimaryMainFrame();
EXPECT_FALSE(popup_rfh->GetSiteInstance()->IsCrossOriginIsolated());
EXPECT_TRUE(popup_rfh->GetSiteInstance()->IsRelatedSiteInstance(
current_frame_host()->GetSiteInstance()));
}
// Open an isolated popup.
{
RenderFrameHostImpl* popup_rfh =
static_cast<WebContentsImpl*>(
OpenPopup(current_frame_host(), isolated_page, "")->web_contents())
->GetPrimaryMainFrame();
EXPECT_TRUE(popup_rfh->GetSiteInstance()->IsCrossOriginIsolated());
EXPECT_EQ(popup_rfh->GetSiteInstance(),
current_frame_host()->GetSiteInstance());
}
// Open an isolated popup, but cross-origin.
{
RenderFrameHostImpl* popup_rfh =
static_cast<WebContentsImpl*>(
OpenPopup(current_frame_host(), isolated_page_b, "")
->web_contents())
->GetPrimaryMainFrame();
EXPECT_TRUE(popup_rfh->GetSiteInstance()->IsCrossOriginIsolated());
EXPECT_TRUE(popup_rfh->GetSiteInstance()->IsRelatedSiteInstance(
current_frame_host()->GetSiteInstance()));
EXPECT_NE(popup_rfh->GetSiteInstance()->GetProcess(),
current_frame_host()->GetSiteInstance()->GetProcess());
}
}
// Tests that navigations involving error pages are correctly assigned a
// cross-origin isolated SiteInstance based on their DocumentIsolationPolicy
// status.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest,
CrossOriginIsolatedSiteInstance_ErrorPage) {
GURL isolated_page = GetDocumentIsolationPolicyURL(
"a.test", "Cross-Origin-Embedder-Policy: require-corp");
GURL non_coep_page(https_server()->GetURL("b.test",
"/set-header?"
"Access-Control-Allow-Origin: *"));
GURL invalid_url(
https_server()->GetURL("a.test", "/this_page_does_not_exist.html"));
GURL error_url(https_server()->GetURL("a.test", "/page404.html"));
GURL non_isolated_page(
https_server()->GetURL("a.test",
"/set-header?"
"Cross-Origin-Embedder-Policy: require-corp"));
// Initial cross-origin isolated page.
EXPECT_TRUE(NavigateToURL(shell(), isolated_page));
SiteInstanceImpl* main_si = current_frame_host()->GetSiteInstance();
EXPECT_TRUE(main_si->IsCrossOriginIsolated());
// Iframe.
{
TestNavigationManager iframe_navigation(web_contents(), invalid_url);
EXPECT_TRUE(
ExecJs(web_contents(),
JsReplace("const iframe = document.createElement('iframe'); "
"iframe.src = $1; "
"document.body.appendChild(iframe);",
invalid_url)));
ASSERT_TRUE(iframe_navigation.WaitForNavigationFinished());
EXPECT_FALSE(iframe_navigation.was_successful());
RenderFrameHostImpl* iframe_rfh =
current_frame_host()->child_at(0)->current_frame_host();
SiteInstanceImpl* iframe_si = iframe_rfh->GetSiteInstance();
// The load of the document with 404 status code is blocked by COEP.
// An error page is expected in lieu of that document.
EXPECT_EQ(GURL(kUnreachableWebDataURL),
EvalJs(iframe_rfh, "document.location.href;"));
EXPECT_TRUE(IsExpectedSubframeErrorTransition(main_si, iframe_si));
EXPECT_FALSE(iframe_si->IsCrossOriginIsolated());
EXPECT_NE(iframe_si, main_si);
}
// Iframe with a body added to the HTTP 404.
{
TestNavigationManager iframe_navigation(web_contents(), error_url);
EXPECT_TRUE(
ExecJs(web_contents(),
JsReplace("const iframe = document.createElement('iframe'); "
"iframe.src = $1; "
"document.body.appendChild(iframe);",
error_url)));
ASSERT_TRUE(iframe_navigation.WaitForNavigationFinished());
EXPECT_FALSE(iframe_navigation.was_successful());
RenderFrameHostImpl* iframe_rfh =
current_frame_host()->child_at(0)->current_frame_host();
SiteInstanceImpl* iframe_si = iframe_rfh->GetSiteInstance();
EXPECT_TRUE(IsExpectedSubframeErrorTransition(main_si, iframe_si));
// The load of the document with 404 status code and custom body is blocked
// by COEP. An error page is expected in lieu of that document.
EXPECT_EQ(GURL(kUnreachableWebDataURL),
EvalJs(iframe_rfh, "document.location.href;"));
EXPECT_FALSE(iframe_si->IsCrossOriginIsolated());
EXPECT_NE(iframe_si, main_si);
}
// Iframe blocked by coep.
{
TestNavigationManager iframe_navigation(web_contents(), non_coep_page);
EXPECT_TRUE(
ExecJs(web_contents(),
JsReplace("const iframe = document.createElement('iframe'); "
"iframe.src = $1; "
"document.body.appendChild(iframe);",
non_coep_page)));
ASSERT_TRUE(iframe_navigation.WaitForNavigationFinished());
EXPECT_FALSE(iframe_navigation.was_successful());
RenderFrameHostImpl* iframe_rfh =
current_frame_host()->child_at(0)->current_frame_host();
SiteInstanceImpl* iframe_si = iframe_rfh->GetSiteInstance();
EXPECT_TRUE(IsExpectedSubframeErrorTransition(main_si, iframe_si));
EXPECT_FALSE(iframe_si->IsCrossOriginIsolated());
}
// Top frame.
{
scoped_refptr<SiteInstanceImpl> previous_si =
current_frame_host()->GetSiteInstance();
EXPECT_FALSE(NavigateToURL(shell(), invalid_url));
SiteInstanceImpl* current_si = current_frame_host()->GetSiteInstance();
if (IsBackForwardCacheEnabled()) {
EXPECT_FALSE(current_si->IsRelatedSiteInstance(previous_si.get()));
} else {
EXPECT_TRUE(current_si->IsRelatedSiteInstance(previous_si.get()));
}
EXPECT_NE(current_si->GetProcess(),
previous_si->GetOrCreateProcessForTesting());
EXPECT_FALSE(current_si->IsCrossOriginIsolated());
}
// DIP iframe inside non-DIP page.
{
EXPECT_TRUE(NavigateToURL(shell(), non_isolated_page));
SiteInstanceImpl* current_si = current_frame_host()->GetSiteInstance();
EXPECT_FALSE(current_si->IsCrossOriginIsolated());
// First, add an iframe with DocumentIsolationPolicy.
TestNavigationManager iframe_navigation(web_contents(), isolated_page);
EXPECT_TRUE(
ExecJs(web_contents(),
JsReplace("const iframe = document.createElement('iframe'); "
"iframe.id = 'iframe';"
"iframe.src = $1; "
"document.body.appendChild(iframe);",
isolated_page)));
ASSERT_TRUE(iframe_navigation.WaitForNavigationFinished());
EXPECT_TRUE(iframe_navigation.was_successful());
RenderFrameHostImpl* iframe_rfh =
current_frame_host()->child_at(0)->current_frame_host();
scoped_refptr<SiteInstanceImpl> iframe_si = iframe_rfh->GetSiteInstance();
EXPECT_TRUE(iframe_si->IsCrossOriginIsolated());
EXPECT_NE(current_si, iframe_si);
EXPECT_NE(current_si->GetProcess(), iframe_si->GetProcess());
// Now navigate the iframe to an error page.
TestNavigationManager error_navigation(web_contents(), invalid_url);
EXPECT_TRUE(ExecJs(
web_contents(),
JsReplace("document.getElementById('iframe').src = $1;", invalid_url)));
ASSERT_TRUE(error_navigation.WaitForNavigationFinished());
EXPECT_FALSE(error_navigation.was_successful());
iframe_rfh = current_frame_host()->child_at(0)->current_frame_host();
scoped_refptr<SiteInstanceImpl> error_si = iframe_rfh->GetSiteInstance();
EXPECT_FALSE(error_si->IsCrossOriginIsolated());
EXPECT_NE(error_si, iframe_si);
EXPECT_NE(error_si->GetProcess(), iframe_si->GetProcess());
EXPECT_EQ(error_si, current_si);
EXPECT_EQ(error_si->GetProcess(), current_si->GetProcess());
}
}
// Tests that a reload navigation that redirects to a page with a
// Document-Isolation-Policy header is placed in a cross-origin isolated
// SiteInstance, even if the original page did not have DocumentIsolationPolicy.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest,
ReloadRedirectsToDipPage) {
GURL dip_page = GetDocumentIsolationPolicyURL("a.test");
GURL redirect_page(https_server()->GetURL(
"a.test", "/redirect-on-second-navigation?" + dip_page.spec()));
// Navigate to the redirect page. On the first navigation, this is a simple
// empty page with no headers.
EXPECT_TRUE(NavigateToURL(shell(), redirect_page));
scoped_refptr<SiteInstanceImpl> main_si =
current_frame_host()->GetSiteInstance();
EXPECT_EQ(current_frame_host()->GetLastCommittedURL(), redirect_page);
// Reload. This time we should be redirected to a DIP:
// isolate-and-require-corp page.
ReloadBlockUntilNavigationsComplete(shell(), 1);
EXPECT_EQ(current_frame_host()->GetLastCommittedURL(), dip_page);
// We should have swapped SiteInstance.
EXPECT_NE(main_si, current_frame_host()->GetSiteInstance());
EXPECT_TRUE(
main_si->IsRelatedSiteInstance(current_frame_host()->GetSiteInstance()));
EXPECT_NE(main_si->GetOrCreateProcessForTesting(),
current_frame_host()->GetSiteInstance()->GetProcess());
}
// Tests that a reload navigation where the page starts sending
// DocumentIsolationPolicy header on the reload (while the initial load did not
// have them). Tha navigation should end up in a cross-origin isolated
// SiteInstance.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest,
ReloadPageWithUpdatedDipHeader) {
GURL changing_dip_page(
https_server()->GetURL("a.test", "/serve-dip-on-second-navigation"));
// Navigate to the page. On the first navigation, this is a simple empty page
// with no headers.
EXPECT_TRUE(NavigateToURL(shell(), changing_dip_page));
scoped_refptr<SiteInstanceImpl> main_si =
current_frame_host()->GetSiteInstance();
// Reload. This time the page should be served with DIP:
// isolate-and-require-corp.
ReloadBlockUntilNavigationsComplete(shell(), 1);
// We should have swapped SiteInstance.
EXPECT_NE(main_si, current_frame_host()->GetSiteInstance());
EXPECT_TRUE(
main_si->IsRelatedSiteInstance(current_frame_host()->GetSiteInstance()));
EXPECT_NE(main_si->GetOrCreateProcessForTesting(),
current_frame_host()->GetSiteInstance()->GetProcess());
}
// Checks that a cross-origin but same site iframe is placed in a different
// SiteInstance from its parent when both have DocumentIsolationPolicy.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest,
CrossOriginSameSiteIframe) {
GURL isolated_page = GetDocumentIsolationPolicyURL("a.test");
GURL isolated_page_b = GetDocumentIsolationPolicyURL("cdn.a.test");
// Initial cross-origin isolated page.
EXPECT_TRUE(NavigateToURL(shell(), isolated_page));
SiteInstanceImpl* main_si = current_frame_host()->GetSiteInstance();
EXPECT_TRUE(main_si->IsCrossOriginIsolated());
TestNavigationManager cross_origin_iframe_navigation(web_contents(),
isolated_page_b);
// Add a cross-origin but same-site iframe.
EXPECT_TRUE(
ExecJs(web_contents(),
JsReplace("const iframe = document.createElement('iframe'); "
"iframe.src = $1; "
"document.body.appendChild(iframe);",
isolated_page_b)));
ASSERT_TRUE(cross_origin_iframe_navigation.WaitForNavigationFinished());
EXPECT_TRUE(cross_origin_iframe_navigation.was_successful());
RenderFrameHostImpl* iframe_rfh =
current_frame_host()->child_at(0)->current_frame_host();
SiteInstanceImpl* iframe_si = iframe_rfh->GetSiteInstance();
EXPECT_NE(iframe_si, main_si);
EXPECT_TRUE(iframe_si->IsCrossOriginIsolated());
EXPECT_TRUE(iframe_si->IsRelatedSiteInstance(main_si));
EXPECT_NE(iframe_si->GetProcess(), main_si->GetProcess());
// Open an isolated popup from the cross-origin but same-site iframe. It
// should end up in the same SiteInstance as the main frame, since they are
// same-origin with the same Document-Isolation-Policy.
{
RenderFrameHostImpl* popup_rfh =
static_cast<WebContentsImpl*>(
OpenPopup(iframe_rfh, isolated_page, "", "", true)->web_contents())
->GetPrimaryMainFrame();
EXPECT_EQ(main_si, popup_rfh->GetSiteInstance());
}
}
// Checks that the WebExposedIsolationLevel of a RenderFrameHost is properly
// computed when cross-origin isolation is enabled through
// DocumentIsolationPolicy.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest,
WebExposedIsolationLevel) {
GURL isolated_page = GetDocumentIsolationPolicyURL("a.test");
GURL isolated_page_b = GetDocumentIsolationPolicyURL("b.test");
// Not isolated:
EXPECT_TRUE(NavigateToURL(shell(), https_server()->GetURL("/empty.html")));
EXPECT_EQ(WebExposedIsolationLevel::kNotIsolated,
current_frame_host()->GetWebExposedIsolationLevel());
// Cross-Origin Isolated:
EXPECT_TRUE(NavigateToURL(shell(), isolated_page));
EXPECT_EQ(WebExposedIsolationLevel::kIsolated,
current_frame_host()->GetWebExposedIsolationLevel());
// Cross-origin isolated iframe without permission delegation. The iframe
// should be cross-origin isolated, as the permission only applies to
// cross-origin isolation inherited from the parent (and enabled by COOP &
// COEP).
std::string create_iframe = R"(
new Promise(resolve => {
const iframe = document.createElement('iframe');
iframe.src = $1;
iframe.allow = "cross-origin-isolated 'none'";
iframe.addEventListener('load', () => resolve(true));
document.body.appendChild(iframe);
});
)";
EXPECT_TRUE(ExecJs(shell(), JsReplace(create_iframe, isolated_page_b)));
RenderFrameHostImpl* iframe_rfh =
current_frame_host()->child_at(0)->current_frame_host();
EXPECT_EQ(WebExposedIsolationLevel::kIsolated,
iframe_rfh->GetWebExposedIsolationLevel());
}
// Checks that a document with document isolation policy has its
// crossOriginIsolated property set to true and can instantiate
// SharedArrayBuffers.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest, SAB) {
GURL url = GetDocumentIsolationPolicyURL("a.test");
EXPECT_TRUE(NavigateToURL(shell(), url));
EXPECT_EQ(true, EvalJs(current_frame_host(), "self.crossOriginIsolated"));
EXPECT_EQ(true,
EvalJs(current_frame_host(), "'SharedArrayBuffer' in globalThis"));
}
// Checks that a document with document isolation policy can transfer a
// SharedArrayBuffer to a crossOriginIsolated same-origin iframe.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest,
SAB_TransferToIframe) {
CHECK(!base::FeatureList::IsEnabled(features::kSharedArrayBuffer));
GURL url = GetDocumentIsolationPolicyURL("a.test");
EXPECT_TRUE(NavigateToURL(shell(), url));
EXPECT_TRUE(ExecJs(current_frame_host(),
"g_iframe = document.createElement('iframe');"
"g_iframe.src = location.href;"
"document.body.appendChild(g_iframe);"));
WaitForLoadStop(web_contents());
RenderFrameHostImpl* main_document = current_frame_host();
RenderFrameHostImpl* sub_document =
current_frame_host()->child_at(0)->current_frame_host();
EXPECT_EQ(true, EvalJs(main_document, "self.crossOriginIsolated"));
EXPECT_EQ(true, EvalJs(sub_document, "self.crossOriginIsolated"));
EXPECT_TRUE(ExecJs(sub_document, R"(
g_sab_size = new Promise(resolve => {
addEventListener("message", event => resolve(event.data.byteLength));
});
)",
EXECUTE_SCRIPT_NO_RESOLVE_PROMISES));
EXPECT_TRUE(ExecJs(main_document, R"(
const sab = new SharedArrayBuffer(1234);
g_iframe.contentWindow.postMessage(sab, "*");
)"));
EXPECT_EQ(1234, EvalJs(sub_document, "g_sab_size"));
}
// Checks that a document with document isolation policy can transfer a
// SharedArrayBuffer to a crossOriginIsolated same-origin about:blank iframe.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest,
SAB_TransferToAboutBlankIframe) {
CHECK(!base::FeatureList::IsEnabled(features::kSharedArrayBuffer));
GURL url = GetDocumentIsolationPolicyURL("a.test");
EXPECT_TRUE(NavigateToURL(shell(), url));
EXPECT_TRUE(ExecJs(current_frame_host(),
"g_iframe = document.createElement('iframe');"
"g_iframe.src = 'about:blank';"
"document.body.appendChild(g_iframe);"));
WaitForLoadStop(web_contents());
RenderFrameHostImpl* main_document = current_frame_host();
RenderFrameHostImpl* sub_document =
current_frame_host()->child_at(0)->current_frame_host();
EXPECT_EQ(true, EvalJs(main_document, "self.crossOriginIsolated"));
EXPECT_EQ(true, EvalJs(sub_document, "self.crossOriginIsolated"));
EXPECT_EQ(true, EvalJs(sub_document, "'SharedArrayBuffer' in globalThis"));
}
// Checks that an about:blank iframe created by a cross-origin isolated document
// (through Document-Isolation-Policy) is set as crossOriginIsolated
// synchronously.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest,
AboutBlankIsSetCOISynchronously) {
CHECK(!base::FeatureList::IsEnabled(features::kSharedArrayBuffer));
GURL url = GetDocumentIsolationPolicyURL("a.test");
EXPECT_TRUE(NavigateToURL(shell(), url));
EXPECT_EQ(true, EvalJs(current_frame_host(),
"const iframe = document.createElement('iframe');"
"document.body.appendChild(iframe);"
"iframe.contentWindow.crossOriginIsolated;"));
}
// Transfer a SharedArrayBuffer in between two documents with a parent/child
// relationship. The child has not set Document-Isolation-Policy, and is not
// cross-origin isolated. It cannot receive the object.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest,
SAB_TransferToNoCrossOriginIsolatedIframe) {
CHECK(!base::FeatureList::IsEnabled(features::kSharedArrayBuffer));
GURL main_url = GetDocumentIsolationPolicyURL("a.test");
GURL iframe_url = https_server()->GetURL("a.test", "/title1.html");
EXPECT_TRUE(NavigateToURL(shell(), main_url));
EXPECT_TRUE(ExecJs(current_frame_host(),
JsReplace("g_iframe = document.createElement('iframe');"
"g_iframe.src = $1;"
"document.body.appendChild(g_iframe);",
iframe_url)));
WaitForLoadStop(web_contents());
RenderFrameHostImpl* main_document = current_frame_host();
RenderFrameHostImpl* sub_document =
current_frame_host()->child_at(0)->current_frame_host();
EXPECT_EQ(true, EvalJs(main_document, "self.crossOriginIsolated"));
EXPECT_EQ(false, EvalJs(sub_document, "self.crossOriginIsolated"));
// The parent and its child frame are in different processes, so it's not
// possible to transfer a SharedArrayBuffer between the two of them.
EXPECT_NE(main_document->GetSiteInstance()->GetProcess(),
sub_document->GetSiteInstance()->GetProcess());
}
// Transfer a SharedArrayBuffer in between two documents with a
// parent/child relationship. The child does not have Document-Isolation-Policy.
// This non-cross-origin-isolated document cannot transfer a SharedArrayBuffer
// toward the cross-origin-isolated one.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest,
SAB_TransferFromNoCrossOriginIsolatedIframe) {
CHECK(!base::FeatureList::IsEnabled(features::kSharedArrayBuffer));
GURL main_url = GetDocumentIsolationPolicyURL("a.test");
GURL iframe_url = https_server()->GetURL("a.test", "/title1.html");
EXPECT_TRUE(NavigateToURL(shell(), main_url));
EXPECT_TRUE(ExecJs(current_frame_host(),
JsReplace("g_iframe = document.createElement('iframe');"
"g_iframe.src = $1;"
"document.body.appendChild(g_iframe);",
iframe_url)));
WaitForLoadStop(web_contents());
RenderFrameHostImpl* main_document = current_frame_host();
RenderFrameHostImpl* sub_document =
current_frame_host()->child_at(0)->current_frame_host();
EXPECT_EQ(true, EvalJs(main_document, "self.crossOriginIsolated"));
EXPECT_EQ(false, EvalJs(sub_document, "self.crossOriginIsolated"));
EXPECT_TRUE(ExecJs(main_document, R"(
g_sab_size = new Promise(resolve => {
addEventListener("message", event => resolve(event.data.byteLength));
});
)",
EXECUTE_SCRIPT_NO_RESOLVE_PROMISES));
EXPECT_EQ(false, EvalJs(sub_document, "'SharedArrayBuffer' in globalThis"));
// Unlike crossOriginIsolation enabled by COOP and COEP, the SAB cannot be
// created in the non-crossOriginIsolated iframe.
// See https://crbug.com/1144838 for discussions about this behavior in COOP
// and COEP.
EXPECT_FALSE(ExecJs(sub_document, R"(
// Create a WebAssembly Memory to try to bypass the SAB constructor
// restriction.
const sab = new (new WebAssembly.Memory(
{ shared:true, initial:1, maximum:1 }).buffer.constructor)(1234);
parent.postMessage(sab, "*");
)"));
}
// Check that a same-origin iframe can become cross-origin isolated using
// DocumentIsolationPolicy regardless of its parent's crossOriginIsolated
// status.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest,
CrossOriginIsolatedIframe_SameOrigin) {
CHECK(!base::FeatureList::IsEnabled(features::kSharedArrayBuffer));
GURL main_url = https_server()->GetURL("a.test", "/title1.html");
GURL iframe_url = GetDocumentIsolationPolicyURL("a.test");
EXPECT_TRUE(NavigateToURL(shell(), main_url));
EXPECT_TRUE(ExecJs(current_frame_host(),
JsReplace("g_iframe = document.createElement('iframe');"
"g_iframe.src = $1;"
"document.body.appendChild(g_iframe);",
iframe_url)));
WaitForLoadStop(web_contents());
RenderFrameHostImpl* main_document = current_frame_host();
RenderFrameHostImpl* sub_document =
current_frame_host()->child_at(0)->current_frame_host();
EXPECT_EQ(false, EvalJs(main_document, "self.crossOriginIsolated"));
EXPECT_EQ(true, EvalJs(sub_document, "self.crossOriginIsolated"));
EXPECT_NE(main_document->GetSiteInstance()->GetProcess(),
sub_document->GetSiteInstance()->GetProcess());
}
// Check that a cross-origin iframe can become cross-origin isolated using
// DocumentIsolationPolicy regardless of its parent's crossOriginIsolated
// status.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest,
CrossOriginIsolatedIframe_CrossOrigin) {
CHECK(!base::FeatureList::IsEnabled(features::kSharedArrayBuffer));
GURL main_url = https_server()->GetURL("a.test", "/title1.html");
GURL iframe_url = GetDocumentIsolationPolicyURL("b.test");
EXPECT_TRUE(NavigateToURL(shell(), main_url));
EXPECT_TRUE(ExecJs(current_frame_host(),
JsReplace("g_iframe = document.createElement('iframe');"
"g_iframe.src = $1;"
"document.body.appendChild(g_iframe);",
iframe_url)));
WaitForLoadStop(web_contents());
RenderFrameHostImpl* main_document = current_frame_host();
RenderFrameHostImpl* sub_document =
current_frame_host()->child_at(0)->current_frame_host();
EXPECT_EQ(false, EvalJs(main_document, "self.crossOriginIsolated"));
EXPECT_EQ(true, EvalJs(sub_document, "self.crossOriginIsolated"));
EXPECT_NE(main_document->GetSiteInstance()->GetProcess(),
sub_document->GetSiteInstance()->GetProcess());
}
// Regression test for crbug.com/394350439.
// Doing a cross-document navigation to a local scheme that inherits DIP should
// not cause a crash.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest,
DIPNavigationToLocalScheme) {
// Navigate to a page with DocumentIsolationPolicy.
GURL main_url = GetDocumentIsolationPolicyURL("a.test");
EXPECT_TRUE(NavigateToURL(shell(), main_url));
// Now navigate the top-level frame to about:blank. It should be cross-origin
// isolated because it inherits the DocumentIsolationPolicy of its navigation
// initiator (the current document).
EXPECT_TRUE(ExecJs(current_frame_host(), "location = 'about:blank';"));
EXPECT_TRUE(WaitForLoadStop(web_contents()));
EXPECT_EQ(url::kAboutBlankURL, current_frame_host()->GetLastCommittedURL());
EXPECT_EQ(current_frame_host()
->policy_container_host()
->policies()
.document_isolation_policy,
GetDocumentIsolationPolicy());
}
// TODO(crbug.com/349104385): Add a test checking that the
// Document-Isolation-Policy header is ignored on redirect responses.
// Regression test for crbug.com/393480086.
// Blocking a navigation at BeginNavigation stage in a child frame of a document
// with DocumentIsolationPolicy should not cause a crash.
IN_PROC_BROWSER_TEST_P(DocumentIsolationPolicyBrowserTest,
IframeLoadCancelledInBeginNavigation) {
GURL main_url = GetDocumentIsolationPolicyURL(
"a.test", "Content-Security-Policy: frame-src 'none';");
GURL iframe_url = https_server()->GetURL("a.test", "/title1.html");
EXPECT_TRUE(NavigateToURL(shell(), main_url));
EXPECT_TRUE(ExecJs(current_frame_host(),
JsReplace("g_iframe = document.createElement('iframe');"
"g_iframe.src = $1;"
"document.body.appendChild(g_iframe);",
iframe_url)));
WaitForLoadStop(web_contents());
// TODO(crbug.com/395036622): The subframe error page should commit in the
// isolated error page process.
RenderFrameHostImpl* main_document = current_frame_host();
RenderFrameHostImpl* sub_document =
current_frame_host()->child_at(0)->current_frame_host();
EXPECT_EQ(main_document->GetSiteInstance()->GetProcess(),
sub_document->GetSiteInstance()->GetProcess());
EXPECT_FALSE(sub_document->GetSiteInstance()->GetSiteInfo().is_error_page());
}
static auto kTestParams =
testing::Combine(testing::ValuesIn(RenderDocumentFeatureLevelValues()),
testing::Bool(),
testing::Bool());
INSTANTIATE_TEST_SUITE_P(All,
DocumentIsolationPolicyBrowserTest,
kTestParams,
DocumentIsolationPolicyBrowserTest::DescribeParams);
INSTANTIATE_TEST_SUITE_P(All,
DocumentIsolationPolicyWithoutFeatureBrowserTest,
kTestParams,
DocumentIsolationPolicyBrowserTest::DescribeParams);
INSTANTIATE_TEST_SUITE_P(All,
DocumentIsolationPolicyWithoutSiteIsolationBrowserTest,
kTestParams,
DocumentIsolationPolicyBrowserTest::DescribeParams);
} // namespace content