// Copyright 2022 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "content/browser/site_per_process_browsertest.h" #include "content/browser/renderer_host/navigation_entry_restore_context_impl.h" #include "content/browser/web_contents/web_contents_impl.h" #include "content/public/browser/site_isolation_policy.h" #include "content/public/common/content_switches.h" #include "content/public/test/browser_test.h" #include "content/public/test/browser_test_utils.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/test_frame_navigation_observer.h" #include "content/test/render_document_feature.h" #include "net/dns/mock_host_resolver.h" #include "third_party/blink/public/common/features.h" #include "third_party/blink/public/common/page_state/page_state_serialization.h" #include "third_party/blink/public/common/switches.h" namespace content { // Test class that runs with kNewBaseUrlInheritanceBehavior enabled. class BaseUrlInheritanceBehaviorIframeTest : public ContentBrowserTest { public: BaseUrlInheritanceBehaviorIframeTest() { feature_list_.InitAndEnableFeature( blink::features::kNewBaseUrlInheritanceBehavior); } void SetUpOnMainThread() override { // Support multiple sites on the test server. host_resolver()->AddRule("*", "127.0.0.1"); } void StartEmbeddedServer() { SetupCrossSiteRedirector(embedded_test_server()); ASSERT_TRUE(embedded_test_server()->Start()); } private: base::test::ScopedFeatureList feature_list_; }; // class NewBaseUrlInheritanceBehaviorIframeTest // Test class that runs with the legacy base url behavior. class BaseUrlLegacyBehaviorIframeTest : public ContentBrowserTest { public: BaseUrlLegacyBehaviorIframeTest() { feature_list_.InitAndDisableFeature( blink::features::kNewBaseUrlInheritanceBehavior); } void SetUpOnMainThread() override { // Support multiple sites on the test server. host_resolver()->AddRule("*", "127.0.0.1"); } void StartEmbeddedServer() { SetupCrossSiteRedirector(embedded_test_server()); ASSERT_TRUE(embedded_test_server()->Start()); } private: base::test::ScopedFeatureList feature_list_; }; // class BaseUrlLegacyBehaviorIframeTest // A class for tests that should run both with and without the new BaseURL // inheritance behavior. class BaseUrlInheritanceIframeTest : public ContentBrowserTest, public ::testing::WithParamInterface<bool> { public: BaseUrlInheritanceIframeTest() { if (GetParam()) { // Test new base url behavior. feature_list_.InitWithFeatureState( blink::features::kNewBaseUrlInheritanceBehavior, true); } else { // Need to force off kIsolateSandboxedIframes if it's enabled in order to // test the legacy base url behavior. feature_list_.InitWithFeatureStates( {{blink::features::kNewBaseUrlInheritanceBehavior, false}, {blink::features::kIsolateSandboxedIframes, false}}); } } void SetUpOnMainThread() override { // Support multiple sites on the test server. host_resolver()->AddRule("*", "127.0.0.1"); } void StartEmbeddedServer() { SetupCrossSiteRedirector(embedded_test_server()); ASSERT_TRUE(embedded_test_server()->Start()); } private: base::test::ScopedFeatureList feature_list_; }; // class BaseUrlInheritanceIframeTest // A test to make sure that restoring a session history entry that was saved // with an about:blank subframe never results in an initiator_base_url of // an empty string. std::nullopt is expected instead of an empty GURL with // legacy base url behavior, or the non-empty initiator base url in the // new base url inheritance mode. This test runs in both modes. IN_PROC_BROWSER_TEST_P(BaseUrlInheritanceIframeTest, BaseURLFromSessionHistoryIsNulloptNotEmptyString) { StartEmbeddedServer(); GURL main_url( embedded_test_server()->GetURL("a.com", "/page_with_iframe.html")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) ->GetPrimaryFrameTree() .root(); ASSERT_EQ(1U, root->child_count()); FrameTreeNode* child = root->child_at(0); // Navigate child to about:blank. { TestNavigationObserver iframe_observer(shell()->web_contents()); EXPECT_TRUE(ExecJs(child, "location.href = 'about:blank';")); iframe_observer.Wait(); } GURL child_frame_url = child->current_frame_host()->GetLastCommittedURL(); // Save the page state. NavigationControllerImpl& controller = static_cast<NavigationControllerImpl&>( shell()->web_contents()->GetController()); NavigationEntryImpl* entry = controller.GetLastCommittedEntry(); blink::PageState page_state = entry->GetPageState(); // Decode the page state so we can inspect what base url value it contains. blink::ExplodedPageState exploded_page_state; ASSERT_TRUE( blink::DecodePageState(page_state.ToEncodedData(), &exploded_page_state)); EXPECT_EQ(1U, exploded_page_state.top.children.size()); if (GetParam()) { // Make sure the about:blank child has the correct initiator_base_url. GURL initiator_url(embedded_test_server()->GetURL("a.com", "/title1.html")); EXPECT_TRUE(exploded_page_state.top.children[0] .initiator_base_url_string.has_value()); EXPECT_EQ( base::UTF8ToUTF16(initiator_url.spec()), exploded_page_state.top.children[0].initiator_base_url_string.value()); } else { // Make sure the about:blank child has nullopt, and not an empty string, for // the initiator_base_url. EXPECT_EQ(std::nullopt, exploded_page_state.top.children[0].initiator_base_url_string); } } // A test to make sure that restoring a session history entry that was saved // while the new behavior was enabled doesn't hit any CHECKs if it's restored // while using the legacy behavior. IN_PROC_BROWSER_TEST_F(BaseUrlLegacyBehaviorIframeTest, RestoreNonEmptyBaseURLFromSessionHistory) { StartEmbeddedServer(); GURL main_url( embedded_test_server()->GetURL("a.com", "/page_with_iframe.html")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) ->GetPrimaryFrameTree() .root(); ASSERT_EQ(1U, root->child_count()); FrameTreeNode* child = root->child_at(0); // Navigate child to about:blank. { TestNavigationObserver iframe_observer(shell()->web_contents()); EXPECT_TRUE(ExecJs(child, "location.href = 'about:blank';")); iframe_observer.Wait(); } GURL child_frame_url = child->current_frame_host()->GetLastCommittedURL(); // Save the page state. NavigationControllerImpl& controller = static_cast<NavigationControllerImpl&>( shell()->web_contents()->GetController()); NavigationEntryImpl* entry = controller.GetLastCommittedEntry(); blink::PageState page_state = entry->GetPageState(); // Simulate the case that the PageState was stored from a session with the new // base URL inheritance behavior enabled, by defining the // initiator_base_url_string. This approach is necessary because it is // difficult to change the feature state at runtime during the test. { blink::ExplodedPageState exploded_page_state; ASSERT_TRUE(blink::DecodePageState(page_state.ToEncodedData(), &exploded_page_state)); EXPECT_EQ(1U, exploded_page_state.top.children.size()); // Add a non-null base url which shouldn't be there if the feature is turned // off. exploded_page_state.top.children[0].initiator_base_url_string = base::UTF8ToUTF16(main_url.spec()); std::string encoded_data; blink::EncodePageState(exploded_page_state, &encoded_data); page_state = blink::PageState::CreateFromEncodedData(encoded_data); } // Restore the altered entry in a new tab and verify the frame loads without // hitting any CHECKs. Shell* new_shell = Shell::CreateNewWindow( controller.GetBrowserContext(), GURL::EmptyGURL(), nullptr, gfx::Size()); FrameTreeNode* new_root = static_cast<WebContentsImpl*>(new_shell->web_contents()) ->GetPrimaryFrameTree() .root(); NavigationControllerImpl& new_controller = static_cast<NavigationControllerImpl&>( new_shell->web_contents()->GetController()); // Create the restored entry. std::unique_ptr<NavigationEntryImpl> restored_entry = entry->Clone(); NavigationEntryRestoreContextImpl context; restored_entry->SetPageState(page_state, &context); EXPECT_EQ(main_url, restored_entry->root_node()->frame_entry->url()); ASSERT_EQ(1U, restored_entry->root_node()->children.size()); EXPECT_EQ(child_frame_url, restored_entry->root_node()->children[0]->frame_entry->url()); std::vector<std::unique_ptr<NavigationEntry>> entries; entries.push_back(std::move(restored_entry)); new_controller.Restore(entries.size() - 1, RestoreType::kRestored, &entries); ASSERT_EQ(0u, entries.size()); { TestNavigationObserver restore_observer(new_shell->web_contents()); new_controller.LoadIfNecessary(); restore_observer.Wait(); } ASSERT_EQ(1U, new_root->child_count()); EXPECT_EQ(main_url, new_root->current_url()); EXPECT_EQ(GURL("about:blank"), new_root->child_at(0)->current_url()); } // Test class to allow testing srcdoc functionality both with and without // `kIsolateSandboxedIframes` enabled. The tests verify the correct operation of // plumbing of both srcdoc attribute values, as well as the srcdoc frame's // parent's base url values, to the srcdoc's frame's renderer. class SrcdocIsolatedSandboxedIframeTest : public ContentBrowserTest, public ::testing::WithParamInterface<bool> { public: SrcdocIsolatedSandboxedIframeTest() { feature_list_.InitWithFeatureState( blink::features::kIsolateSandboxedIframes, GetParam()); } void SetUpOnMainThread() override { // Support multiple sites on the test server. host_resolver()->AddRule("*", "127.0.0.1"); } void StartEmbeddedServer() { SetupCrossSiteRedirector(embedded_test_server()); ASSERT_TRUE(embedded_test_server()->Start()); } private: base::test::ScopedFeatureList feature_list_; }; // class SrcdocIsolatedSandboxedIframeTest // Test class to verify that the enterprise policy // NewBaseUrlInheritanceBehaviorAllowed can be used to control whether the // NewBaseUrlInheritanceBehavior and IsolateSandboxedIframes features can be // used. class BaseUrlInheritanceBehaviorEnterprisePolicyTest : public SrcdocIsolatedSandboxedIframeTest { public: BaseUrlInheritanceBehaviorEnterprisePolicyTest() = default; void SetUpCommandLine(base::CommandLine* command_line) override { SrcdocIsolatedSandboxedIframeTest::SetUpCommandLine(command_line); command_line->AppendSwitch( blink::switches::kDisableNewBaseUrlInheritanceBehavior); } }; // class BaseUrlInheritanceBehaviorEnterprisePolicyTest // Out-of-process-sandboxed-iframe (OOPSIF) tests. // // Test classes for isolating sandboxed iframes and documents in a different // process from the rest of their site. // See https://crbug.com/510122. class SitePerProcessIsolatedSandboxedIframeTest : public SitePerProcessBrowserTest { public: SitePerProcessIsolatedSandboxedIframeTest() { feature_list_.InitAndEnableFeature( blink::features::kIsolateSandboxedIframes); } private: base::test::ScopedFeatureList feature_list_; }; class SitePerProcessNotIsolatedSandboxedIframeTest : public SitePerProcessBrowserTest { public: SitePerProcessNotIsolatedSandboxedIframeTest() { feature_list_.InitAndDisableFeature( blink::features::kIsolateSandboxedIframes); } private: base::test::ScopedFeatureList feature_list_; }; // A test class to allow testing isolated sandboxed iframes using the per-origin // process model. class SitePerProcessPerOriginIsolatedSandboxedIframeTest : public SitePerProcessBrowserTest { public: SitePerProcessPerOriginIsolatedSandboxedIframeTest() { feature_list_.InitWithFeaturesAndParameters( {{blink::features::kIsolateSandboxedIframes, {{"grouping", "per-origin"}}}}, {/* disabled_features */}); } private: base::test::ScopedFeatureList feature_list_; }; class SitePerProcessIsolatedSandboxWithoutStrictSiteIsolationBrowserTest : public SitePerProcessIsolatedSandboxedIframeTest { public: SitePerProcessIsolatedSandboxWithoutStrictSiteIsolationBrowserTest() { feature_list_.InitAndEnableFeature( blink::features::kIsolateSandboxedIframes); } void SetUpCommandLine(base::CommandLine* command_line) override { SitePerProcessIsolatedSandboxedIframeTest::SetUpCommandLine(command_line); // Because this test derives from SitePerProcessBrowserTestBase which // calls IsolateAllSitesForTesting, we need to manually remove the // kSitePerProcess switch to simulate the environment where not all sites // automatically get isolation. command_line->RemoveSwitch(switches::kSitePerProcess); } void SetUpOnMainThread() override { SitePerProcessIsolatedSandboxedIframeTest::SetUpOnMainThread(); // Override BrowserClient to disable strict site isolation. browser_client_ = std::make_unique<PartialSiteIsolationContentBrowserClient>(); // The custom ContentBrowserClient below typically ensures that this test // runs without strict site isolation, but it's still possible to // inadvertently override this when running with --site-per-process on the // command line. This might happen on try bots, so these tests take this // into account to prevent failures, but this is not an intended // configuration for these tests, since isolating sandboxed iframes in // these tests depends on use of default SiteInstances. if (AreAllSitesIsolatedForTesting()) { LOG(WARNING) << "This test should be run without --site-per-process, " << "as it's designed to exercise code paths when strict " << "site isolation is turned off."; } } void TearDownOnMainThread() override { SitePerProcessIsolatedSandboxedIframeTest::TearDownOnMainThread(); browser_client_.reset(); } // A custom ContentBrowserClient to turn off strict site isolation, since // isolated sandboxed iframes behave differently in environments like Android // where it is not (generally) used. Note that kSitePerProcess is a // higher-layer feature, so we can't just disable it here. class PartialSiteIsolationContentBrowserClient : public ContentBrowserTestContentBrowserClient { public: bool ShouldEnableStrictSiteIsolation() override { return false; } bool DoesSiteRequireDedicatedProcess( BrowserContext* browser_context, const GURL& effective_site_url) override { return effective_site_url == GURL("http://isolated.com"); } }; private: base::test::ScopedFeatureList feature_list_; std::unique_ptr<PartialSiteIsolationContentBrowserClient> browser_client_; }; // A test class to allow testing isolated sandboxed iframes using the // per-document grouping model. class SitePerProcessPerDocumentIsolatedSandboxedIframeTest : public SitePerProcessBrowserTest { public: SitePerProcessPerDocumentIsolatedSandboxedIframeTest() { feature_list_.InitWithFeaturesAndParameters( {{blink::features::kIsolateSandboxedIframes, {{"grouping", "per-document"}}}}, {/* disabled_features */}); } private: base::test::ScopedFeatureList feature_list_; }; // The following test should not crash. In this test the // kIsolateSandboxedIframes flag is forced off, so we don't need to verify // the process isolation details, as is done in // SitePerProcessIsolatedSandboxedIframeTest.SrcdocCspSandboxIsIsolated below. // https://crbug.com/1319430 IN_PROC_BROWSER_TEST_P(SitePerProcessNotIsolatedSandboxedIframeTest, SrcdocSandboxFlagsCheck) { GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); // Create sandboxed srcdoc child frame, with csp sandbox. EXPECT_TRUE(ExecJs(shell(), "var frame = document.createElement('iframe'); " "frame.csp = 'sandbox'; " "frame.srcdoc = 'foo'; " "document.body.appendChild(frame);")); ASSERT_TRUE(WaitForLoadStop(web_contents())); } // Test that a srcdoc iframe that receives its sandbox flags from the CSP // attribute also gets process isolation. This test starts the same as // SitePerProcessNotIsolatedSandboxedIframeTest.SrcdocSandboxFlagsCheck, but in // this test the kIsolateSandboxedIframes flag is on, so we also verify that // the process isolation has indeed occurred. IN_PROC_BROWSER_TEST_P(SitePerProcessIsolatedSandboxedIframeTest, SrcdocCspSandboxIsIsolated) { GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); // Create sandboxed srcdoc child frame, with csp sandbox. EXPECT_TRUE(ExecJs(shell(), "var frame = document.createElement('iframe'); " "frame.csp = 'sandbox'; " "frame.srcdoc = 'foo'; " "document.body.appendChild(frame);")); ASSERT_TRUE(WaitForLoadStop(web_contents())); // Check frame-tree. FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); ASSERT_EQ(1U, root->child_count()); FrameTreeNode* child = root->child_at(0); EXPECT_EQ(network::mojom::WebSandboxFlags::kAll, child->current_frame_host()->active_sandbox_flags()); EXPECT_NE(root->current_frame_host()->GetSiteInstance(), child->current_frame_host()->GetSiteInstance()); EXPECT_TRUE(child->current_frame_host() ->GetSiteInstance() ->GetSiteInfo() .is_sandboxed()); EXPECT_FALSE(root->current_frame_host() ->GetSiteInstance() ->GetSiteInfo() .is_sandboxed()); } // A test to verify that an iframe that is sandboxed using the 'csp' attribute // instead of the 'sandbox' attribute gets process isolation when the // kIsolatedSandboxedIframes flag is enabled. IN_PROC_BROWSER_TEST_P(SitePerProcessIsolatedSandboxedIframeTest, CspIsolatedSandbox) { GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); // Create csp-sandboxed child frame, same-origin. { EXPECT_TRUE(ExecJs(shell(), "var frame = document.createElement('iframe'); " "frame.csp = 'sandbox'; " "frame.srcdoc = '<b>Hello!</b>'; " "document.body.appendChild(frame);")); ASSERT_TRUE(WaitForLoadStop(shell()->web_contents())); } // Check frame-tree. FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); ASSERT_EQ(1U, root->child_count()); FrameTreeNode* child = root->child_at(0); EXPECT_EQ(network::mojom::WebSandboxFlags::kAll, child->current_frame_host()->active_sandbox_flags()); EXPECT_NE(root->current_frame_host()->GetSiteInstance(), child->current_frame_host()->GetSiteInstance()); EXPECT_TRUE(child->current_frame_host() ->GetSiteInstance() ->GetSiteInfo() .is_sandboxed()); EXPECT_FALSE(root->current_frame_host() ->GetSiteInstance() ->GetSiteInfo() .is_sandboxed()); } // A test to verify that an iframe with a fully-restrictive sandbox is rendered // in a separate process from its parent frame even if they have the same // origin. IN_PROC_BROWSER_TEST_P(SitePerProcessIsolatedSandboxedIframeTest, IsolatedSandbox) { GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html")); // The child needs to have the same origin as the parent. GURL child_url(main_url); EXPECT_TRUE(NavigateToURL(shell(), main_url)); // Create sandboxed child frame, same-origin. { std::string js_str = base::StringPrintf( "var frame = document.createElement('iframe'); " "frame.sandbox = ''; " "frame.src = '%s'; " "document.body.appendChild(frame);", child_url.spec().c_str()); EXPECT_TRUE(ExecJs(shell(), js_str)); ASSERT_TRUE(WaitForLoadStop(shell()->web_contents())); } // Check frame-tree. FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); ASSERT_EQ(1U, root->child_count()); FrameTreeNode* child = root->child_at(0); EXPECT_EQ(network::mojom::WebSandboxFlags::kAll, child->effective_frame_policy().sandbox_flags); EXPECT_NE(root->current_frame_host()->GetSiteInstance(), child->current_frame_host()->GetSiteInstance()); EXPECT_TRUE(child->current_frame_host() ->GetSiteInstance() ->GetSiteInfo() .is_sandboxed()); EXPECT_FALSE(root->current_frame_host() ->GetSiteInstance() ->GetSiteInfo() .is_sandboxed()); } // Test that a sandboxed srcdoc iframe loads properly when its parent's url is // different from its site_url. The child should get its own SiteInstance with a // site_url based on the full origin of the parent's original url. IN_PROC_BROWSER_TEST_P(SitePerProcessPerOriginIsolatedSandboxedIframeTest, SrcdocSandboxedFrameWithNonSiteParent) { GURL main_url(embedded_test_server()->GetURL("sub.a.com", "/title1.html")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); // Create sandboxed srcdoc child frame. { std::string js_str = "const frame = document.createElement('iframe'); " "frame.sandbox = ''; " "frame.srcdoc = 'foo'; " "document.body.appendChild(frame);"; EXPECT_TRUE(ExecJs(shell(), js_str)); ASSERT_TRUE(WaitForLoadStop(shell()->web_contents())); } // Check frametree. FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); ASSERT_EQ(1U, root->child_count()); FrameTreeNode* child = root->child_at(0); // sub.a.com EXPECT_EQ(network::mojom::WebSandboxFlags::kAll, child->effective_frame_policy().sandbox_flags); auto* parent_site_instance = root->current_frame_host()->GetSiteInstance(); auto* child_site_instance = child->current_frame_host()->GetSiteInstance(); EXPECT_NE(parent_site_instance, child_site_instance); EXPECT_TRUE(child_site_instance->GetSiteInfo().is_sandboxed()); EXPECT_FALSE(parent_site_instance->GetSiteInfo().is_sandboxed()); EXPECT_EQ(embedded_test_server()->GetURL("sub.a.com", "/"), child_site_instance->GetSiteInfo().site_url()); EXPECT_EQ(GURL("http://a.com/"), parent_site_instance->GetSiteInfo().site_url()); } namespace { GURL GetFrameBaseUrl(RenderFrameHostImpl* rfhi) { return GURL(EvalJs(rfhi, "document.baseURI").ExtractString()); } GURL GetFrameBaseUrl(Shell* shell) { return GURL(EvalJs(shell, "document.baseURI").ExtractString()); } } // namespace IN_PROC_BROWSER_TEST_P(SitePerProcessPerOriginIsolatedSandboxedIframeTest, SrcdocSandboxedFrameInsideAboutBlank) { // Open main page on a.foo.com. It will be put in a site instance with site // url foo.com GURL main_url(embedded_test_server()->GetURL("a.foo.com", "/title1.html")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); // Create an about:blank frame. { std::string js_str = "const frame = document.createElement('iframe'); " "frame.src = 'about:blank'; " "document.body.appendChild(frame);"; EXPECT_TRUE(ExecJs(shell(), js_str)); ASSERT_TRUE(WaitForLoadStop(shell()->web_contents())); } ASSERT_EQ(1U, root->child_count()); FrameTreeNode* child = root->child_at(0); // Create a sandboxed srcdoc frame inside the about:blank child. { std::string js_str = "const frame = document.createElement('iframe'); " "frame.sandbox = 'allow-scripts'; " "frame.srcdoc = 'foo'; " "document.body.appendChild(frame);"; EXPECT_TRUE(ExecJs(child, js_str)); ASSERT_TRUE(WaitForLoadStop(shell()->web_contents())); } ASSERT_EQ(1U, child->child_count()); FrameTreeNode* grand_child = child->child_at(0); auto* grand_child_site_instance = grand_child->current_frame_host()->GetSiteInstance(); EXPECT_TRUE(grand_child_site_instance->GetSiteInfo().is_sandboxed()); EXPECT_EQ(embedded_test_server()->GetURL("a.foo.com", "/"), grand_child_site_instance->GetSiteInfo().site_url()); EXPECT_EQ(main_url, GetFrameBaseUrl(grand_child->current_frame_host())); EXPECT_EQ(main_url, grand_child->current_frame_host()->GetInheritedBaseUrl()); } // Similar to SrcdocSandboxedFrameWithNonSiteParent, but this time the srcdoc // is opened from b.foo.com which is loaded in the SiteInstance that was // created for a.foo.com, so the SiteInstance cannot be used to specify the // origin the srcdoc should use, namely b.foo.com. IN_PROC_BROWSER_TEST_P(SitePerProcessPerOriginIsolatedSandboxedIframeTest, SrcdocSandboxedFrameWithNonSiteParent2) { GURL main_url(embedded_test_server()->GetURL("a.foo.com", "/title1.html")); GURL sibling_url(embedded_test_server()->GetURL("b.foo.com", "/title1.html")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); // Open a new window that will share the SiteInstance of the main window. Shell* new_shell = OpenPopup(root, sibling_url, ""); FrameTreeNode* sibling = static_cast<WebContentsImpl*>(new_shell->web_contents()) ->GetPrimaryFrameTree() .root(); EXPECT_EQ(root->current_frame_host()->GetSiteInstance(), sibling->current_frame_host()->GetSiteInstance()); { std::string js_str = "const frame = document.createElement('iframe'); " "frame.sandbox = ''; " "frame.srcdoc = 'foo'; " "document.body.appendChild(frame);"; EXPECT_TRUE(ExecJs(new_shell, js_str)); ASSERT_TRUE(WaitForLoadStop(new_shell->web_contents())); } ASSERT_EQ(1U, sibling->child_count()); FrameTreeNode* child = sibling->child_at(0); // b.foo.com EXPECT_EQ(network::mojom::WebSandboxFlags::kAll, child->effective_frame_policy().sandbox_flags); auto* sibling_site_instance = sibling->current_frame_host()->GetSiteInstance(); auto* child_site_instance = child->current_frame_host()->GetSiteInstance(); EXPECT_NE(sibling_site_instance, child_site_instance); EXPECT_TRUE(child_site_instance->GetSiteInfo().is_sandboxed()); EXPECT_FALSE(sibling_site_instance->GetSiteInfo().is_sandboxed()); EXPECT_EQ(embedded_test_server()->GetURL("b.foo.com", "/"), child_site_instance->GetSiteInfo().site_url()); EXPECT_EQ(GURL("http://foo.com/"), sibling_site_instance->GetSiteInfo().site_url()); } // Test that sandboxed iframes that are same-site with their parent but // cross-origin from each other are put in different processes from each other, // when the 'per-origin' isolation grouping is active for // kIsolateSandboxedIframes. (In 'per-site' isolation mode they would be in the // same process.) IN_PROC_BROWSER_TEST_P(SitePerProcessPerOriginIsolatedSandboxedIframeTest, CrossOriginIsolatedSandboxedIframes) { GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html")); // The children need to have the same origin as the parent, but be cross // origin from each other. GURL same_origin_child_url(main_url); GURL cross_origin_child_url( embedded_test_server()->GetURL("sub.a.com", "/title1.html")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); // Create sandboxed child frames, both same-origin and cross-origin. { std::string js_str = base::StringPrintf( "var frame1 = document.createElement('iframe'); " "frame1.sandbox = ''; " "frame1.src = '%s'; " "document.body.appendChild(frame1); " "var frame2 = document.createElement('iframe'); " "frame2.sandbox = ''; " "frame2.src = '%s'; " "document.body.appendChild(frame2);", same_origin_child_url.spec().c_str(), cross_origin_child_url.spec().c_str()); EXPECT_TRUE(ExecJs(shell(), js_str)); ASSERT_TRUE(WaitForLoadStop(shell()->web_contents())); } // Check frame-tree. FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); ASSERT_EQ(2U, root->child_count()); FrameTreeNode* child1 = root->child_at(0); // a.com EXPECT_EQ(network::mojom::WebSandboxFlags::kAll, child1->effective_frame_policy().sandbox_flags); EXPECT_NE(root->current_frame_host()->GetSiteInstance(), child1->current_frame_host()->GetSiteInstance()); EXPECT_TRUE(child1->current_frame_host() ->GetSiteInstance() ->GetSiteInfo() .is_sandboxed()); EXPECT_FALSE(root->current_frame_host() ->GetSiteInstance() ->GetSiteInfo() .is_sandboxed()); FrameTreeNode* child2 = root->child_at(1); // sub.a.com EXPECT_EQ(network::mojom::WebSandboxFlags::kAll, child2->effective_frame_policy().sandbox_flags); EXPECT_NE(root->current_frame_host()->GetSiteInstance(), child2->current_frame_host()->GetSiteInstance()); EXPECT_TRUE(child2->current_frame_host() ->GetSiteInstance() ->GetSiteInfo() .is_sandboxed()); // This is the key result for this test: the sandboxed iframes for 'a.com' and // 'sub.a.com' should be in different SiteInstances. auto* child1_site_instance1 = child1->current_frame_host()->GetSiteInstance(); auto* child2_site_instance1 = child2->current_frame_host()->GetSiteInstance(); EXPECT_NE(child1_site_instance1, child2_site_instance1); EXPECT_NE(child1_site_instance1->GetProcess(), child2_site_instance1->GetProcess()); } // Test that, while using 'per-origin' isolation grouping, navigating a // sandboxed iframe from 'a.foo.com' to 'b.foo.com' results in the sandbox using // two different SiteInstances. IN_PROC_BROWSER_TEST_P(SitePerProcessPerOriginIsolatedSandboxedIframeTest, CrossOriginNavigationSwitchesSiteInstances) { GURL main_url(embedded_test_server()->GetURL("foo.com", "/title1.html")); GURL cross_origin_child_url( embedded_test_server()->GetURL("a.foo.com", "/title1.html")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); // Create sandboxed cross-origin child frame. { std::string js_str = base::StringPrintf( "var frame = document.createElement('iframe'); " "frame.id = 'test_frame'; " "frame.sandbox = ''; " "frame.src = '%s'; " "document.body.appendChild(frame);", cross_origin_child_url.spec().c_str()); EXPECT_TRUE(ExecJs(shell(), js_str)); ASSERT_TRUE(WaitForLoadStop(shell()->web_contents())); } // Check frame-tree. FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); ASSERT_EQ(1U, root->child_count()); FrameTreeNode* child = root->child_at(0); // a.foo.com EXPECT_EQ(network::mojom::WebSandboxFlags::kAll, child->effective_frame_policy().sandbox_flags); scoped_refptr<SiteInstanceImpl> site_instance_root = root->current_frame_host()->GetSiteInstance(); scoped_refptr<SiteInstanceImpl> site_instance1 = child->current_frame_host()->GetSiteInstance(); EXPECT_NE(site_instance_root, site_instance1); EXPECT_TRUE(site_instance1->GetSiteInfo().is_sandboxed()); EXPECT_FALSE(site_instance_root->GetSiteInfo().is_sandboxed()); // Navigate sandboxed frame cross-origin to b.foo.com. EXPECT_TRUE(NavigateIframeToURL( shell()->web_contents(), "test_frame", GURL(embedded_test_server()->GetURL("b.foo.com", "/title1.html")))); scoped_refptr<SiteInstanceImpl> site_instance2 = child->current_frame_host()->GetSiteInstance(); EXPECT_NE(site_instance_root, site_instance2); EXPECT_NE(site_instance1, site_instance2); EXPECT_NE(site_instance1->GetProcess(), site_instance2->GetProcess()); } // Test that navigating cross-origin from a non-sandboxed iframe to a CSP // sandboxed iframe results in switching to a new SiteInstance in a different // process. IN_PROC_BROWSER_TEST_P(SitePerProcessPerOriginIsolatedSandboxedIframeTest, CrossOriginNavigationToCSPSwitchesSiteInstances) { GURL main_url(embedded_test_server()->GetURL("foo.com", "/title1.html")); GURL cross_origin_child_url( embedded_test_server()->GetURL("a.foo.com", "/title1.html")); GURL cross_origin_csp_child_url( embedded_test_server()->GetURL("b.foo.com", "/set-header?" "Content-Security-Policy: sandbox ")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); // Create non-sandboxed cross-origin child frame. { std::string js_str = base::StringPrintf( "var frame = document.createElement('iframe'); " "frame.id = 'test_frame'; " "frame.src = '%s'; " "document.body.appendChild(frame);", cross_origin_child_url.spec().c_str()); EXPECT_TRUE(ExecJs(shell(), js_str)); ASSERT_TRUE(WaitForLoadStop(shell()->web_contents())); } // Check frame-tree. FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); ASSERT_EQ(1U, root->child_count()); FrameTreeNode* child = root->child_at(0); // a.foo.com scoped_refptr<SiteInstanceImpl> site_instance_root = root->current_frame_host()->GetSiteInstance(); scoped_refptr<SiteInstanceImpl> site_instance1 = child->current_frame_host()->GetSiteInstance(); EXPECT_EQ(site_instance_root, site_instance1); EXPECT_FALSE(site_instance1->GetSiteInfo().is_sandboxed()); // Navigate child frame cross-origin to CSP-isolated b.foo.com. EXPECT_TRUE(NavigateIframeToURL(shell()->web_contents(), "test_frame", cross_origin_csp_child_url)); // The child frame should now have a different SiteInstance and process than // it did before the navigation. scoped_refptr<SiteInstanceImpl> site_instance2 = child->current_frame_host()->GetSiteInstance(); EXPECT_NE(site_instance1, site_instance2); EXPECT_NE(site_instance1->GetProcess(), site_instance2->GetProcess()); EXPECT_TRUE(site_instance2->GetSiteInfo().is_sandboxed()); } // Check that two same-site sandboxed iframes in unrelated windows share the // same process due to subframe process reuse. IN_PROC_BROWSER_TEST_P(SitePerProcessIsolatedSandboxedIframeTest, SandboxProcessReuse) { GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html")); // The child needs to have the same origin as the parent. GURL child_url(main_url); EXPECT_TRUE(NavigateToURL(shell(), main_url)); // Create sandboxed child frame, same-origin. std::string js_str = base::StringPrintf( "var frame = document.createElement('iframe'); " "frame.sandbox = ''; " "frame.src = '%s'; " "document.body.appendChild(frame);", child_url.spec().c_str()); EXPECT_TRUE(ExecJs(shell(), js_str)); ASSERT_TRUE(WaitForLoadStop(shell()->web_contents())); FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); ASSERT_EQ(1U, root->child_count()); FrameTreeNode* child = root->child_at(0); EXPECT_EQ(network::mojom::WebSandboxFlags::kAll, child->effective_frame_policy().sandbox_flags); EXPECT_NE(root->current_frame_host()->GetSiteInstance(), child->current_frame_host()->GetSiteInstance()); EXPECT_TRUE(child->current_frame_host() ->GetSiteInstance() ->GetSiteInfo() .is_sandboxed()); EXPECT_FALSE(root->current_frame_host() ->GetSiteInstance() ->GetSiteInfo() .is_sandboxed()); // Set up an unrelated window with the same frame hierarchy. Shell* new_shell = CreateBrowser(); EXPECT_TRUE(NavigateToURL(new_shell, main_url)); FrameTreeNode* new_root = static_cast<WebContentsImpl*>(new_shell->web_contents()) ->GetPrimaryFrameTree() .root(); EXPECT_TRUE(ExecJs(new_shell, js_str)); ASSERT_TRUE(WaitForLoadStop(new_shell->web_contents())); FrameTreeNode* new_child = new_root->child_at(0); EXPECT_TRUE(new_child->current_frame_host() ->GetSiteInstance() ->GetSiteInfo() .is_sandboxed()); EXPECT_FALSE(new_root->current_frame_host() ->GetSiteInstance() ->GetSiteInfo() .is_sandboxed()); // Check that the two sandboxed subframes end up in separate // BrowsingInstances but in the same process. EXPECT_FALSE( new_child->current_frame_host()->GetSiteInstance()->IsRelatedSiteInstance( child->current_frame_host()->GetSiteInstance())); EXPECT_EQ(new_child->current_frame_host()->GetProcess(), child->current_frame_host()->GetProcess()); } // A test to verify that when an iframe has two sibling subframes, each with a // fully-restrictive sandbox, that each of the three gets its own process // even though they are all same-origin. // Note: using "sandbox = ''" in this and the following tests creates fully // restricted sandboxes, which will include the kOrigin case we are interested // in. IN_PROC_BROWSER_TEST_P(SitePerProcessIsolatedSandboxedIframeTest, IsolatedSandboxSiblingSubframes) { GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html")); // The child needs to have the same origin as the parent. GURL child_url(main_url); EXPECT_TRUE(NavigateToURL(shell(), main_url)); // Create sandboxed child frame, same-origin. { std::string js_str = base::StringPrintf( "var frame1 = document.createElement('iframe'); " "frame1.sandbox = ''; " "frame1.src = '%s'; " "document.body.appendChild(frame1); " "var frame2 = document.createElement('iframe'); " "frame2.sandbox = ''; " "frame2.src = '%s'; " "document.body.appendChild(frame2);", child_url.spec().c_str(), child_url.spec().c_str()); EXPECT_TRUE(ExecJs(shell(), js_str)); ASSERT_TRUE(WaitForLoadStop(shell()->web_contents())); } // Check frame-tree. FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); ASSERT_EQ(2U, root->child_count()); FrameTreeNode* child1 = root->child_at(0); FrameTreeNode* child2 = root->child_at(1); EXPECT_EQ(network::mojom::WebSandboxFlags::kAll, child1->effective_frame_policy().sandbox_flags); EXPECT_EQ(network::mojom::WebSandboxFlags::kAll, child2->effective_frame_policy().sandbox_flags); EXPECT_NE(root->current_frame_host()->GetSiteInstance(), child1->current_frame_host()->GetSiteInstance()); EXPECT_NE(root->current_frame_host()->GetSiteInstance(), child2->current_frame_host()->GetSiteInstance()); // Because the siblings are same-site to each other (in fact, same origin) we // expect them to share a process when sandboxed. EXPECT_EQ(child1->current_frame_host()->GetSiteInstance(), child2->current_frame_host()->GetSiteInstance()); EXPECT_TRUE(child1->current_frame_host() ->GetSiteInstance() ->GetSiteInfo() .is_sandboxed()); EXPECT_TRUE(child2->current_frame_host() ->GetSiteInstance() ->GetSiteInfo() .is_sandboxed()); EXPECT_FALSE(root->current_frame_host() ->GetSiteInstance() ->GetSiteInfo() .is_sandboxed()); } IN_PROC_BROWSER_TEST_P(SitePerProcessIsolatedSandboxedIframeTest, IsolatedSandboxSrcdocSubframe) { GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); // Create sandboxed child frame, with srcdoc content. std::string child_inner_text("srcdoc sandboxed subframe"); { std::string js_str = base::StringPrintf( "var frame = document.createElement('iframe'); " "frame.sandbox = 'allow-scripts'; " "frame.srcdoc = '%s'; " "document.body.appendChild(frame);", child_inner_text.c_str()); EXPECT_TRUE(ExecJs(shell(), js_str)); ASSERT_TRUE(WaitForLoadStop(shell()->web_contents())); } FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); ASSERT_EQ(1U, root->child_count()); FrameTreeNode* child = root->child_at(0); // Verify that the child has only the 'allow-scripts' permission set. EXPECT_EQ(child->effective_frame_policy().sandbox_flags, network::mojom::WebSandboxFlags::kAll & ~network::mojom::WebSandboxFlags::kScripts & ~network::mojom::WebSandboxFlags::kAutomaticFeatures); EXPECT_EQ(std::string(url::kAboutSrcdocURL), child->current_frame_host()->GetLastCommittedURL()); EXPECT_TRUE(child->current_frame_host()->GetLastCommittedOrigin().opaque()); // Verify that the child's precursor origin matches 'a.com'. Note: we create // the expected value using `main_url` so that the test server port will be // correctly matched. EXPECT_EQ(url::SchemeHostPort(main_url), child->current_origin().GetTupleOrPrecursorTupleIfOpaque()); EXPECT_NE(root->current_frame_host()->GetSiteInstance(), child->current_frame_host()->GetSiteInstance()); EXPECT_TRUE(child->current_frame_host() ->GetSiteInstance() ->GetSiteInfo() .is_sandboxed()); EXPECT_FALSE(root->current_frame_host() ->GetSiteInstance() ->GetSiteInfo() .is_sandboxed()); { std::string js_str("document.body.innerText;"); EXPECT_EQ(child_inner_text, EvalJs(child->current_frame_host(), js_str)); } } // A test to make sure that about:blank in a sandboxed iframe doesn't get // process isolation. If it did, it would be impossible for the parent to inject // any content, and it would be stuck as empty content. IN_PROC_BROWSER_TEST_P(SitePerProcessIsolatedSandboxedIframeTest, NotIsolatedSandboxAboutBlankSubframe) { GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); // Create sandboxed child frame, with about:blank content. { std::string js_str( "var frame = document.createElement('iframe'); " "frame.id = 'child_frame'; " "frame.sandbox = ''; " "frame.src = 'about:blank'; " "document.body.appendChild(frame);"); EXPECT_TRUE(ExecJs(shell(), js_str)); ASSERT_TRUE(WaitForLoadStop(shell()->web_contents())); } FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); ASSERT_EQ(1U, root->child_count()); FrameTreeNode* child = root->child_at(0); // Verify that the child has no permissions set. EXPECT_EQ(child->effective_frame_policy().sandbox_flags, network::mojom::WebSandboxFlags::kAll); EXPECT_EQ(GURL(url::kAboutBlankURL), child->current_frame_host()->GetLastCommittedURL()); EXPECT_TRUE(child->current_frame_host()->GetLastCommittedOrigin().opaque()); // Verify that the child's precursor origin matches 'a.com'. Note: we create // the expected value using `main_url` so that the test server port will be // correctly matched. EXPECT_EQ(url::SchemeHostPort(main_url), child->current_origin().GetTupleOrPrecursorTupleIfOpaque()); // The child needs to be in the parent's SiteInstance. EXPECT_EQ(root->current_frame_host()->GetSiteInstance(), child->current_frame_host()->GetSiteInstance()); EXPECT_FALSE(root->current_frame_host() ->GetSiteInstance() ->GetSiteInfo() .is_sandboxed()); // Navigate to a page that should get process isolation. GURL isolated_child_url( embedded_test_server()->GetURL("a.com", "/title1.html")); EXPECT_TRUE(NavigateFrameToURL(child, isolated_child_url)); EXPECT_NE(root->current_frame_host()->GetSiteInstance(), child->current_frame_host()->GetSiteInstance()); EXPECT_TRUE(child->current_frame_host() ->GetSiteInstance() ->GetSiteInfo() .is_sandboxed()); // Navigate back to about:blank, and verify it's put back into the parent's // SiteInstance. scoped_refptr<SiteInstanceImpl> child_previous_site_instance = child->current_frame_host()->GetSiteInstance(); EXPECT_TRUE(NavigateIframeToURL(shell()->web_contents(), "child_frame", GURL("about:blank"))); EXPECT_EQ(root->current_frame_host()->GetSiteInstance(), child->current_frame_host()->GetSiteInstance()); EXPECT_NE(child_previous_site_instance, child->current_frame_host()->GetSiteInstance()); EXPECT_FALSE(child->current_frame_host() ->GetSiteInstance() ->GetSiteInfo() .is_sandboxed()); } // Test to make sure that javascript: urls don't execute in a sandboxed iframe. IN_PROC_BROWSER_TEST_P(SitePerProcessIsolatedSandboxedIframeTest, SandboxedIframeWithJSUrl) { GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); // Create sandboxed child frame with a javascript: URL. std::string js_url_str("javascript:\"foo\""); { std::string js_str = base::StringPrintf( "var frame = document.createElement('iframe'); " "frame.id = 'test_frame'; " "frame.sandbox = 'allow-scripts'; " "frame.src = '%s'; " "document.body.appendChild(frame);", js_url_str.c_str()); EXPECT_TRUE(ExecJs(shell(), js_str)); ASSERT_TRUE(WaitForLoadStop(shell()->web_contents())); } // Verify parent and child frames share a SiteInstance. A sandboxed iframe // with a javascript: url shouldn't get its own process. FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); ASSERT_EQ(1U, root->child_count()); FrameTreeNode* child = root->child_at(0); EXPECT_EQ(root->current_frame_host()->GetSiteInstance(), child->current_frame_host()->GetSiteInstance()); // Verify that the javascript: url did not execute. This is expected // regardless of IsolatedSandboxedIframes since sandboxed iframes get opaque // origins, and javascript: urls don't execute in opaque origins. EXPECT_TRUE( EvalJs(child->current_frame_host(), "document.body.innerHTML == ''") .ExtractBool()); } // Test to make sure that an iframe with a data:url is process isolated. IN_PROC_BROWSER_TEST_P(SitePerProcessIsolatedSandboxedIframeTest, SandboxedIframeWithDataURLIsIsolated) { GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); // Create sandboxed child frame with a data URL. std::string data_url_str("data:text/html,dataurl"); { std::string js_str = base::StringPrintf( "var frame = document.createElement('iframe'); " "frame.id = 'test_frame'; " "frame.sandbox = ''; " "frame.src = '%s'; " "document.body.appendChild(frame);", data_url_str.c_str()); EXPECT_TRUE(ExecJs(shell(), js_str)); ASSERT_TRUE(WaitForLoadStop(shell()->web_contents())); } // Verify parent and child frames don't share a SiteInstance FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); ASSERT_EQ(1U, root->child_count()); FrameTreeNode* child = root->child_at(0); EXPECT_NE(root->current_frame_host()->GetSiteInstance(), child->current_frame_host()->GetSiteInstance()); EXPECT_TRUE(child->current_frame_host() ->GetSiteInstance() ->GetSiteInfo() .is_sandboxed()); EXPECT_FALSE(root->current_frame_host() ->GetSiteInstance() ->GetSiteInfo() .is_sandboxed()); } // Test to make sure that an iframe with a data:url is appropriately sandboxed. IN_PROC_BROWSER_TEST_P(SitePerProcessIsolatedSandboxedIframeTest, SandboxedIframeWithDataURL) { GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); // Create non-sandboxed child frame with a data URL. std::string data_url_str("data:text/html,dataurl"); { std::string js_str = base::StringPrintf( "var frame = document.createElement('iframe'); " "frame.id = 'test_frame'; " "frame.src = '%s'; " "document.body.appendChild(frame);", data_url_str.c_str()); EXPECT_TRUE(ExecJs(shell(), js_str)); ASSERT_TRUE(WaitForLoadStop(shell()->web_contents())); } // Verify parent and child frames share a SiteInstance FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); ASSERT_EQ(1U, root->child_count()); FrameTreeNode* child = root->child_at(0); EXPECT_EQ(root->current_frame_host()->GetSiteInstance(), child->current_frame_host()->GetSiteInstance()); // Now make subframe sandboxed. { std::string js_str( "var frame = document.getElementById('test_frame'); " "frame.sandbox = ''; "); EXPECT_TRUE(ExecJs(shell(), js_str)); } NavigateFrameToURL(child, embedded_test_server()->GetURL("b.com", "/title1.html")); // Child should now be in a different SiteInstance. EXPECT_NE(root->current_frame_host()->GetSiteInstance(), child->current_frame_host()->GetSiteInstance()); // Go back and ensure the data: URL committed in the same SiteInstance as the // original navigation. EXPECT_TRUE(web_contents()->GetController().CanGoBack()); { TestFrameNavigationObserver frame_observer(child); web_contents()->GetController().GoBack(); frame_observer.WaitForCommit(); } EXPECT_NE(root->current_frame_host()->GetSiteInstance(), child->current_frame_host()->GetSiteInstance()); EXPECT_EQ(GURL(data_url_str), child->current_frame_host()->GetLastCommittedURL()); } // Test to make sure that a sandboxed child iframe with a data url and a // sandboxed parent end up in the same SiteInstance. IN_PROC_BROWSER_TEST_P(SitePerProcessIsolatedSandboxedIframeTest, SandboxedParentWithSandboxedChildWithDataURL) { GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html")); std::string parent_url_str = main_url.spec(); std::string data_url_str("data:text/html,dataurl"); EXPECT_TRUE(NavigateToURL(shell(), main_url)); // Allow "parent" to have the allow-scripts permissions so it can create // a child. { std::string js_str = base::StringPrintf( "var frame = document.createElement('iframe'); " "frame.sandbox = 'allow-scripts'; " "frame.src = '%s'; " "document.body.appendChild(frame);", parent_url_str.c_str()); EXPECT_TRUE(ExecJs(shell(), js_str)); ASSERT_TRUE(WaitForLoadStop(shell()->web_contents())); } FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); ASSERT_EQ(1U, root->child_count()); FrameTreeNode* child = root->child_at(0); EXPECT_NE(root->current_frame_host()->GetSiteInstance(), child->current_frame_host()->GetSiteInstance()); // Give the grandchild the allow-scripts permissions so it matches the child. { std::string js_str = base::StringPrintf( "var frame = document.createElement('iframe'); " "frame.sandbox = 'allow-scripts'; " "frame.src = '%s'; " "document.body.appendChild(frame);", data_url_str.c_str()); EXPECT_TRUE(ExecJs(child->current_frame_host(), js_str)); ASSERT_TRUE(WaitForLoadStop(shell()->web_contents())); } ASSERT_EQ(1U, child->child_count()); FrameTreeNode* grandchild = child->child_at(0); EXPECT_EQ(child->current_frame_host()->GetSiteInstance(), grandchild->current_frame_host()->GetSiteInstance()); EXPECT_EQ(GURL(data_url_str), grandchild->current_frame_host()->GetLastCommittedURL()); } // Test to make sure that a sandboxed iframe with a (not-explicitly) sandboxed // subframe ends up in the same SiteInstance/process as its subframe. IN_PROC_BROWSER_TEST_P(SitePerProcessIsolatedSandboxedIframeTest, IsolatedSandboxWithNonSandboxedSubframe) { GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html")); // The child needs to have the same origin as the parent. GURL child_url( embedded_test_server()->GetURL("a.com", "/page_with_iframe.html")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); // Create sandboxed child frame, same-origin. { std::string js_str = base::StringPrintf( "var frame = document.createElement('iframe'); " "frame.sandbox = ''; " "frame.src = '%s'; " "document.body.appendChild(frame);", child_url.spec().c_str()); EXPECT_TRUE(ExecJs(shell(), js_str)); ASSERT_TRUE(WaitForLoadStop(shell()->web_contents())); } // Check child vs. parent. FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); ASSERT_EQ(1U, root->child_count()); FrameTreeNode* child = root->child_at(0); EXPECT_EQ(network::mojom::WebSandboxFlags::kAll, child->effective_frame_policy().sandbox_flags); EXPECT_NE(root->current_frame_host()->GetSiteInstance(), child->current_frame_host()->GetSiteInstance()); EXPECT_TRUE(child->current_frame_host() ->GetSiteInstance() ->GetSiteInfo() .is_sandboxed()); // Check grandchild vs. child. ASSERT_EQ(1U, child->child_count()); FrameTreeNode* grand_child = child->child_at(0); EXPECT_EQ(network::mojom::WebSandboxFlags::kAll, grand_child->effective_frame_policy().sandbox_flags); EXPECT_EQ(child->current_frame_host()->GetSiteInstance(), grand_child->current_frame_host()->GetSiteInstance()); } // A test to verify that an iframe with a fully-restrictive sandbox is rendered // in the same process as its parent frame when the parent frame is in a // default SiteInstance. IN_PROC_BROWSER_TEST_P( SitePerProcessIsolatedSandboxWithoutStrictSiteIsolationBrowserTest, NotIsolatedSandbox) { GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html")); // The child needs to have the same origin as the parent. GURL child_url(main_url); EXPECT_TRUE(NavigateToURL(shell(), main_url)); // Create sandboxed child frame, same-origin. { std::string js_str = base::StringPrintf( "var frame = document.createElement('iframe'); " "frame.sandbox = ''; " "frame.src = '%s'; " "document.body.appendChild(frame);", child_url.spec().c_str()); EXPECT_TRUE(ExecJs(shell(), js_str)); ASSERT_TRUE(WaitForLoadStop(shell()->web_contents())); } // Check frame-tree. FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); ASSERT_EQ(1U, root->child_count()); FrameTreeNode* child = root->child_at(0); auto* parent_site_instance = root->current_frame_host()->GetSiteInstance(); auto* child_site_instance = child->current_frame_host()->GetSiteInstance(); EXPECT_EQ(network::mojom::WebSandboxFlags::kAll, child->effective_frame_policy().sandbox_flags); EXPECT_FALSE(parent_site_instance->RequiresDedicatedProcess()); EXPECT_EQ(parent_site_instance, child_site_instance); EXPECT_FALSE(child_site_instance->GetSiteInfo().is_sandboxed()); } // Similar to the NotIsolatedSandbox test, but using a site that requires a // dedicated process, and thus resulting in a separate process for the sandboxed // iframe. IN_PROC_BROWSER_TEST_P( SitePerProcessIsolatedSandboxWithoutStrictSiteIsolationBrowserTest, IsolatedSandbox) { // Specify an isolated.com site to get the main frame into a dedicated // process. GURL main_url(embedded_test_server()->GetURL("isolated.com", "/title1.html")); // The child needs to have the same origin as the parent. GURL child_url(main_url); EXPECT_TRUE(NavigateToURL(shell(), main_url)); // Create sandboxed child frame, same-origin. { std::string js_str = base::StringPrintf( "var frame = document.createElement('iframe'); " "frame.sandbox = ''; " "frame.src = '%s'; " "document.body.appendChild(frame);", child_url.spec().c_str()); EXPECT_TRUE(ExecJs(shell(), js_str)); ASSERT_TRUE(WaitForLoadStop(shell()->web_contents())); } // Check frame-tree. FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); ASSERT_EQ(1U, root->child_count()); FrameTreeNode* child = root->child_at(0); auto* parent_site_instance = root->current_frame_host()->GetSiteInstance(); auto* child_site_instance = child->current_frame_host()->GetSiteInstance(); EXPECT_EQ(network::mojom::WebSandboxFlags::kAll, child->effective_frame_policy().sandbox_flags); EXPECT_TRUE(parent_site_instance->RequiresDedicatedProcess()); EXPECT_NE(parent_site_instance, child_site_instance); EXPECT_NE(parent_site_instance->GetProcess(), child_site_instance->GetProcess()); EXPECT_TRUE(child_site_instance->GetSiteInfo().is_sandboxed()); } // In this test, a main frame requests sandbox isolation for a site that would // not normally be given a dedicated process. This causes the sandbox isolation // request to fail. IN_PROC_BROWSER_TEST_P( SitePerProcessIsolatedSandboxWithoutStrictSiteIsolationBrowserTest, CSPSandboxedMainFrame) { GURL main_url(embedded_test_server()->GetURL( "a.com", "/set-header?Content-Security-Policy: sandbox allow-scripts")); GURL child_url(embedded_test_server()->GetURL("a.com", "/title1.html")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); // Create sandboxed child frame, same-origin. { std::string js_str = base::StringPrintf( "var frame = document.createElement('iframe'); " "frame.sandbox = ''; " "frame.src = '%s'; " "document.body.appendChild(frame);", child_url.spec().c_str()); EXPECT_TRUE(ExecJs(shell(), js_str)); ASSERT_TRUE(WaitForLoadStop(shell()->web_contents())); } // Check frame-tree. FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); ASSERT_EQ(1U, root->child_count()); FrameTreeNode* child = root->child_at(0); auto* parent_site_instance = root->current_frame_host()->GetSiteInstance(); auto* child_site_instance = child->current_frame_host()->GetSiteInstance(); EXPECT_FALSE(parent_site_instance->RequiresDedicatedProcess()); EXPECT_FALSE(parent_site_instance->GetSiteInfo().is_sandboxed()); // TODO(wjmaclean): It seems weird that the // effective_frame_policy().sandbox_flags don't get set in this case. Maybe // worth investigating this at some point. https://crbug.com/1346723 EXPECT_EQ(network::mojom::WebSandboxFlags::kNone, root->effective_frame_policy().sandbox_flags); // Since the parent is sandboxed, the child is same process to it. EXPECT_EQ(parent_site_instance, child_site_instance); } // Same as CSPSandboxedMainframe, but this time the site is isolatable on its // own, so it gets the sandbox attribute via the CSP header. IN_PROC_BROWSER_TEST_P( SitePerProcessIsolatedSandboxWithoutStrictSiteIsolationBrowserTest, CSPSandboxedMainframeIsolated) { GURL main_url(embedded_test_server()->GetURL( "isolated.com", "/set-header?Content-Security-Policy: sandbox allow-scripts")); GURL child_url( embedded_test_server()->GetURL("isolated.com", "/title1.html")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); // Create sandboxed child frame, same-origin. { std::string js_str = base::StringPrintf( "var frame = document.createElement('iframe'); " "frame.sandbox = ''; " "frame.src = '%s'; " "document.body.appendChild(frame);", child_url.spec().c_str()); EXPECT_TRUE(ExecJs(shell(), js_str)); ASSERT_TRUE(WaitForLoadStop(shell()->web_contents())); } // Check frame-tree. FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); ASSERT_EQ(1U, root->child_count()); FrameTreeNode* child = root->child_at(0); auto* parent_site_instance = root->current_frame_host()->GetSiteInstance(); auto* child_site_instance = child->current_frame_host()->GetSiteInstance(); EXPECT_TRUE(parent_site_instance->RequiresDedicatedProcess()); EXPECT_TRUE(parent_site_instance->GetSiteInfo().is_sandboxed()); // TODO(wjmaclean): It seems weird that the // effective_frame_policy().sandbox_flags don't get set in this case. Maybe // worth investigating this at some point. https://crbug.com/1346723 EXPECT_EQ(network::mojom::WebSandboxFlags::kNone, root->effective_frame_policy().sandbox_flags); // Since the parent is sandboxed, the child is same process to it. // Note: this assumes that we are running per-site isolation mode for isolated // sandboxed iframes. EXPECT_EQ(parent_site_instance, child_site_instance); } // Test to verify which IsolationContext is used when a BrowsingInstance swap is // performed during a navigation. // Note: this test does not work as hoped, see comment before the final // expectation of the test. IN_PROC_BROWSER_TEST_P( SitePerProcessIsolatedSandboxWithoutStrictSiteIsolationBrowserTest, MainFrameBrowsingInstanceSwap) { GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); scoped_refptr<SiteInstanceImpl> site_instance_a = web_contents()->GetSiteInstance(); EXPECT_FALSE(site_instance_a->GetSiteInfo().is_sandboxed()); // Force BrowsingInstance swap to a URL with a CSP sandbox header. GURL isolated_url(embedded_test_server()->GetURL( "b.com", "/set-header?Content-Security-Policy: sandbox")); SiteInstance::StartIsolatingSite( shell()->web_contents()->GetController().GetBrowserContext(), isolated_url, content::ChildProcessSecurityPolicy::IsolatedOriginSource::TEST); EXPECT_TRUE(NavigateToURL(shell(), isolated_url)); scoped_refptr<SiteInstanceImpl> site_instance_b = web_contents()->GetSiteInstance(); EXPECT_NE(site_instance_a, site_instance_b); EXPECT_NE(site_instance_a->GetIsolationContext().browsing_instance_id(), site_instance_b->GetIsolationContext().browsing_instance_id()); // The SiteInstance is not considered sandboxed even in the new // BrowsingInstance. This is not the result we wanted, but without a massive // amount of work it's the best we can do. This happens because // NavigationRequest::GetUrlInfo() doesn't know (at the time // NavigationRequest::OnResponseStarted() calls // RenderFrameHostManager::GetFrameHostForNavigation()) that there will be // a BrowsingInstance swap, and it doesn't have access to the new // BrowsingInstance (IsolationContext) when deciding to add the `is_sandboxed` // attribute to UrlInfoInit. // This is an edge case we can live with since it only happens with the // main frame getting a CSP sandbox, and the main frame does get its own // process regardless in this case. EXPECT_FALSE(site_instance_b->GetSiteInfo().is_sandboxed()); } IN_PROC_BROWSER_TEST_P( SitePerProcessIsolatedSandboxWithoutStrictSiteIsolationBrowserTest, MainFrameWithSandboxedOpener) { // Specify an isolated.com site to get the main frame into a dedicated // process. GURL main_url(embedded_test_server()->GetURL("isolated.com", "/title1.html")); // The child needs to have the same origin as the parent. GURL child_url(main_url); EXPECT_TRUE(NavigateToURL(shell(), main_url)); // Create sandboxed child frame, same-origin. { std::string js_str = base::StringPrintf( "var frame = document.createElement('iframe'); " "frame.sandbox = 'allow-scripts allow-popups'; " "frame.src = '%s'; " "document.body.appendChild(frame);", child_url.spec().c_str()); EXPECT_TRUE(ExecJs(shell(), js_str)); ASSERT_TRUE(WaitForLoadStop(shell()->web_contents())); } // Check frame-tree. FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); ASSERT_EQ(1U, root->child_count()); FrameTreeNode* child = root->child_at(0); auto* parent_site_instance = root->current_frame_host()->GetSiteInstance(); auto* child_site_instance = child->current_frame_host()->GetSiteInstance(); network::mojom::WebSandboxFlags expected_flags = network::mojom::WebSandboxFlags::kAll & ~network::mojom::WebSandboxFlags::kScripts & ~network::mojom::WebSandboxFlags::kPopups & ~network::mojom::WebSandboxFlags::kAutomaticFeatures & ~network::mojom::WebSandboxFlags::kTopNavigationToCustomProtocols; EXPECT_EQ(expected_flags, child->effective_frame_policy().sandbox_flags); EXPECT_TRUE(parent_site_instance->RequiresDedicatedProcess()); EXPECT_NE(parent_site_instance, child_site_instance); EXPECT_TRUE(child_site_instance->GetSiteInfo().is_sandboxed()); // Sandboxed child calls window.open. Shell* new_shell = OpenPopup(child, child_url, ""); EXPECT_TRUE(new_shell); FrameTreeNode* new_root = static_cast<WebContentsImpl*>(new_shell->web_contents()) ->GetPrimaryFrameTree() .root(); auto* new_window_site_instance = new_root->current_frame_host()->GetSiteInstance(); EXPECT_TRUE(new_window_site_instance->RequiresDedicatedProcess()); EXPECT_TRUE(new_window_site_instance->GetSiteInfo().is_sandboxed()); // Note: this assumes per-site mode for sandboxed iframe isolation. If we // settle on per-document mode, this will change to EXPECT_NE. EXPECT_EQ(child_site_instance, new_window_site_instance); } // Test that sandboxed iframes that are same-site with their parent but // same-origin to each other are put in different processes from each other, // when the 'per-document' isolation grouping is active for // kIsolateSandboxedIframes. (In 'per-site' and 'per-origin' isolation groupings // they would be in the same process.) IN_PROC_BROWSER_TEST_P(SitePerProcessPerDocumentIsolatedSandboxedIframeTest, SameOriginIsolatedSandboxedIframes) { GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html")); // The children need to be same origin to each other, and be (at least) // same-site to the parent. GURL same_origin_child_url( embedded_test_server()->GetURL("sub.a.com", "/title1.html")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); // Create sandboxed child frames. { std::string js_str = base::StringPrintf( "var frame1 = document.createElement('iframe'); " "frame1.sandbox = ''; " "frame1.src = '%s'; " "document.body.appendChild(frame1); " "var frame2 = document.createElement('iframe'); " "frame2.sandbox = ''; " "frame2.src = '%s'; " "document.body.appendChild(frame2);", same_origin_child_url.spec().c_str(), same_origin_child_url.spec().c_str()); EXPECT_TRUE(ExecJs(shell(), js_str)); ASSERT_TRUE(WaitForLoadStop(shell()->web_contents())); } // Check frame-tree. FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); ASSERT_EQ(2U, root->child_count()); FrameTreeNode* child1 = root->child_at(0); // sub.a.com EXPECT_EQ(network::mojom::WebSandboxFlags::kAll, child1->effective_frame_policy().sandbox_flags); EXPECT_NE(root->current_frame_host()->GetSiteInstance(), child1->current_frame_host()->GetSiteInstance()); EXPECT_TRUE(child1->current_frame_host() ->GetSiteInstance() ->GetSiteInfo() .is_sandboxed()); EXPECT_FALSE(root->current_frame_host() ->GetSiteInstance() ->GetSiteInfo() .is_sandboxed()); FrameTreeNode* child2 = root->child_at(1); // sub.a.com EXPECT_EQ(network::mojom::WebSandboxFlags::kAll, child2->effective_frame_policy().sandbox_flags); EXPECT_NE(root->current_frame_host()->GetSiteInstance(), child2->current_frame_host()->GetSiteInstance()); EXPECT_TRUE(child2->current_frame_host() ->GetSiteInstance() ->GetSiteInfo() .is_sandboxed()); // This is the key result for this test: the sandboxed iframes for both child // frames should be in different SiteInstances, even though they are // same-origin. auto* child1_site_instance = child1->current_frame_host()->GetSiteInstance(); auto* child2_site_instance = child2->current_frame_host()->GetSiteInstance(); EXPECT_EQ(child1_site_instance->GetSiteInfo().site_url(), child2_site_instance->GetSiteInfo().site_url()); EXPECT_NE(child1_site_instance->GetSiteInfo().unique_sandbox_id(), child2_site_instance->GetSiteInfo().unique_sandbox_id()); EXPECT_NE(child1_site_instance, child2_site_instance); EXPECT_NE(child1_site_instance->GetProcess(), child2_site_instance->GetProcess()); } // This test ensures that nested srcdoc iframes get correct base urls. IN_PROC_BROWSER_TEST_P(SitePerProcessIsolatedSandboxedIframeTest, NestedSrcdocIframes) { GURL main_url(embedded_test_server()->GetURL("a.com", "/title2.html")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); // Create sandboxed srcdoc child frame. { std::string js_str = "var frame = document.createElement('iframe'); " "frame.sandbox = 'allow-scripts'; " "frame.srcdoc = 'foo'; " "document.body.appendChild(frame);"; EXPECT_TRUE(ExecJs(shell(), js_str)); ASSERT_TRUE(WaitForLoadStop(shell()->web_contents())); } FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); ASSERT_EQ(1U, root->child_count()); FrameTreeNode* child = root->child_at(0); // Make sure the parent's base url propagates properly to the child. GURL parent_base_url = GetFrameBaseUrl(root->current_frame_host()); GURL child_base_url = GetFrameBaseUrl(child->current_frame_host()); // Verify child inherited base url from parent as expected. EXPECT_EQ(parent_base_url, child_base_url); EXPECT_EQ(parent_base_url, child->current_frame_host()->GetInheritedBaseUrl()); // Switch the base url of the root. GURL new_root_base_url("http://b.com/"); { std::string js_str = base::StringPrintf( "var base_element = document.createElement('base'); " "base_element.href = '%s'; " "document.head.appendChild(base_element);", new_root_base_url.spec().c_str()); EXPECT_TRUE(ExecJs(shell(), js_str)); EXPECT_EQ(new_root_base_url, GetFrameBaseUrl(shell())); } // Create sandboxed srcdoc grandchild frame. { std::string js_str = "var frame = document.createElement('iframe'); " "frame.sandbox = 'allow-scripts'; " "frame.srcdoc = 'foo'; " "document.body.appendChild(frame);"; EXPECT_TRUE(ExecJs(child->current_frame_host(), js_str)); ASSERT_TRUE(WaitForLoadStop(shell()->web_contents())); } ASSERT_EQ(1U, child->child_count()); FrameTreeNode* grandchild = child->child_at(0); // Make sure the child's snapshotted base url propagates properly to the // grandchild. And make sure child's snapshotted base url hasn't changed with // the creation of the child. EXPECT_EQ(parent_base_url, GetFrameBaseUrl(child->current_frame_host())); EXPECT_EQ(parent_base_url, child->current_frame_host()->GetInheritedBaseUrl()); EXPECT_EQ(parent_base_url, GetFrameBaseUrl(grandchild->current_frame_host())); EXPECT_EQ(parent_base_url, grandchild->current_frame_host()->GetInheritedBaseUrl()); } // Test to verify that nested sandboxed iframes aren't put in the same // SiteInstance. IN_PROC_BROWSER_TEST_P(SitePerProcessPerDocumentIsolatedSandboxedIframeTest, NestedIsolatedSandboxedIframes) { GURL main_url(embedded_test_server()->GetURL("a.com", "/title2.html")); // The children need to be same origin to each other, and be (at least) // same-site to the parent. GURL same_origin_child_url( embedded_test_server()->GetURL("a.com", "/title1.html")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); // Create sandboxed child frame. { std::string js_str = base::StringPrintf( "var frame = document.createElement('iframe'); " "frame.sandbox = 'allow-scripts'; " "frame.src = '%s'; " "document.body.appendChild(frame);", same_origin_child_url.spec().c_str()); EXPECT_TRUE(ExecJs(shell(), js_str)); ASSERT_TRUE(WaitForLoadStop(shell()->web_contents())); } FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); ASSERT_EQ(1U, root->child_count()); FrameTreeNode* child = root->child_at(0); // Create sandboxed grand-child frame. { std::string js_str = base::StringPrintf( "var frame = document.createElement('iframe'); " "frame.sandbox = ''; " "frame.src = '%s'; " "document.body.appendChild(frame);", same_origin_child_url.spec().c_str()); EXPECT_TRUE(ExecJs(child->current_frame_host(), js_str)); ASSERT_TRUE(WaitForLoadStop(shell()->web_contents())); } ASSERT_EQ(1U, child->child_count()); FrameTreeNode* grandchild = child->child_at(0); // Check frame tree. auto* child_site_instance = child->current_frame_host()->GetSiteInstance(); auto* grandchild_site_instance = grandchild->current_frame_host()->GetSiteInstance(); EXPECT_NE(child_site_instance, grandchild_site_instance); EXPECT_NE(child_site_instance->GetProcess(), grandchild_site_instance->GetProcess()); EXPECT_NE(child_site_instance->GetSiteInfo().unique_sandbox_id(), grandchild_site_instance->GetSiteInfo().unique_sandbox_id()); } // Verify same-document navigations in a sandboxed iframe stay in the same // SiteInstance, and that the unique_sandbox_id changes for any // non-same-document navigation. IN_PROC_BROWSER_TEST_P(SitePerProcessPerDocumentIsolatedSandboxedIframeTest, SandboxedIframeNavigations) { GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html")); // The child needs to be same-site to the parent. GURL same_site_child_url( embedded_test_server()->GetURL("a.com", "/title1.html")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); // Create sandboxed child frame. { std::string js_str = base::StringPrintf( "var frame = document.createElement('iframe'); " "frame.id = 'test_frame'; " "frame.sandbox = ''; " "frame.src = '%s'; " "document.body.appendChild(frame);", same_site_child_url.spec().c_str()); EXPECT_TRUE(ExecJs(shell(), js_str)); ASSERT_TRUE(WaitForLoadStop(shell()->web_contents())); } // Check frame-tree. FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); ASSERT_EQ(1U, root->child_count()); FrameTreeNode* child = root->child_at(0); scoped_refptr<SiteInstanceImpl> root_site_instance = root->current_frame_host()->GetSiteInstance(); EXPECT_FALSE(root_site_instance->GetSiteInfo().is_sandboxed()); scoped_refptr<SiteInstanceImpl> child_site_instance1 = child->current_frame_host()->GetSiteInstance(); EXPECT_EQ(network::mojom::WebSandboxFlags::kAll, child->effective_frame_policy().sandbox_flags); EXPECT_NE(root_site_instance, child_site_instance1); EXPECT_TRUE(child_site_instance1->GetSiteInfo().is_sandboxed()); // Navigate child same-site, same-origin, same-document. { std::string js_str = base::StringPrintf( "var frame = document.getElementById('test_frame'); " "frame.src = '%s#foo';", same_site_child_url.spec().c_str()); EXPECT_TRUE(ExecJs(shell(), js_str)); ASSERT_TRUE(WaitForLoadStop(shell()->web_contents())); } scoped_refptr<SiteInstanceImpl> child_site_instance1a = child->current_frame_host()->GetSiteInstance(); // Since the sandboxed iframe is navigated same-document, we expect the // SiteInstance to remain the same. EXPECT_EQ(child_site_instance1, child_site_instance1a); // Navigate child same-site, same-origin, cross-document. GURL same_site_child_url2( embedded_test_server()->GetURL("a.com", "/title2.html")); ASSERT_TRUE(NavigateIframeToURL(shell()->web_contents(), "test_frame", same_site_child_url2)); scoped_refptr<SiteInstanceImpl> child_site_instance2 = child->current_frame_host()->GetSiteInstance(); // Since the sandboxed iframe is navigated same-site but to a different // document, we expect the SiteInstance to change. EXPECT_NE(child_site_instance1, child_site_instance2); EXPECT_NE(child_site_instance1->GetSiteInfo().unique_sandbox_id(), child_site_instance2->GetSiteInfo().unique_sandbox_id()); // Navigate child cross-site. GURL cross_site_child_url( embedded_test_server()->GetURL("b.com", "/title1.html")); ASSERT_TRUE(NavigateIframeToURL(shell()->web_contents(), "test_frame", cross_site_child_url)); scoped_refptr<SiteInstanceImpl> child_site_instance3 = child->current_frame_host()->GetSiteInstance(); // Since the sandboxed iframe is navigated same-site, but cross-document // we expect the SiteInstance to change. EXPECT_NE(child_site_instance1, child_site_instance3); EXPECT_NE(child_site_instance1->GetSiteInfo().unique_sandbox_id(), child_site_instance3->GetSiteInfo().unique_sandbox_id()); } // Verify that a sandboxed iframe with an about:blank subframe shares its // SiteInstance with that subframe. Further, if the about:blank subframe // navigates cross-site, it gets a new SiteInstance. IN_PROC_BROWSER_TEST_P(SitePerProcessPerDocumentIsolatedSandboxedIframeTest, SandboxedAboutBlankSubframes) { GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html")); // The child needs to be same-site to the parent. GURL same_site_child_url( embedded_test_server()->GetURL("a.com", "/title1.html")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); // Create sandboxed child frame. { std::string js_str = base::StringPrintf( "var frame = document.createElement('iframe'); " "frame.id = 'test_frame'; " "frame.sandbox = 'allow-scripts'; " "frame.src = '%s'; " "document.body.appendChild(frame);", same_site_child_url.spec().c_str()); EXPECT_TRUE(ExecJs(shell(), js_str)); ASSERT_TRUE(WaitForLoadStop(shell()->web_contents())); } // Check frame-tree. FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); ASSERT_EQ(1U, root->child_count()); FrameTreeNode* child = root->child_at(0); // Add about:blank subframe to child. Verify that it stays in its parent's // SiteInstance. { std::string js_str = "var frame = document.createElement('iframe'); " "frame.id = 'test_frame'; " "frame.src = 'about:blank'; " "document.body.appendChild(frame);"; EXPECT_TRUE(ExecJs(child->current_frame_host(), js_str)); ASSERT_TRUE(WaitForLoadStop(shell()->web_contents())); } ASSERT_EQ(1U, child->child_count()); FrameTreeNode* grandchild = child->child_at(0); scoped_refptr<SiteInstanceImpl> child_site_instance = child->current_frame_host()->GetSiteInstance(); scoped_refptr<SiteInstanceImpl> grandchild_site_instance1 = grandchild->current_frame_host()->GetSiteInstance(); EXPECT_EQ(child_site_instance, grandchild_site_instance1); // Navigate the grandchild same-site but cross-document and verify it gets a // new sandboxing id (and therefore a new SiteInstance). GURL cross_document_child_url( embedded_test_server()->GetURL("a.com", "/title2.html")); ASSERT_TRUE(NavigateToURLFromRenderer(grandchild, cross_document_child_url)); scoped_refptr<SiteInstanceImpl> grandchild_site_instance2 = grandchild->current_frame_host()->GetSiteInstance(); EXPECT_NE(child_site_instance, grandchild_site_instance2); EXPECT_NE(child_site_instance->GetSiteInfo().unique_sandbox_id(), grandchild_site_instance2->GetSiteInfo().unique_sandbox_id()); } // Test to verify that sibling srcdoc sandboxed iframes are placed in separate // SiteInstances in the per-document grouping model. IN_PROC_BROWSER_TEST_P(SitePerProcessPerDocumentIsolatedSandboxedIframeTest, SiblingSrcdocIframesGetDifferentProcesses) { // Create any main frame. GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); // Create two identical sibling srcdoc sandboxed iframes. // Create sandboxed child frame, with srcdoc content. { std::string js_str = "var frame1 = document.createElement('iframe'); " "frame1.sandbox = ''; " "frame1.srcdoc = 'srcdoc sandboxed subframe1'; " "var frame2 = document.createElement('iframe'); " "frame2.sandbox = ''; " "frame2.srcdoc = 'srcdoc sandboxed subframe2'; " "document.body.appendChild(frame1); " "document.body.appendChild(frame2);"; EXPECT_TRUE(ExecJs(shell(), js_str)); ASSERT_TRUE(WaitForLoadStop(shell()->web_contents())); } // Check frame tree. FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); ASSERT_EQ(2U, root->child_count()); FrameTreeNode* child1 = root->child_at(0); // frame1 FrameTreeNode* child2 = root->child_at(1); // frame2 auto* root_site_instance = root->current_frame_host()->GetSiteInstance(); auto* child1_site_instance = child1->current_frame_host()->GetSiteInstance(); auto* child2_site_instance = child2->current_frame_host()->GetSiteInstance(); EXPECT_FALSE(root_site_instance->GetSiteInfo().is_sandboxed()); EXPECT_EQ(network::mojom::WebSandboxFlags::kAll, child1->effective_frame_policy().sandbox_flags); EXPECT_TRUE(child1_site_instance->GetSiteInfo().is_sandboxed()); EXPECT_EQ(network::mojom::WebSandboxFlags::kAll, child2->effective_frame_policy().sandbox_flags); EXPECT_TRUE(child2_site_instance->GetSiteInfo().is_sandboxed()); EXPECT_NE(root_site_instance, child1_site_instance); EXPECT_NE(root_site_instance, child2_site_instance); // Verify siblings have different SiteInstances and processes. EXPECT_NE(child1_site_instance, child2_site_instance); EXPECT_NE(child1_site_instance->GetSiteInfo().unique_sandbox_id(), child2_site_instance->GetSiteInfo().unique_sandbox_id()); EXPECT_NE(child1_site_instance->GetProcess(), child2_site_instance->GetProcess()); } // Test that changes to an iframe's srcdoc attribute propagate through the // browser and are stored/cleared on the RenderFrameHost as needed. IN_PROC_BROWSER_TEST_P(SrcdocIsolatedSandboxedIframeTest, SrcdocIframe) { StartEmbeddedServer(); GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); // Create srcdoc iframe. { std::string js_str = "const frame = document.createElement('iframe'); " "frame.id = 'test_frame'; " "frame.srcdoc = 'srcdoc test content'; " "document.body.append(frame);"; EXPECT_TRUE(ExecJs(shell(), js_str)); ASSERT_TRUE(WaitForLoadStop(shell()->web_contents())); } // Verify content on RenderFrameHost. FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) ->GetPrimaryFrameTree() .root(); ASSERT_EQ(1U, root->child_count()); FrameTreeNode* child = root->child_at(0); EXPECT_EQ(GURL(url::kAboutSrcdocURL), child->current_url()); EXPECT_EQ("srcdoc test content", child->srcdoc_value()); if (SiteIsolationPolicy::AreIsolatedSandboxedIframesEnabled()) { EXPECT_EQ(main_url, GetFrameBaseUrl(child->parent())); } if (blink::features::IsNewBaseUrlInheritanceBehaviorEnabled()) { EXPECT_EQ(main_url, child->current_frame_host()->GetInheritedBaseUrl()); } EXPECT_EQ(main_url, GetFrameBaseUrl(child->current_frame_host())); // Reset the srcdoc attribute, and verify the FrameTreeNode is updated // accordingly. { std::string js_str = "const frame = document.getElementById('test_frame'); " "frame.removeAttribute('srcdoc');"; EXPECT_TRUE(ExecJs(shell(), js_str)); ASSERT_TRUE(WaitForLoadStop(shell()->web_contents())); // The next line serves two purposes. First, it confirms via JS that the // srcdoc attribute has indeed been removed. Secondly, and more importantly, // it synchronizes the mojo pipe where the two DidChangeSrcDoc calls occur; // the first call sets the srcdoc value to '' and the second call removes // the BaseUrl. Waiting for loadstop is insufficient to catch the second // call. EXPECT_EQ( false, EvalJs(shell(), "document.getElementById('test_frame').hasAttribute('srcdoc')")); } EXPECT_EQ(GURL(url::kAboutBlankURL), child->current_url()); EXPECT_EQ("", child->srcdoc_value()); // The base url is set on the parent, and not cleared with the child's srcdoc // information. EXPECT_EQ(main_url, GetFrameBaseUrl(child->current_frame_host())); if (blink::features::IsNewBaseUrlInheritanceBehaviorEnabled()) { EXPECT_EQ(main_url, child->current_frame_host()->GetInheritedBaseUrl()); } // Repeat the srcdoc attribute tests from above, but this time using // src='about:srcdoc' to make the frame srcdoc. { std::string js_str = "const frame = document.createElement('iframe'); " "frame.id = 'test_frame2'; " "frame.src = 'about:srcdoc'; " "document.body.append(frame);"; EXPECT_TRUE(ExecJs(shell(), js_str)); ASSERT_TRUE(WaitForLoadStop(shell()->web_contents())); } ASSERT_EQ(2U, root->child_count()); FrameTreeNode* child2 = root->child_at(1); EXPECT_EQ(GURL(url::kAboutSrcdocURL), child2->current_url()); EXPECT_EQ("", child2->srcdoc_value()); EXPECT_EQ(main_url, GetFrameBaseUrl(child->current_frame_host())); EXPECT_EQ(main_url, GetFrameBaseUrl(child2->parent())); EXPECT_EQ(main_url, GetFrameBaseUrl(child2->current_frame_host())); if (blink::features::IsNewBaseUrlInheritanceBehaviorEnabled()) { EXPECT_EQ(main_url, child2->current_frame_host()->GetInheritedBaseUrl()); } // Reset the src attribute, and verify the FrameTreeNode is updated // accordingly. { std::string js_str = "const frame = document.getElementById('test_frame2'); " "frame.removeAttribute('src');"; EXPECT_TRUE(ExecJs(shell(), js_str)); ASSERT_TRUE(WaitForLoadStop(shell()->web_contents())); // The next line serves two purposes. First, it confirms via JS that the // srcdoc attribute has indeed been removed. Secondly, and more importantly, // it synchronizes the mojo pipe where the two DidChangeSrcDoc calls occur; // the first call sets the srcdoc value to '' and the second call removes // the BaseUrl. Waiting for loadstop is insufficient to catch the second // call. EXPECT_EQ( false, EvalJs(shell(), "document.getElementById('test_frame').hasAttribute('src')")); } EXPECT_EQ(GURL(url::kAboutBlankURL), child2->current_url()); EXPECT_EQ("", child2->srcdoc_value()); EXPECT_EQ(GURL("about:blank"), child->current_url()); if (blink::features::IsNewBaseUrlInheritanceBehaviorEnabled()) { EXPECT_EQ(main_url, child->current_frame_host()->GetInheritedBaseUrl()); } } // Test that when a frame changes its base url by manipulating its // base-element, and then undoes those changes, that the browser is properly // notified. IN_PROC_BROWSER_TEST_P(SrcdocIsolatedSandboxedIframeTest, FrameChangesBaseUrl) { StartEmbeddedServer(); GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); FrameTreeNode* root_ftn = static_cast<WebContentsImpl*>(shell()->web_contents()) ->GetPrimaryFrameTree() .root(); // Initially we don't expect the baseurl value to have been sent from the // renderer. EXPECT_EQ(main_url, GetFrameBaseUrl(root_ftn->current_frame_host())); EXPECT_EQ(GURL(), root_ftn->current_frame_host()->GetInheritedBaseUrl()); // The page modifies its base element to set a non-standard value the browser // knows nothing about, so the renderer sends it to the browser. { std::string js_str = "const base_element = document.createElement('base'); " "base_element.id = 'base_element'; " "base_element.href = 'http://foo.com'; " "document.head.append(base_element);"; EXPECT_TRUE(ExecJs(shell(), js_str)); // The following JS is useful, but also forces synchronization on the mojo // pipe that sends the srcdoc base url data. EXPECT_EQ( true, EvalJs(shell(), "document.getElementById('base_element').hasAttribute('href')")); } GURL foo_url("http://foo.com"); EXPECT_EQ(foo_url, GetFrameBaseUrl(root_ftn->current_frame_host())); EXPECT_EQ(GURL(), root_ftn->current_frame_host()->GetInheritedBaseUrl()); // The page removes its base element, restoring the standard baseurl value. // The previous value sent to the browser should be reset. { EXPECT_TRUE(ExecJs(shell(), "document.querySelector('base').remove();")); // The following JS is useful, but also forces synchronization on the mojo // pipe that sends the srcdoc base url data. EXPECT_EQ(true, EvalJs(shell(), "document.getElementById('base_element') == undefined")); } EXPECT_EQ(main_url, GetFrameBaseUrl(root_ftn->current_frame_host())); EXPECT_EQ(GURL(), root_ftn->current_frame_host()->GetInheritedBaseUrl()); } // A test to make sure that a sandboxed srcdoc iframe correctly updates its // base url with the <base> element, and restores the snapshotted base url from // the parent if it removes its <base> element. IN_PROC_BROWSER_TEST_P(SrcdocIsolatedSandboxedIframeTest, SandboxedSrcdocIframeAddsRemovesBaseUrl) { StartEmbeddedServer(); GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); // Create srcdoc iframe with base url from a.com. { std::string js_str = "var frame = document.createElement('iframe'); " "frame.sandbox = 'allow-scripts'; " "frame.srcdoc = 'foo'; " "document.body.appendChild(frame);"; EXPECT_TRUE(ExecJs(shell(), js_str)); ASSERT_TRUE(WaitForLoadStop(shell()->web_contents())); } FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) ->GetPrimaryFrameTree() .root(); auto* child = root->child_at(0); EXPECT_EQ(main_url, GetFrameBaseUrl(child->current_frame_host())); EXPECT_EQ(main_url, GetFrameBaseUrl(root->current_frame_host())); if (blink::features::IsNewBaseUrlInheritanceBehaviorEnabled()) { EXPECT_EQ(main_url, child->current_frame_host()->GetInheritedBaseUrl()); } // Srcdoc frame changes its base url. GURL b_url("http://b.com/"); { std::string js_str = base::StringPrintf( "var base_element = document.createElement('base'); " "base_element.href = '%s'; " "document.head.appendChild(base_element);", b_url.spec().c_str()); EXPECT_TRUE(ExecJs(child, js_str)); EXPECT_EQ(b_url, GetFrameBaseUrl(child->current_frame_host())); } EXPECT_EQ(main_url, GetFrameBaseUrl(root->current_frame_host())); EXPECT_EQ(b_url, GetFrameBaseUrl(child->current_frame_host())); if (blink::features::IsNewBaseUrlInheritanceBehaviorEnabled()) { EXPECT_EQ(main_url, child->current_frame_host()->GetInheritedBaseUrl()); } // Root frame adds base element. GURL c_url("http://c.com/"); { std::string js_str = base::StringPrintf( "var base_element = document.createElement('base'); " "base_element.href = '%s'; " "document.head.appendChild(base_element);", c_url.spec().c_str()); EXPECT_TRUE(ExecJs(root, js_str)); EXPECT_EQ(c_url, GetFrameBaseUrl(root->current_frame_host())); } EXPECT_EQ(b_url, GetFrameBaseUrl(child->current_frame_host())); EXPECT_EQ(c_url, GetFrameBaseUrl(root->current_frame_host())); if (blink::features::IsNewBaseUrlInheritanceBehaviorEnabled()) { EXPECT_EQ(main_url, child->current_frame_host()->GetInheritedBaseUrl()); } // The srcdoc removes its base element. { EXPECT_TRUE(ExecJs(child, "document.querySelector('base').remove();")); if (SiteIsolationPolicy::AreIsolatedSandboxedIframesEnabled()) { EXPECT_EQ(main_url, GetFrameBaseUrl(child->current_frame_host())); } if (blink::features::IsNewBaseUrlInheritanceBehaviorEnabled()) { EXPECT_EQ(main_url, GetFrameBaseUrl(child->current_frame_host())); EXPECT_EQ(main_url, child->current_frame_host()->GetInheritedBaseUrl()); } else { // TODO(wjmaclean): we know this expectation is wrong, and is fixed when // IsNewBaseUrlInheritanceBehaviorEnabled() is true. // https://crbug.com/1356658 EXPECT_EQ(c_url, GetFrameBaseUrl(child->current_frame_host())); } } EXPECT_EQ(c_url, GetFrameBaseUrl(root->current_frame_host())); } // Test that when a sandboxed srcdoc iframe's parent changes its base url, the // srcdoc continues to use the original base url until it reloads. IN_PROC_BROWSER_TEST_P(SrcdocIsolatedSandboxedIframeTest, SrcdocParentChangesBaseUrl) { StartEmbeddedServer(); GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) ->GetPrimaryFrameTree() .root(); GURL b_url("http://b.com/"); { std::string js_str = base::StringPrintf( "var base_element = document.createElement('base'); " "base_element.href = '%s'; " "document.head.appendChild(base_element);", b_url.spec().c_str()); EXPECT_TRUE(ExecJs(root, js_str)); EXPECT_EQ(b_url, GetFrameBaseUrl(root->current_frame_host())); } // Create srcdoc iframe inheriting a base url of b.com. { std::string js_str = "var frame = document.createElement('iframe'); " "frame.id = 'child-srcdoc'; " "frame.sandbox = 'allow-scripts'; " "frame.srcdoc = 'foo'; " "document.body.appendChild(frame);"; EXPECT_TRUE(ExecJs(shell(), js_str)); ASSERT_TRUE(WaitForLoadStop(shell()->web_contents())); } auto* child = root->child_at(0); EXPECT_EQ(b_url, GetFrameBaseUrl(child->current_frame_host())); EXPECT_EQ(b_url, GetFrameBaseUrl(root->current_frame_host())); if (blink::features::IsNewBaseUrlInheritanceBehaviorEnabled()) { EXPECT_EQ(b_url, child->current_frame_host()->GetInheritedBaseUrl()); } // Remove base element from root. EXPECT_TRUE(ExecJs(root, "document.querySelector('base').remove();")); EXPECT_EQ(main_url, GetFrameBaseUrl(root->current_frame_host())); EXPECT_EQ(b_url, GetFrameBaseUrl(child->current_frame_host())); if (blink::features::IsNewBaseUrlInheritanceBehaviorEnabled()) { EXPECT_EQ(b_url, child->current_frame_host()->GetInheritedBaseUrl()); } // Reload child. Since the child is initiating the reload, it should reload // with the same base url it had before the reload. { EXPECT_TRUE(ExecJs(child, "location.reload();")); ASSERT_TRUE(WaitForLoadStop(shell()->web_contents())); } if (blink::features::IsNewBaseUrlInheritanceBehaviorEnabled()) { EXPECT_EQ(b_url, GetFrameBaseUrl(child->current_frame_host())); EXPECT_EQ(b_url, child->current_frame_host()->GetInheritedBaseUrl()); } else { EXPECT_EQ(main_url, GetFrameBaseUrl(child->current_frame_host())); } // Have the parent initiate the reload. This time the parent's original url // should be sent to the child as its base url. { EXPECT_TRUE(ExecJs(shell(), "var frame = document.getElementById('child-srcdoc'); " "frame.srcdoc = frame.srcdoc;")); ASSERT_TRUE(WaitForLoadStop(shell()->web_contents())); } EXPECT_EQ(main_url, GetFrameBaseUrl(child->current_frame_host())); if (blink::features::IsNewBaseUrlInheritanceBehaviorEnabled()) { EXPECT_EQ(main_url, child->current_frame_host()->GetInheritedBaseUrl()); } } // A test to verify that the base url stored in RFHI for an about:srcdoc frame // is cleared when the frame navigates to a non-srcdoc/blank url. IN_PROC_BROWSER_TEST_P(SrcdocIsolatedSandboxedIframeTest, InheritedBaseUrlClearedOnNavigation) { StartEmbeddedServer(); GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html")); GURL child_url(embedded_test_server()->GetURL("a.com", "/title2.html")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) ->GetPrimaryFrameTree() .root(); { std::string js_str = "var frame = document.createElement('iframe'); " "frame.id = 'child-srcdoc'; " "frame.srcdoc = 'foo'; " "document.body.appendChild(frame);"; EXPECT_TRUE(ExecJs(shell(), js_str)); ASSERT_TRUE(WaitForLoadStop(shell()->web_contents())); } auto* child = root->child_at(0); EXPECT_EQ(main_url, GetFrameBaseUrl(child->current_frame_host())); if (blink::features::IsNewBaseUrlInheritanceBehaviorEnabled()) { EXPECT_EQ(main_url, child->current_frame_host()->GetInheritedBaseUrl()); } // Remove the srcdoc attribute from the child frame. This should trigger a // navigation to about:blank. { EXPECT_TRUE(ExecJs(shell(), "var frame = document.getElementById('child-srcdoc'); " "frame.removeAttribute('srcdoc'); ")); ASSERT_TRUE(WaitForLoadStop(shell()->web_contents())); } EXPECT_EQ(GURL("about:blank"), child->current_frame_host()->GetLastCommittedURL()); EXPECT_EQ(main_url, GetFrameBaseUrl(child->current_frame_host())); if (blink::features::IsNewBaseUrlInheritanceBehaviorEnabled()) { EXPECT_EQ(main_url, child->current_frame_host()->GetInheritedBaseUrl()); } // Navigate the subframe to `child_url`. This should remove the inherited base // URL. { std::string js_str = base::StringPrintf( "var frame = document.getElementById('child-srcdoc'); " "frame.src = '%s';", child_url.spec().c_str()); EXPECT_TRUE(ExecJs(shell(), js_str)); ASSERT_TRUE(WaitForLoadStop(shell()->web_contents())); } EXPECT_EQ(child_url, child->current_frame_host()->GetLastCommittedURL()); EXPECT_EQ(child_url, GetFrameBaseUrl(child->current_frame_host())); EXPECT_EQ(GURL(), child->current_frame_host()->GetInheritedBaseUrl()); } // This test verifies that using enterprise policy to disable // NewBaseUrlInheritanceBehavior effectively disables both // the new base url inheritance behavior, and isolation of sandboxed iframes by // forcing both AreIsolatedSandboxedIframesEnabled() and // IsNewBaseUrlInheritanceBehaviorEnabled() to return false. IN_PROC_BROWSER_TEST_P(BaseUrlInheritanceBehaviorEnterprisePolicyTest, VerifyEnterprisePolicyDisables) { EXPECT_FALSE(SiteIsolationPolicy::AreIsolatedSandboxedIframesEnabled()); EXPECT_FALSE(blink::features::IsNewBaseUrlInheritanceBehaviorEnabled()); // Verify that the about:blank window does not get the base url of its // initiator, which is the expected behavior when the // IsolateSandboxedIframes or NewBaseUrlInheritanceBehavior features are // overridden by the enterprise policy. StartEmbeddedServer(); GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); EXPECT_NE(GURL("about:blank"), GetFrameBaseUrl(shell())); FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) ->GetPrimaryFrameTree() .root(); ShellAddedObserver new_shell_observer; EXPECT_TRUE(ExecJs(root, "popup = window.open('about:blank');")); Shell* popup = new_shell_observer.GetShell(); EXPECT_EQ(GURL("about:blank"), GetFrameBaseUrl(popup)); } // A test to verify the initial stages of the initiator base url plumbing work. // The test verifies the value propagates as far as NavigationRequest and // FrameNavigationEntry. The test is based on // SitePerProcessIsolatedSandboxedIframeTest since that will automatically // enable the NewBaseUrlInheritanceBehavior. IN_PROC_BROWSER_TEST_P(SitePerProcessIsolatedSandboxedIframeTest, VerifyBaseUrlPlumbing) { GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html")); // The child needs to have the same origin as the parent. GURL child_url(main_url); EXPECT_TRUE(NavigateToURL(shell(), main_url)); FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); FrameNavigationEntry* root_frame_entry = root->current_frame_host()->last_committed_frame_entry(); ASSERT_TRUE(root_frame_entry); EXPECT_FALSE(root_frame_entry->initiator_base_url().has_value()); // Create srcdoc iframe. Verify the baseurl is plumbed as far as the // FrameNavigationEntry. { std::string js_str = "const frm = document.createElement('iframe'); " "frm.srcdoc = 'foo'; " "document.body.appendChild(frm); "; EXPECT_TRUE(ExecJs(shell(), js_str)); } ASSERT_TRUE(WaitForLoadStop(web_contents())); ASSERT_EQ(1U, root->child_count()); FrameTreeNode* child = root->child_at(0); EXPECT_EQ(main_url, child->current_frame_host()->GetInheritedBaseUrl()); FrameNavigationEntry* child_frame_entry = child->current_frame_host()->last_committed_frame_entry(); ASSERT_TRUE(child_frame_entry); ASSERT_TRUE(child_frame_entry->initiator_base_url().has_value()); EXPECT_EQ(main_url, child_frame_entry->initiator_base_url().value()); // Create about:blank iframe. Verify the baseurl is plumbed as far as the // FrameNavigationEntry. { std::string js_str = "const frm = document.createElement('iframe'); " "frm.src = 'about:blank'; " "document.body.appendChild(frm); "; EXPECT_TRUE(ExecJs(shell(), js_str)); } ASSERT_TRUE(WaitForLoadStop(web_contents())); ASSERT_EQ(2U, root->child_count()); child = root->child_at(1); EXPECT_EQ(main_url, child->current_frame_host()->GetInheritedBaseUrl()); child_frame_entry = child->current_frame_host()->last_committed_frame_entry(); ASSERT_TRUE(child_frame_entry); ASSERT_TRUE(child_frame_entry->initiator_base_url().has_value()); EXPECT_EQ(main_url, child_frame_entry->initiator_base_url().value()); // Renderer-initiated navigation of the top-level frame to about:blank; there // should be an initiator base url. EXPECT_TRUE(ExecJs(shell(), "location = 'about:blank';")); ASSERT_TRUE(WaitForLoadStop(web_contents())); root_frame_entry = root->current_frame_host()->last_committed_frame_entry(); ASSERT_TRUE(root_frame_entry); ASSERT_TRUE(root_frame_entry->initiator_base_url().has_value()); EXPECT_EQ(main_url, root_frame_entry->initiator_base_url().value()); // Browser-initiated navigation of the top-level frame to about:blank; there // should be no initiator base url. EXPECT_TRUE(NavigateToURL(shell(), GURL("about:blank"))); root_frame_entry = root->current_frame_host()->last_committed_frame_entry(); ASSERT_TRUE(root_frame_entry); EXPECT_FALSE(root_frame_entry->initiator_base_url().has_value()); EXPECT_EQ(GURL(), root->current_frame_host()->GetInheritedBaseUrl()); } // This test verifies that a renderer process doesn't crash if a srcdoc calls // document.write on a mainframe parent. IN_PROC_BROWSER_TEST_F(BaseUrlInheritanceBehaviorIframeTest, SrcdocWritesMainFrame) { StartEmbeddedServer(); GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) ->GetPrimaryFrameTree() .root(); // Create srcdoc child. EXPECT_TRUE(ExecJs(root, "var frm = document.createElement('iframe'); " "frm.srcdoc = 'foo'; " "document.body.appendChild(frm);")); ASSERT_EQ(1U, root->child_count()); FrameTreeNode* child = root->child_at(0); // Have the srcdoc child call document.write on the mainframe-parent. std::string test_str("test-complete"); // Since having the child write the parent's document will delete the child, // we use setTimeout to ensure ExecJS returns true, and then wait for the // child's RenderFrameHost to be deleted so we know that the write has // completed. Note: the child's subframe exiting does not mean that its // process, which it shares with the parent, has exited. RenderFrameDeletedObserver observer(child->current_frame_host()); EXPECT_TRUE(ExecJs( child, JsReplace("setTimeout(() => { parent.document.write($1); }, 100);", test_str))); observer.WaitUntilDeleted(); // But fortunately `root` is still valid. EXPECT_EQ(test_str, EvalJs(root, "document.body.innerText").ExtractString()); // If we get here without a crash, we've passed. } // A test to verify that a new about:blank mainframe inherits its base url // from its initiator. IN_PROC_BROWSER_TEST_F(BaseUrlInheritanceBehaviorIframeTest, PopupsInheritBaseUrl) { StartEmbeddedServer(); GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) ->GetPrimaryFrameTree() .root(); ShellAddedObserver new_shell_observer; EXPECT_TRUE(ExecJs(root, "var w = window.open()")); Shell* new_shell = new_shell_observer.GetShell(); WebContentsImpl* new_contents = static_cast<WebContentsImpl*>(new_shell->web_contents()); EXPECT_TRUE(WaitForLoadStop(new_contents)); ASSERT_NE(new_contents, shell()->web_contents()); // The popup should get the same base URL as its initiator. FrameTreeNode* new_root = new_contents->GetPrimaryFrameTree().root(); EXPECT_EQ(EvalJs(root, "document.baseURI").ExtractString(), EvalJs(new_root, "document.baseURI").ExtractString()); } IN_PROC_BROWSER_TEST_F(BaseUrlInheritanceBehaviorIframeTest, AboutBlankInheritsBaseUrlFromSiblingInitiator) { StartEmbeddedServer(); GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) ->GetPrimaryFrameTree() .root(); // Create siblings. EXPECT_TRUE(ExecJs(root, "var frm = document.createElement('iframe'); " "frm.src = 'about:blank'; " "frm.id = 'frm1'; " "document.body.appendChild(frm);")); ASSERT_EQ(1U, root->child_count()); FrameTreeNode* child1 = root->child_at(0); EXPECT_TRUE(ExecJs(root, "var frm = document.createElement('iframe'); " "frm.id = 'frm2'; " "document.body.appendChild(frm);")); ASSERT_EQ(2U, root->child_count()); FrameTreeNode* child2 = root->child_at(1); // First child navigates to about:blank on second child. EXPECT_TRUE(ExecJs(child1, "var base = document.createElement('base'); " "base.href = 'https://example.com'; " "document.head.appendChild(base); " "window.top.window[1].location.href = 'about:blank';")); // Make sure second child inherited base url from the first child. EXPECT_EQ(GURL("https://example.com"), GetFrameBaseUrl(child2->current_frame_host())); } INSTANTIATE_TEST_SUITE_P(All, SitePerProcessIsolatedSandboxedIframeTest, testing::ValuesIn(RenderDocumentFeatureLevelValues())); INSTANTIATE_TEST_SUITE_P(All, SitePerProcessNotIsolatedSandboxedIframeTest, testing::ValuesIn(RenderDocumentFeatureLevelValues())); INSTANTIATE_TEST_SUITE_P(All, SitePerProcessPerOriginIsolatedSandboxedIframeTest, testing::ValuesIn(RenderDocumentFeatureLevelValues())); INSTANTIATE_TEST_SUITE_P( All, SitePerProcessIsolatedSandboxWithoutStrictSiteIsolationBrowserTest, testing::ValuesIn(RenderDocumentFeatureLevelValues())); INSTANTIATE_TEST_SUITE_P(All, SitePerProcessPerDocumentIsolatedSandboxedIframeTest, testing::ValuesIn(RenderDocumentFeatureLevelValues())); INSTANTIATE_TEST_SUITE_P(All, SrcdocIsolatedSandboxedIframeTest, testing::Bool(), [](const testing::TestParamInfo<bool>& info) { return info.param ? "isolated" : "non_isolated"; }); INSTANTIATE_TEST_SUITE_P(All, BaseUrlInheritanceIframeTest, testing::Bool(), [](const testing::TestParamInfo<bool>& info) { return info.param ? "new_base_url_inheritance_behavior" : "legacy_base_url_inheritance_behavior"; }); INSTANTIATE_TEST_SUITE_P(All, BaseUrlInheritanceBehaviorEnterprisePolicyTest, testing::Bool(), [](const testing::TestParamInfo<bool>& info) { return info.param ? "isolated" : "non_isolated"; }); } // namespace content