| // Copyright 2017 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <sstream> |
| #include <tuple> |
| #include <vector> |
| |
| #include "base/command_line.h" |
| #include "base/functional/bind.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/memory/raw_ref.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/test/bind.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "build/build_config.h" |
| #include "content/browser/bad_message.h" |
| #include "content/browser/child_process_security_policy_impl.h" |
| #include "content/browser/origin_agent_cluster_isolation_state.h" |
| #include "content/browser/process_lock.h" |
| #include "content/browser/renderer_host/navigation_request.h" |
| #include "content/browser/renderer_host/navigator.h" |
| #include "content/browser/renderer_host/render_process_host_impl.h" |
| #include "content/browser/site_info.h" |
| #include "content/browser/storage_partition_impl.h" |
| #include "content/browser/web_contents/web_contents_impl.h" |
| #include "content/common/content_navigation_policy.h" |
| #include "content/common/features.h" |
| #include "content/public/browser/browser_or_resource_context.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "content/public/browser/site_isolation_policy.h" |
| #include "content/public/browser/storage_partition_config.h" |
| #include "content/public/common/content_client.h" |
| #include "content/public/common/content_features.h" |
| #include "content/public/common/content_switches.h" |
| #include "content/public/test/back_forward_cache_util.h" |
| #include "content/public/test/browser_test.h" |
| #include "content/public/test/browser_test_utils.h" |
| #include "content/public/test/content_browser_test.h" |
| #include "content/public/test/content_browser_test_utils.h" |
| #include "content/public/test/content_mock_cert_verifier.h" |
| #include "content/public/test/navigation_handle_observer.h" |
| #include "content/public/test/prerender_test_util.h" |
| #include "content/public/test/test_frame_navigation_observer.h" |
| #include "content/public/test/test_navigation_observer.h" |
| #include "content/public/test/test_utils.h" |
| #include "content/public/test/url_loader_interceptor.h" |
| #include "content/shell/browser/shell.h" |
| #include "content/test/content_browser_test_utils_internal.h" |
| #include "content/test/did_commit_navigation_interceptor.h" |
| #include "mojo/public/cpp/bindings/pending_receiver.h" |
| #include "mojo/public/cpp/bindings/receiver_set.h" |
| #include "net/dns/mock_host_resolver.h" |
| #include "net/test/embedded_test_server/embedded_test_server.h" |
| #include "net/test/embedded_test_server/http_request.h" |
| #include "net/test/embedded_test_server/http_response.h" |
| #include "services/network/public/cpp/features.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/blink/public/common/features.h" |
| #include "third_party/blink/public/common/storage_key/storage_key.h" |
| #include "third_party/blink/public/common/tokens/tokens.h" |
| #include "third_party/blink/public/mojom/dom_storage/dom_storage.mojom-test-utils.h" |
| #include "url/gurl.h" |
| |
| namespace content { |
| |
| using IsolatedOriginSource = ChildProcessSecurityPolicy::IsolatedOriginSource; |
| |
| // This is a base class for all tests in this class. It does not isolate any |
| // origins and only provides common helper functions to the other test classes. |
| class IsolatedOriginTestBase : public ContentBrowserTest { |
| public: |
| IsolatedOriginTestBase() = default; |
| ~IsolatedOriginTestBase() override = default; |
| |
| IsolatedOriginTestBase(const IsolatedOriginTestBase&) = delete; |
| IsolatedOriginTestBase& operator=(const IsolatedOriginTestBase&) = delete; |
| |
| // Check if `origin` is an isolated origin. This helper is used in tests |
| // that care only about globally applicable isolated origins (not restricted |
| // to a particular BrowsingInstance or profile). |
| bool IsIsolatedOrigin(const url::Origin& origin) { |
| auto* policy = ChildProcessSecurityPolicyImpl::GetInstance(); |
| IsolationContext isolation_context( |
| shell()->web_contents()->GetBrowserContext()); |
| return policy->IsIsolatedOrigin(isolation_context, origin, |
| false /* origin_requests_isolation */); |
| } |
| |
| bool IsIsolatedOrigin(const GURL& url) { |
| return IsIsolatedOrigin(url::Origin::Create(url)); |
| } |
| |
| OriginAgentClusterIsolationState MakeOACIsolationState( |
| bool requires_origin_keyed_process) { |
| // Assume |requires_origin_keyed_process| is the same as |
| // |is_origin_agent_cluster| here. |
| if (!requires_origin_keyed_process) { |
| return OriginAgentClusterIsolationState::CreateNonIsolated(); |
| } |
| return OriginAgentClusterIsolationState::CreateForOriginAgentCluster( |
| requires_origin_keyed_process); |
| } |
| |
| bool ShouldOriginGetOptInProcessIsolation(const url::Origin& origin) { |
| auto* site_instance = static_cast<SiteInstanceImpl*>( |
| shell()->web_contents()->GetPrimaryMainFrame()->GetSiteInstance()); |
| |
| OriginAgentClusterIsolationState isolation_request = |
| OriginAgentClusterIsolationState::CreateNonIsolated(); |
| |
| return ChildProcessSecurityPolicyImpl::GetInstance() |
| ->DetermineOriginAgentClusterIsolation( |
| site_instance->GetIsolationContext(), origin, isolation_request) |
| .requires_origin_keyed_process(); |
| } |
| |
| // Assuming no additional explicit opt-in or opt-out was requested, check what |
| // isolation state would currently be used for a navigation to |url| in |
| // |site_instance| in the test, based on the current state in the |
| // BrowsingInstance. |
| OriginAgentClusterIsolationState DetermineOriginAgentClusterIsolation( |
| SiteInstanceImpl* site_instance, |
| const GURL& url) { |
| OriginAgentClusterIsolationState isolation_request = |
| OriginAgentClusterIsolationState::CreateForDefaultIsolation( |
| shell()->web_contents()->GetBrowserContext()); |
| |
| return ChildProcessSecurityPolicyImpl::GetInstance() |
| ->DetermineOriginAgentClusterIsolation( |
| site_instance->GetIsolationContext(), url::Origin::Create(url), |
| isolation_request); |
| } |
| |
| ProcessLock ProcessLockFromUrl(const std::string& url) { |
| BrowserContext* browser_context = web_contents()->GetBrowserContext(); |
| return ProcessLock::FromSiteInfo(SiteInfo( |
| GURL(url), GURL(url), false /* requires_origin_keyed_process */, |
| false /* is_sandboxed */, UrlInfo::kInvalidUniqueSandboxId, |
| StoragePartitionConfig::CreateDefault(browser_context), |
| WebExposedIsolationInfo::CreateNonIsolated(), false /* is_guest */, |
| false /* does_site_request_dedicated_process_for_coop */, |
| false /* is_jit_disabled */, false /* is_pdf */, |
| false /* is_fenced */)); |
| } |
| |
| WebContentsImpl* web_contents() const { |
| return static_cast<WebContentsImpl*>(shell()->web_contents()); |
| } |
| |
| // Helper function that computes an appropriate process lock that corresponds |
| // to `url`'s origin (without converting to sites, handling effective URLs, |
| // etc). This must be equivalent to what |
| // SiteInstanceImpl::DetermineProcessLockURL() would return |
| // for strict origin isolation. |
| // Note: do not use this for opt-in origin isolation, as it won't set |
| // requires_origin_keyed_process to true. |
| ProcessLock GetStrictProcessLock(const GURL& url) { |
| BrowserContext* browser_context = web_contents()->GetBrowserContext(); |
| GURL origin_url = url::Origin::Create(url).GetURL(); |
| return ProcessLock::FromSiteInfo(SiteInfo( |
| origin_url, origin_url, false /* requires_origin_keyed_process */, |
| false /* is_sandboxed */, UrlInfo::kInvalidUniqueSandboxId, |
| StoragePartitionConfig::CreateDefault(browser_context), |
| WebExposedIsolationInfo::CreateNonIsolated(), false /* is_guest */, |
| false /* does_site_request_dedicated_process_for_coop */, |
| false /* is_jit_disabled */, false /* is_pdf */, |
| false /* is_fenced */)); |
| } |
| |
| protected: |
| void SetUpOnMainThread() override { |
| ContentBrowserTest::SetUpOnMainThread(); |
| mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK); |
| } |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| ContentBrowserTest::SetUpCommandLine(command_line); |
| mock_cert_verifier_.SetUpCommandLine(command_line); |
| } |
| |
| void SetUpInProcessBrowserTestFixture() override { |
| ContentBrowserTest::SetUpInProcessBrowserTestFixture(); |
| mock_cert_verifier_.SetUpInProcessBrowserTestFixture(); |
| } |
| |
| void TearDownInProcessBrowserTestFixture() override { |
| ContentBrowserTest::TearDownInProcessBrowserTestFixture(); |
| mock_cert_verifier_.TearDownInProcessBrowserTestFixture(); |
| } |
| |
| private: |
| content::ContentMockCertVerifier mock_cert_verifier_; |
| }; |
| |
| class IsolatedOriginTest : public IsolatedOriginTestBase { |
| public: |
| IsolatedOriginTest() = default; |
| ~IsolatedOriginTest() override = default; |
| |
| IsolatedOriginTest(const IsolatedOriginTest&) = delete; |
| IsolatedOriginTest& operator=(const IsolatedOriginTest&) = delete; |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| IsolatedOriginTestBase::SetUpCommandLine(command_line); |
| ASSERT_TRUE(embedded_test_server()->InitializeAndListen()); |
| |
| std::string origin_list = |
| embedded_test_server()->GetURL("isolated.foo.com", "/").spec() + "," + |
| embedded_test_server()->GetURL("isolated.bar.com", "/").spec(); |
| command_line->AppendSwitchASCII(switches::kIsolateOrigins, origin_list); |
| } |
| |
| void SetUpOnMainThread() override { |
| IsolatedOriginTestBase::SetUpOnMainThread(); |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| embedded_test_server()->StartAcceptingConnections(); |
| } |
| |
| void InjectAndClickLinkTo(GURL url) { |
| EXPECT_TRUE(ExecJs(web_contents(), |
| "var link = document.createElement('a');" |
| "link.href = '" + |
| url.spec() + |
| "';" |
| "document.body.appendChild(link);" |
| "link.click();")); |
| } |
| }; |
| |
| // Tests that verify the header can be used to opt-in to origin isolation. |
| class OriginIsolationOptInHeaderTest : public IsolatedOriginTestBase { |
| public: |
| OriginIsolationOptInHeaderTest() |
| : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) { |
| feature_list_.InitWithFeatures( |
| {features::kOriginIsolationHeader}, |
| {blink::features::kOriginAgentClusterDefaultEnabled}); |
| } |
| ~OriginIsolationOptInHeaderTest() override = default; |
| |
| OriginIsolationOptInHeaderTest(const OriginIsolationOptInHeaderTest&) = |
| delete; |
| OriginIsolationOptInHeaderTest& operator=( |
| const OriginIsolationOptInHeaderTest&) = delete; |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| IsolatedOriginTestBase::SetUpCommandLine(command_line); |
| ASSERT_TRUE(embedded_test_server()->InitializeAndListen()); |
| |
| // This is needed for this test to run properly on platforms where |
| // --site-per-process isn't the default, such as Android. |
| IsolateAllSitesForTesting(command_line); |
| |
| // Start the HTTPS server here so derived tests can use it if they override |
| // SetUpCommandLine(). |
| https_server()->AddDefaultHandlers(GetTestDataFilePath()); |
| https_server()->RegisterRequestHandler( |
| base::BindRepeating(&OriginIsolationOptInHeaderTest::HandleResponse, |
| base::Unretained(this))); |
| ASSERT_TRUE(https_server()->Start()); |
| } |
| |
| void SetHeaderValue(const std::string& header_value) { |
| header_ = header_value; |
| } |
| |
| void SetRedirectTarget(const std::string& redirect_target) { |
| redirect_target_ = redirect_target; |
| } |
| |
| // Allows specifying what content to return when an opt-in isolation header is |
| // intercepted. Uses a queue so that multiple requests can be handled without |
| // returning to the test body. If the queue is empty, the document content is |
| // simply "isolate me!". |
| void AddContentToQueue(const std::string& content_str) { |
| content_.push(content_str); |
| } |
| |
| void SetUpOnMainThread() override { |
| IsolatedOriginTestBase::SetUpOnMainThread(); |
| |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| embedded_test_server()->StartAcceptingConnections(); |
| } |
| |
| void TearDownOnMainThread() override { |
| ASSERT_TRUE(https_server()->ShutdownAndWaitUntilComplete()); |
| IsolatedOriginTestBase::TearDownOnMainThread(); |
| } |
| |
| // Need an https server because the header requires HTTPS. |
| net::EmbeddedTestServer* https_server() { return &https_server_; } |
| |
| private: |
| std::unique_ptr<net::test_server::HttpResponse> HandleResponse( |
| const net::test_server::HttpRequest& request) { |
| if (request.relative_url == "/isolate_origin") { |
| auto response = std::make_unique<net::test_server::BasicHttpResponse>(); |
| response->set_code(net::HTTP_OK); |
| response->set_content_type("text/html"); |
| |
| if (header_) { |
| response->AddCustomHeader("Origin-Agent-Cluster", *header_); |
| } |
| |
| if (!content_.empty()) { |
| response->set_content(content_.front()); |
| content_.pop(); |
| } else { |
| response->set_content("isolate me!"); |
| } |
| return std::move(response); |
| } else if (request.relative_url == "/redirect_me") { |
| auto response = std::make_unique<net::test_server::BasicHttpResponse>(); |
| response->set_code(net::HTTP_MOVED_PERMANENTLY); |
| response->AddCustomHeader("Location", *redirect_target_); |
| response->AddCustomHeader("Origin-Agent-Cluster", *header_); |
| response->set_content("redirected"); |
| return std::move(response); |
| } |
| |
| // If we return nullptr, then the server will go ahead and actually serve |
| // the file. |
| return nullptr; |
| } |
| |
| net::EmbeddedTestServer https_server_; |
| base::test::ScopedFeatureList feature_list_; |
| |
| absl::optional<std::string> header_; |
| absl::optional<std::string> redirect_target_; |
| std::queue<std::string> content_; |
| }; |
| |
| // A set of tests that enable OriginAgentCluster by default. |
| class OriginIsolationDefaultOACTest : public OriginIsolationOptInHeaderTest { |
| public: |
| OriginIsolationDefaultOACTest() { |
| feature_list_.InitAndEnableFeature( |
| blink::features::kOriginAgentClusterDefaultEnabled); |
| } |
| |
| ~OriginIsolationDefaultOACTest() override = default; |
| |
| OriginIsolationDefaultOACTest(const OriginIsolationDefaultOACTest&) = delete; |
| OriginIsolationDefaultOACTest& operator=(OriginIsolationDefaultOACTest&) = |
| delete; |
| |
| private: |
| base::test::ScopedFeatureList feature_list_; |
| }; |
| |
| class OriginIsolationPrerenderOptInHeaderTest |
| : public OriginIsolationOptInHeaderTest { |
| public: |
| OriginIsolationPrerenderOptInHeaderTest() |
| : prerender_helper_(base::BindRepeating( |
| &OriginIsolationPrerenderOptInHeaderTest::prerender_web_contents, |
| base::Unretained(this))) {} |
| ~OriginIsolationPrerenderOptInHeaderTest() override = default; |
| |
| OriginIsolationPrerenderOptInHeaderTest( |
| const OriginIsolationPrerenderOptInHeaderTest&) = delete; |
| OriginIsolationPrerenderOptInHeaderTest& operator=( |
| const OriginIsolationPrerenderOptInHeaderTest&) = delete; |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| // This must be called prior to starting the test server. |
| prerender_helper_.SetUp(https_server()); |
| OriginIsolationOptInHeaderTest::SetUpCommandLine(command_line); |
| } |
| |
| void set_prerender_web_contents(WebContents* web_contents) { |
| prerender_web_contents_ = web_contents; |
| } |
| WebContents* prerender_web_contents() { return prerender_web_contents_; } |
| |
| protected: |
| test::PrerenderTestHelper prerender_helper_; |
| |
| private: |
| raw_ptr<WebContents, DanglingUntriaged> prerender_web_contents_; |
| }; // class OriginIsolationPrerenderOptInHeaderTest |
| |
| // As in OriginIsolationOptInHeaderTest, but with same-process origin |
| // isolation. |
| class SameProcessOriginIsolationOptInHeaderTest |
| : public OriginIsolationOptInHeaderTest { |
| public: |
| SameProcessOriginIsolationOptInHeaderTest() = default; |
| ~SameProcessOriginIsolationOptInHeaderTest() override = default; |
| |
| SameProcessOriginIsolationOptInHeaderTest( |
| const SameProcessOriginIsolationOptInHeaderTest&) = delete; |
| SameProcessOriginIsolationOptInHeaderTest& operator=( |
| const SameProcessOriginIsolationOptInHeaderTest&) = delete; |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| OriginIsolationOptInHeaderTest::SetUpCommandLine(command_line); |
| command_line->AppendSwitch(switches::kDisableSiteIsolation); |
| command_line->RemoveSwitch(switches::kSitePerProcess); |
| } |
| }; |
| |
| // As in SameProcessOriginIsolationOptInHeaderTest, but command-line isolate |
| // foo.com. |
| class SameProcessOriginIsolationOptInHeaderWithIsolatedOriginTest |
| : public SameProcessOriginIsolationOptInHeaderTest { |
| public: |
| SameProcessOriginIsolationOptInHeaderWithIsolatedOriginTest() = default; |
| ~SameProcessOriginIsolationOptInHeaderWithIsolatedOriginTest() override = |
| default; |
| |
| SameProcessOriginIsolationOptInHeaderWithIsolatedOriginTest( |
| const SameProcessOriginIsolationOptInHeaderWithIsolatedOriginTest&) = |
| delete; |
| SameProcessOriginIsolationOptInHeaderWithIsolatedOriginTest& operator=( |
| const SameProcessOriginIsolationOptInHeaderWithIsolatedOriginTest&) = |
| delete; |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| SameProcessOriginIsolationOptInHeaderTest::SetUpCommandLine(command_line); |
| command_line->AppendSwitchASCII(switches::kIsolateOrigins, |
| "https://foo.com/"); |
| } |
| }; |
| |
| // Force WebSecurity off for tests. |
| class SameProcessNoWebSecurityOriginIsolationOptInHeaderTest |
| : public SameProcessOriginIsolationOptInHeaderTest { |
| public: |
| SameProcessNoWebSecurityOriginIsolationOptInHeaderTest() = default; |
| ~SameProcessNoWebSecurityOriginIsolationOptInHeaderTest() override = default; |
| |
| // Disallow copy & assign. |
| SameProcessNoWebSecurityOriginIsolationOptInHeaderTest( |
| const SameProcessNoWebSecurityOriginIsolationOptInHeaderTest&) = delete; |
| SameProcessNoWebSecurityOriginIsolationOptInHeaderTest& operator=( |
| const SameProcessNoWebSecurityOriginIsolationOptInHeaderTest&) = delete; |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| SameProcessOriginIsolationOptInHeaderTest::SetUpCommandLine(command_line); |
| command_line->AppendSwitch(switches::kDisableWebSecurity); |
| } |
| }; |
| |
| // Used for a few tests that check non-HTTPS secure context behavior. |
| class OriginIsolationOptInHttpServerHeaderTest : public IsolatedOriginTestBase { |
| public: |
| OriginIsolationOptInHttpServerHeaderTest() = default; |
| |
| OriginIsolationOptInHttpServerHeaderTest( |
| const OriginIsolationOptInHttpServerHeaderTest&) = delete; |
| OriginIsolationOptInHttpServerHeaderTest& operator=( |
| const OriginIsolationOptInHttpServerHeaderTest&) = delete; |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| IsolatedOriginTestBase::SetUpCommandLine(command_line); |
| ASSERT_TRUE(embedded_test_server()->InitializeAndListen()); |
| |
| // This is needed for this test to run properly on platforms where |
| // --site-per-process isn't the default, such as Android. |
| IsolateAllSitesForTesting(command_line); |
| |
| feature_list_.InitAndEnableFeature(features::kOriginIsolationHeader); |
| |
| embedded_test_server()->RegisterRequestHandler(base::BindRepeating( |
| &OriginIsolationOptInHttpServerHeaderTest::HandleResponse, |
| base::Unretained(this))); |
| } |
| |
| void SetUpOnMainThread() override { |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| embedded_test_server()->StartAcceptingConnections(); |
| } |
| |
| private: |
| std::unique_ptr<net::test_server::HttpResponse> HandleResponse( |
| const net::test_server::HttpRequest& request) { |
| auto response = std::make_unique<net::test_server::BasicHttpResponse>(); |
| response->set_code(net::HTTP_OK); |
| response->set_content_type("text/html"); |
| response->AddCustomHeader("Origin-Agent-Cluster", "?1"); |
| |
| response->set_content("isolate me!"); |
| return std::move(response); |
| } |
| |
| base::test::ScopedFeatureList feature_list_; |
| }; |
| |
| // This class allows testing the interaction of OptIn isolation and command-line |
| // isolation for origins. Tests using this class will isolate foo.com and |
| // bar.com by default using command-line isolation, but any opt-in isolation |
| // will override this. |
| class OriginIsolationOptInHeaderCommandLineTest |
| : public OriginIsolationOptInHeaderTest { |
| public: |
| OriginIsolationOptInHeaderCommandLineTest() = default; |
| |
| OriginIsolationOptInHeaderCommandLineTest( |
| const OriginIsolationOptInHeaderCommandLineTest&) = delete; |
| OriginIsolationOptInHeaderCommandLineTest& operator=( |
| const OriginIsolationOptInHeaderCommandLineTest&) = delete; |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| OriginIsolationOptInHeaderTest::SetUpCommandLine(command_line); |
| // The base class should already have started the HTTPS server so we can use |
| // it here to generate origins to specify on the command line. |
| ASSERT_TRUE(https_server()->Started()); |
| |
| std::string origin_list = https_server()->GetURL("foo.com", "/").spec() + |
| "," + |
| https_server()->GetURL("bar.com", "/").spec(); |
| command_line->AppendSwitchASCII(switches::kIsolateOrigins, origin_list); |
| } |
| }; |
| |
| // This test verifies that opt-in isolation takes precedence over command-line |
| // isolation. It loads an opt-in isolated base origin (which would have |
| // otherwise been isolated via command-line isolation), and then loads a child |
| // frame sub-origin which should-not be isolated (but would have been if the |
| // base origin was command-line isolated). |
| IN_PROC_BROWSER_TEST_F(OriginIsolationOptInHeaderCommandLineTest, |
| OptInOverridesCommandLine) { |
| SetHeaderValue("?1"); |
| // Start off with an isolated base-origin in an a(a) configuration, then |
| // navigate the subframe to a sub-origin not requesting isolation. |
| // Note: this works because we serve mock headers with the base origin's html |
| // file, which set the header. |
| GURL isolated_base_origin_url(https_server()->GetURL( |
| "foo.com", "/isolated_base_origin_with_subframe.html")); |
| GURL non_isolated_sub_origin( |
| https_server()->GetURL("non_isolated.foo.com", "/title1.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), isolated_base_origin_url)); |
| // The .html main frame has two iframes, this test only uses the first one. |
| EXPECT_EQ(3u, CollectAllRenderFrameHosts(shell()->web_contents()).size()); |
| |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| FrameTreeNode* child_frame_node = root->child_at(0); |
| |
| EXPECT_TRUE( |
| NavigateToURLFromRenderer(child_frame_node, non_isolated_sub_origin)); |
| |
| auto* policy = ChildProcessSecurityPolicyImpl::GetInstance(); |
| EXPECT_TRUE(policy |
| ->DetermineOriginAgentClusterIsolation( |
| root->current_frame_host() |
| ->GetSiteInstance() |
| ->GetIsolationContext(), |
| url::Origin::Create(isolated_base_origin_url), |
| MakeOACIsolationState(false)) |
| .requires_origin_keyed_process()); |
| EXPECT_FALSE(policy |
| ->DetermineOriginAgentClusterIsolation( |
| root->current_frame_host() |
| ->GetSiteInstance() |
| ->GetIsolationContext(), |
| url::Origin::Create(non_isolated_sub_origin), |
| MakeOACIsolationState(false)) |
| .requires_origin_keyed_process()); |
| // Make sure the child (i.e. sub-origin) is not isolated. |
| EXPECT_NE(root->current_frame_host()->GetSiteInstance(), |
| child_frame_node->current_frame_host()->GetSiteInstance()); |
| EXPECT_EQ( |
| GURL("https://foo.com"), |
| child_frame_node->current_frame_host()->GetSiteInstance()->GetSiteURL()); |
| // The following test passes because IsIsolatedOrigin doesn't distinguish |
| // between command-line isolation and opt-in isolation. |
| EXPECT_TRUE(policy->IsIsolatedOrigin( |
| root->current_frame_host()->GetSiteInstance()->GetIsolationContext(), |
| url::Origin::Create(non_isolated_sub_origin), |
| false /* origin_requests_isolation */)); |
| |
| // Make sure the opt-in isolated origin is origin-keyed, and the non-opt-in |
| // origin is site-keyed. |
| EXPECT_TRUE(root->current_frame_host() |
| ->GetSiteInstance() |
| ->GetSiteInfo() |
| .requires_origin_keyed_process()); |
| EXPECT_FALSE(child_frame_node->current_frame_host() |
| ->GetSiteInstance() |
| ->GetSiteInfo() |
| .requires_origin_keyed_process()); |
| |
| // Make sure the master opt-in list has the base origin isolated and the sub |
| // origin not isolated. |
| BrowserContext* browser_context = web_contents()->GetBrowserContext(); |
| EXPECT_TRUE(policy->HasOriginEverRequestedOriginAgentClusterValue( |
| browser_context, url::Origin::Create(isolated_base_origin_url))); |
| EXPECT_FALSE(policy->HasOriginEverRequestedOriginAgentClusterValue( |
| browser_context, url::Origin::Create(non_isolated_sub_origin))); |
| } |
| |
| // A test to confirm that if an Origin-Agent-Cluster header is encountered (but |
| // not committed) as part of a redirect, that it does not opt-in to |
| // OriginAgentCluster isolation. The setup in this test is subtle, since in |
| // order for the call to NavigationRequest::OnRequestRedirected() to attempt to |
| // create a new SiteInstance, we must load the same origin the redirect wants to |
| // use, and load it without OriginAgentCluster isolation. Prior to the fix for |
| // https://crbug.com/1329061 the redirect would result in opting the origin into |
| // OriginAgentCluster isolation since no global walk is present to detect that |
| // it has already been loaded without. |
| IN_PROC_BROWSER_TEST_F(OriginIsolationOptInHeaderTest, |
| RedirectSameSiteWithOACDoesntOptIn) { |
| GURL main_frame_url(https_server()->GetURL( |
| "foo.com", "/cross_site_iframe_factory.html?foo.com(foo.com)")); |
| GURL redirect_url(https_server()->GetURL("foo.com", "/redirect_me")); |
| GURL expected_commit_url(https_server()->GetURL("foo.com", "/title1.html")); |
| url::Origin origin(url::Origin::Create(main_frame_url)); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), main_frame_url)); |
| EXPECT_FALSE(ShouldOriginGetOptInProcessIsolation(origin)); |
| EXPECT_EQ(2u, CollectAllRenderFrameHosts(shell()->web_contents()).size()); |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| FrameTreeNode* child_frame_node = root->child_at(0); |
| |
| SetRedirectTarget("/title1.html"); |
| SetHeaderValue("?1"); |
| EXPECT_TRUE(NavigateToURLFromRenderer(child_frame_node, redirect_url, |
| expected_commit_url)); |
| // This next line verifies that the OriginAgentCluster header sent with the |
| // 301 redirect failed to opt foo.com into OriginAgentCluster isolation, as |
| // it should. The check will fail if the origin was opted-in to |
| // OriginAgentCluster isolation. |
| EXPECT_FALSE(ShouldOriginGetOptInProcessIsolation(origin)); |
| } |
| |
| // Same as the preceding test, but the redirect is cross-site. |
| IN_PROC_BROWSER_TEST_F(OriginIsolationOptInHeaderTest, |
| RedirectCrossSiteWithOACDoesntOptIn) { |
| GURL main_frame_url(https_server()->GetURL( |
| "foo.com", "/cross_site_iframe_factory.html?foo.com(foo.com)")); |
| GURL redirect_url(https_server()->GetURL("bar.com", "/redirect_me")); |
| GURL expected_commit_url(https_server()->GetURL("foo.com", "/title1.html")); |
| url::Origin origin(url::Origin::Create(main_frame_url)); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), main_frame_url)); |
| EXPECT_FALSE(ShouldOriginGetOptInProcessIsolation(origin)); |
| |
| EXPECT_EQ(2u, CollectAllRenderFrameHosts(shell()->web_contents()).size()); |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| FrameTreeNode* child_frame_node = root->child_at(0); |
| |
| SetRedirectTarget(expected_commit_url.spec()); |
| SetHeaderValue("?1"); |
| EXPECT_TRUE(NavigateToURLFromRenderer(child_frame_node, redirect_url, |
| expected_commit_url)); |
| |
| // This next line verifies that the OriginAgentCluster header sent with the |
| // 301 redirect failed to opt foo.com into OriginAgentCluster isolation, as |
| // it should. The check will fail if the origin was opted-in to |
| // OriginAgentCluster isolation. |
| EXPECT_FALSE(ShouldOriginGetOptInProcessIsolation(origin)); |
| } |
| |
| // This tests that header-based opt-in causes the origin to end up in the |
| // isolated origins list. |
| IN_PROC_BROWSER_TEST_F(OriginIsolationOptInHeaderTest, Basic) { |
| base::HistogramTester histograms; |
| SetHeaderValue("?1"); |
| |
| GURL url(https_server()->GetURL("isolated.foo.com", "/isolate_origin")); |
| url::Origin origin(url::Origin::Create(url)); |
| |
| EXPECT_FALSE(ShouldOriginGetOptInProcessIsolation(origin)); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| EXPECT_TRUE(ShouldOriginGetOptInProcessIsolation(origin)); |
| |
| EXPECT_THAT( |
| histograms.GetAllSamples("Navigation.OriginAgentCluster.Result"), |
| testing::ElementsAre(base::Bucket( |
| static_cast<int>(NavigationRequest::OriginAgentClusterEndResult:: |
| kRequestedAndOriginKeyed), |
| 1))); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(OriginIsolationDefaultOACTest, Basic) { |
| GURL test_url(https_server()->GetURL("foo.com", |
| "/cross_site_iframe_factory.html?" |
| "foo.com(foo.com)")); |
| // We must load the origins to be isolated (or not) into a child frame so that |
| // they all stay in the same BrowsingInstance, since the test relies on |
| // knowing isolation history for the OriginAgentClusterEndResult::*But* cases. |
| // In this test, the convention is: |
| // foo.com is (implicitly) isolated, |
| // isolated.foo.com is (explicitly) isolated, |
| // isolated.bar.com is (implicitly) isolated, and |
| // bar.com is (explicitly) not isolated. |
| EXPECT_TRUE(NavigateToURL(shell(), test_url)); |
| EXPECT_EQ(2u, CollectAllRenderFrameHosts(shell()->web_contents()).size()); |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| FrameTreeNode* child_frame_node = root->child_at(0); |
| |
| // The first three scenarios should all get the isolation status they request |
| // (the "And" cases). |
| { |
| // Explicitly request OriginAgentCluster via the header. |
| SetHeaderValue("?1"); |
| base::HistogramTester histograms; |
| GURL isolated_suborigin_url( |
| https_server()->GetURL("isolated.foo.com", "/isolate_origin")); |
| EXPECT_TRUE( |
| NavigateToURLFromRenderer(child_frame_node, isolated_suborigin_url)); |
| auto* site_instance = |
| child_frame_node->current_frame_host()->GetSiteInstance(); |
| EXPECT_TRUE(DetermineOriginAgentClusterIsolation(site_instance, |
| isolated_suborigin_url) |
| .requires_origin_keyed_process()); |
| |
| EXPECT_THAT( |
| histograms.GetAllSamples("Navigation.OriginAgentCluster.Result"), |
| testing::ElementsAre(base::Bucket( |
| static_cast<int>(NavigationRequest::OriginAgentClusterEndResult:: |
| kExplicitlyRequestedAndOriginKeyed), |
| 1))); |
| } |
| { |
| // Even though this request has no OriginAgentCluster header, it should get |
| // OAC by default. |
| SetHeaderValue(""); |
| base::HistogramTester histograms; |
| GURL default_isolated_url( |
| https_server()->GetURL("isolated.bar.com", "/title1.html")); |
| EXPECT_TRUE( |
| NavigateToURLFromRenderer(child_frame_node, default_isolated_url)); |
| |
| auto* site_instance = |
| child_frame_node->current_frame_host()->GetSiteInstance(); |
| OriginAgentClusterIsolationState isolation_state = |
| DetermineOriginAgentClusterIsolation(site_instance, |
| default_isolated_url); |
| // TODO(wjmaclean): If OriginAgentCluster-by-default transitions to using |
| // process-isolation at some future date, the second expectation below will |
| // need to change to EXPECT_TRUE. |
| EXPECT_TRUE(isolation_state.is_origin_agent_cluster()); |
| EXPECT_FALSE(isolation_state.requires_origin_keyed_process()); |
| |
| EXPECT_THAT( |
| histograms.GetAllSamples("Navigation.OriginAgentCluster.Result"), |
| testing::ElementsAre(base::Bucket( |
| static_cast<int>(NavigationRequest::OriginAgentClusterEndResult:: |
| kNotExplicitlyRequestedAndOriginKeyed), |
| 1))); |
| // Ensure that the implicit case did not do a global walk (which would be |
| // inefficient), by noticing that a hypothetical request for non-isolation |
| // of that origin in the same SiteInstance would succeed. That can only |
| // happen if the implicit case was not recorded in the BrowsingInstance. |
| OriginAgentClusterIsolationState hypothetical_isolation_request = |
| OriginAgentClusterIsolationState::CreateNonIsolated(); |
| OriginAgentClusterIsolationState hypothetical_isolation_state = |
| ChildProcessSecurityPolicyImpl::GetInstance() |
| ->DetermineOriginAgentClusterIsolation( |
| site_instance->GetIsolationContext(), |
| url::Origin::Create(default_isolated_url), |
| hypothetical_isolation_request); |
| EXPECT_FALSE(hypothetical_isolation_state.is_origin_agent_cluster()); |
| } |
| { |
| // The "isolate_origin" path in the url will force the test framework to |
| // include the OriginAgentCluster header. Here we explicitly request not to |
| // have OAC. |
| SetHeaderValue("?0"); |
| base::HistogramTester histograms; |
| GURL explicit_non_isolated_url( |
| https_server()->GetURL("bar.com", "/isolate_origin")); |
| EXPECT_TRUE( |
| NavigateToURLFromRenderer(child_frame_node, explicit_non_isolated_url)); |
| auto* site_instance = |
| child_frame_node->current_frame_host()->GetSiteInstance(); |
| |
| EXPECT_FALSE(DetermineOriginAgentClusterIsolation(site_instance, |
| explicit_non_isolated_url) |
| .is_origin_agent_cluster()); |
| |
| EXPECT_THAT( |
| histograms.GetAllSamples("Navigation.OriginAgentCluster.Result"), |
| testing::ElementsAre(base::Bucket( |
| static_cast<int>(NavigationRequest::OriginAgentClusterEndResult:: |
| kExplicitlyNotRequestedAndNotOriginKeyed), |
| 1))); |
| } |
| |
| // The next three cases should all fail to get the isolation status they |
| // request (the "But" cases). In these cases, URLs from origins we have |
| // already visited in the BrowsingInstance return different OAC header values, |
| // but are forced to stick with their earlier value rather than the newly |
| // requested value. |
| { |
| // Even though the lack of a header would normally lead to default OAC |
| // isolation, the previous explicitly non-isolated visit to this origin |
| // means that this origin will remain not origin keyed. |
| SetHeaderValue(""); |
| base::HistogramTester histograms; |
| GURL url(https_server()->GetURL("bar.com", "/title1.html")); |
| EXPECT_TRUE(NavigateToURLFromRenderer(child_frame_node, url)); |
| auto* site_instance = |
| child_frame_node->current_frame_host()->GetSiteInstance(); |
| |
| EXPECT_FALSE(DetermineOriginAgentClusterIsolation(site_instance, url) |
| .is_origin_agent_cluster()); |
| |
| EXPECT_THAT( |
| histograms.GetAllSamples("Navigation.OriginAgentCluster.Result"), |
| testing::ElementsAre(base::Bucket( |
| static_cast<int>(NavigationRequest::OriginAgentClusterEndResult:: |
| kNotExplicitlyRequestedButNotOriginKeyed), |
| 1))); |
| } |
| |
| { |
| // An explicit opt-out for isolated.bar.com should not be granted given the |
| // previous default-opt-in above. |
| SetHeaderValue("?0"); |
| base::HistogramTester histograms; |
| GURL explicit_non_isolated_url( |
| https_server()->GetURL("isolated.bar.com", "/isolate_origin")); |
| EXPECT_TRUE( |
| NavigateToURLFromRenderer(child_frame_node, explicit_non_isolated_url)); |
| auto* site_instance = |
| child_frame_node->current_frame_host()->GetSiteInstance(); |
| |
| OriginAgentClusterIsolationState isolation_state = |
| DetermineOriginAgentClusterIsolation(site_instance, |
| explicit_non_isolated_url); |
| EXPECT_TRUE(isolation_state.is_origin_agent_cluster()); |
| EXPECT_FALSE(isolation_state.requires_origin_keyed_process()); |
| |
| EXPECT_THAT( |
| histograms.GetAllSamples("Navigation.OriginAgentCluster.Result"), |
| testing::ElementsAre(base::Bucket( |
| static_cast<int>(NavigationRequest::OriginAgentClusterEndResult:: |
| kExplicitlyNotRequestedButOriginKeyed), |
| 1))); |
| } |
| { |
| // Verify that we don't explicitly opt-in an origin that was explicitly |
| // opted-out. |
| SetHeaderValue("?1"); |
| base::HistogramTester histograms; |
| GURL explicit_isolated_url( |
| https_server()->GetURL("bar.com", "/isolate_origin")); |
| EXPECT_TRUE( |
| NavigateToURLFromRenderer(child_frame_node, explicit_isolated_url)); |
| auto* site_instance = |
| child_frame_node->current_frame_host()->GetSiteInstance(); |
| |
| EXPECT_FALSE(DetermineOriginAgentClusterIsolation(site_instance, |
| explicit_isolated_url) |
| .is_origin_agent_cluster()); |
| |
| EXPECT_THAT( |
| histograms.GetAllSamples("Navigation.OriginAgentCluster.Result"), |
| testing::ElementsAre(base::Bucket( |
| static_cast<int>(NavigationRequest::OriginAgentClusterEndResult:: |
| kExplicitlyRequestedButNotOriginKeyed), |
| 1))); |
| } |
| } |
| |
| // These tests ensure that non-HTTPS secure contexts (see |
| // https://w3c.github.io/webappsec-secure-contexts/#is-origin-trustworthy) are |
| // able to use origin isolation. |
| IN_PROC_BROWSER_TEST_F(OriginIsolationOptInHttpServerHeaderTest, Localhost) { |
| GURL url(embedded_test_server()->GetURL("localhost", "/")); |
| url::Origin origin(url::Origin::Create(url)); |
| |
| EXPECT_FALSE(ShouldOriginGetOptInProcessIsolation(origin)); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| EXPECT_TRUE(ShouldOriginGetOptInProcessIsolation(origin)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(OriginIsolationOptInHttpServerHeaderTest, DotLocalhost) { |
| GURL url(embedded_test_server()->GetURL("test.localhost", "/")); |
| url::Origin origin(url::Origin::Create(url)); |
| |
| EXPECT_FALSE(ShouldOriginGetOptInProcessIsolation(origin)); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| EXPECT_TRUE(ShouldOriginGetOptInProcessIsolation(origin)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(OriginIsolationOptInHttpServerHeaderTest, |
| OneTwentySeven) { |
| GURL url(embedded_test_server()->GetURL("127.0.0.1", "/")); |
| url::Origin origin(url::Origin::Create(url)); |
| |
| EXPECT_FALSE(ShouldOriginGetOptInProcessIsolation(origin)); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| EXPECT_TRUE(ShouldOriginGetOptInProcessIsolation(origin)); |
| } |
| |
| // Two tests for basic OAC operation w.r.t. prerendering FrameTrees. |
| |
| // Basic test to make sure an origin opting-in in a primary FrameTree |
| // triggers registration of a non-opting-origin in an existing prerendering |
| // Frametree. |
| IN_PROC_BROWSER_TEST_F(OriginIsolationPrerenderOptInHeaderTest, |
| SimplePrerenderSubOriginIsolationTest) { |
| GURL test_url(https_server()->GetURL("foo.com", |
| "/cross_site_iframe_factory.html?" |
| "foo.com(foo.com)")); |
| // Navigate primary tab to a non-isolated origin. |
| EXPECT_TRUE(NavigateToURL(shell(), test_url)); |
| EXPECT_EQ(2u, CollectAllRenderFrameHosts(shell()->web_contents()).size()); |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| FrameTreeNode* child_frame_node = root->child_at(0); |
| |
| // Create prerender tab, load non-isolated "a.foo.com". |
| Shell* prerender_tab = CreateBrowser(); |
| EXPECT_TRUE(NavigateToURL(prerender_tab, GURL(https_server()->GetURL( |
| "a.foo.com", "/title1.html")))); |
| auto* prerender_web_contents = |
| static_cast<WebContentsImpl*>(prerender_tab->web_contents()); |
| set_prerender_web_contents(prerender_web_contents); |
| GURL non_isolated_origin_url( |
| https_server()->GetURL("a.foo.com", "/title2.html")); |
| |
| int host_id = prerender_helper_.AddPrerender(non_isolated_origin_url); |
| |
| // In primary tab, navigate to an isolated origin. |
| SetHeaderValue("?1"); |
| GURL isolated_suborigin_url( |
| https_server()->GetURL("a.foo.com", "/isolate_origin")); |
| EXPECT_TRUE( |
| NavigateToURLFromRenderer(child_frame_node, isolated_suborigin_url)); |
| EXPECT_NE(root->current_frame_host()->GetSiteInstance(), |
| child_frame_node->current_frame_host()->GetSiteInstance()); |
| EXPECT_TRUE(child_frame_node->current_frame_host() |
| ->GetSiteInstance() |
| ->RequiresDedicatedProcess()); |
| EXPECT_TRUE(child_frame_node->current_frame_host() |
| ->GetSiteInstance() |
| ->GetSiteInfo() |
| .requires_origin_keyed_process()); |
| |
| // Verify in prerender tab that "a.foo.com" is registered as a non-isolated |
| // origin. We must get the SiteInstance() to test from the |
| // PrerenderedMainFrameHost() to make sure the opt-out registration has |
| // propagated to the right place. |
| auto* policy = ChildProcessSecurityPolicyImpl::GetInstance(); |
| auto* prerender_site_instance_impl = static_cast<SiteInstanceImpl*>( |
| prerender_helper_.GetPrerenderedMainFrameHost(host_id) |
| ->GetSiteInstance()); |
| EXPECT_FALSE(policy |
| ->DetermineOriginAgentClusterIsolation( |
| prerender_site_instance_impl->GetIsolationContext(), |
| url::Origin::Create(non_isolated_origin_url), |
| MakeOACIsolationState(true)) |
| .requires_origin_keyed_process()); |
| |
| // Activate the prerendered page and confirm the non-isolated origin remains |
| // non-isolated. |
| prerender_helper_.NavigatePrimaryPage(non_isolated_origin_url); |
| auto* new_prerender_site_instance_impl = static_cast<SiteInstanceImpl*>( |
| prerender_tab->web_contents()->GetSiteInstance()); |
| EXPECT_EQ(prerender_site_instance_impl, new_prerender_site_instance_impl); |
| EXPECT_FALSE(policy |
| ->DetermineOriginAgentClusterIsolation( |
| new_prerender_site_instance_impl->GetIsolationContext(), |
| url::Origin::Create(non_isolated_origin_url), |
| MakeOACIsolationState(true)) |
| .requires_origin_keyed_process()); |
| EXPECT_FALSE(new_prerender_site_instance_impl->GetSiteInfo() |
| .requires_origin_keyed_process()); |
| EXPECT_TRUE(new_prerender_site_instance_impl->GetSiteURL() == |
| GURL("https://foo.com") || |
| new_prerender_site_instance_impl->IsDefaultSiteInstance()); |
| } |
| |
| // Basic test to make sure an origin opting-in in a prerendering FrameTree |
| // triggers registration of a non-opting-origin in an existing primary |
| // Frametree. |
| IN_PROC_BROWSER_TEST_F(OriginIsolationPrerenderOptInHeaderTest, |
| SimplePrerenderSubOriginIsolationTest2) { |
| GURL test_url(https_server()->GetURL("foo.com", |
| "/cross_site_iframe_factory.html?" |
| "foo.com(foo.com)")); |
| EXPECT_TRUE(NavigateToURL(shell(), test_url)); |
| EXPECT_EQ(2u, CollectAllRenderFrameHosts(shell()->web_contents()).size()); |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| FrameTreeNode* child_frame_node = root->child_at(0); |
| // Navigate child frame to a non-isolated origin "a.foo.com". |
| GURL non_isolated_suborigin_url( |
| https_server()->GetURL("a.foo.com", "/title1.html")); |
| EXPECT_TRUE( |
| NavigateToURLFromRenderer(child_frame_node, non_isolated_suborigin_url)); |
| EXPECT_EQ(root->current_frame_host()->GetSiteInstance(), |
| child_frame_node->current_frame_host()->GetSiteInstance()); |
| |
| // Create prerender tab, load isolated "a.foo.com". |
| Shell* prerender_tab = CreateBrowser(); |
| EXPECT_TRUE(NavigateToURL(prerender_tab, GURL(https_server()->GetURL( |
| "a.foo.com", "/title1.html")))); |
| auto* prerender_web_contents = |
| static_cast<WebContentsImpl*>(prerender_tab->web_contents()); |
| set_prerender_web_contents(prerender_web_contents); |
| SetHeaderValue("?1"); |
| GURL isolated_origin_url( |
| https_server()->GetURL("a.foo.com", "/isolate_origin")); |
| |
| int host_id = prerender_helper_.AddPrerender(isolated_origin_url); |
| |
| // Verify origin is isolated in the prerender IsolationContext. |
| auto* policy = ChildProcessSecurityPolicyImpl::GetInstance(); |
| auto* prerender_site_instance_impl = static_cast<SiteInstanceImpl*>( |
| prerender_helper_.GetPrerenderedMainFrameHost(host_id) |
| ->GetSiteInstance()); |
| EXPECT_TRUE(policy |
| ->DetermineOriginAgentClusterIsolation( |
| prerender_site_instance_impl->GetIsolationContext(), |
| url::Origin::Create(isolated_origin_url), |
| MakeOACIsolationState(false)) |
| .requires_origin_keyed_process()); |
| EXPECT_TRUE(prerender_site_instance_impl->RequiresDedicatedProcess()); |
| EXPECT_TRUE(prerender_site_instance_impl->GetSiteInfo() |
| .requires_origin_keyed_process()); |
| |
| // Verify in original tab that "a.foo.com" is now registered as a non-isolated |
| // origin. |
| auto* primary_site_instance_impl = static_cast<SiteInstanceImpl*>( |
| shell()->web_contents()->GetSiteInstance()); |
| EXPECT_FALSE(policy |
| ->DetermineOriginAgentClusterIsolation( |
| primary_site_instance_impl->GetIsolationContext(), |
| url::Origin::Create(isolated_origin_url), |
| MakeOACIsolationState(true)) |
| .requires_origin_keyed_process()); |
| |
| // Activate the prerendered page and confirm the isolated origin remains |
| // isolated. |
| prerender_helper_.NavigatePrimaryPage(isolated_origin_url); |
| auto* new_prerender_site_instance_impl = static_cast<SiteInstanceImpl*>( |
| prerender_tab->web_contents()->GetSiteInstance()); |
| EXPECT_EQ(prerender_site_instance_impl, new_prerender_site_instance_impl); |
| EXPECT_TRUE(policy |
| ->DetermineOriginAgentClusterIsolation( |
| new_prerender_site_instance_impl->GetIsolationContext(), |
| url::Origin::Create(isolated_origin_url), |
| MakeOACIsolationState(false)) |
| .requires_origin_keyed_process()); |
| EXPECT_TRUE(prerender_site_instance_impl->RequiresDedicatedProcess()); |
| EXPECT_TRUE(new_prerender_site_instance_impl->GetSiteInfo() |
| .requires_origin_keyed_process()); |
| } |
| |
| // Further tests deep-dive into various scenarios for the isolation opt-ins. |
| |
| // In this test the sub-origin is isolated because the header requests it. It |
| // will have a different site instance than the main frame. |
| IN_PROC_BROWSER_TEST_F(OriginIsolationOptInHeaderTest, |
| SimpleSubOriginIsolationTest) { |
| base::HistogramTester histograms; |
| SetHeaderValue("?1"); |
| // Start off with an a(a) page, then navigate the subframe to an isolated sub |
| // origin. |
| GURL test_url(https_server()->GetURL("foo.com", |
| "/cross_site_iframe_factory.html?" |
| "foo.com(foo.com)")); |
| GURL isolated_suborigin_url( |
| https_server()->GetURL("isolated.foo.com", "/isolate_origin")); |
| GURL origin_url = url::Origin::Create(isolated_suborigin_url).GetURL(); |
| BrowserContext* browser_context = web_contents()->GetBrowserContext(); |
| auto expected_isolated_suborigin_lock = ProcessLock::FromSiteInfo(SiteInfo( |
| origin_url, origin_url, true /* requires_origin_keyed_process */, |
| false /* is_sandboxed */, UrlInfo::kInvalidUniqueSandboxId, |
| StoragePartitionConfig::CreateDefault(browser_context), |
| WebExposedIsolationInfo::CreateNonIsolated(), false /* is_guest */, |
| false /* does_site_request_dedicated_process_for_coop */, |
| false /* is_jit_disabled */, false /* is_pdf */, false /* is_fenced */)); |
| EXPECT_TRUE(NavigateToURL(shell(), test_url)); |
| EXPECT_EQ(2u, CollectAllRenderFrameHosts(shell()->web_contents()).size()); |
| |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| FrameTreeNode* child_frame_node = root->child_at(0); |
| EXPECT_TRUE( |
| NavigateToURLFromRenderer(child_frame_node, isolated_suborigin_url)); |
| EXPECT_NE(root->current_frame_host()->GetSiteInstance(), |
| child_frame_node->current_frame_host()->GetSiteInstance()); |
| EXPECT_TRUE(child_frame_node->current_frame_host() |
| ->GetSiteInstance() |
| ->RequiresDedicatedProcess()); |
| GURL expected_isolated_sub_origin = |
| url::Origin::Create(isolated_suborigin_url).GetURL(); |
| EXPECT_EQ( |
| expected_isolated_sub_origin, |
| child_frame_node->current_frame_host()->GetSiteInstance()->GetSiteURL()); |
| EXPECT_EQ(expected_isolated_suborigin_lock, |
| ProcessLock::FromSiteInfo(child_frame_node->current_frame_host() |
| ->GetSiteInstance() |
| ->GetSiteInfo())); |
| EXPECT_EQ( |
| ProcessLock::FromSiteInfo(child_frame_node->current_frame_host() |
| ->GetSiteInstance() |
| ->GetSiteInfo()), |
| child_frame_node->current_frame_host()->GetProcess()->GetProcessLock()); |
| |
| EXPECT_THAT( |
| histograms.GetAllSamples("Navigation.OriginAgentCluster.Result"), |
| testing::ElementsAre( |
| base::Bucket( |
| static_cast<int>(NavigationRequest::OriginAgentClusterEndResult:: |
| kNotRequestedAndNotOriginKeyed), |
| 2), |
| base::Bucket( |
| static_cast<int>(NavigationRequest::OriginAgentClusterEndResult:: |
| kRequestedAndOriginKeyed), |
| 1))); |
| } |
| |
| // Check that two same-site Origin-Agent-Cluster subframes in unrelated windows |
| // obey the subframe process reuse policy. |
| IN_PROC_BROWSER_TEST_F(OriginIsolationOptInHeaderTest, |
| OriginAgentClusterProcessReuse) { |
| SetHeaderValue("?1"); |
| // Start off with an a(a) page, then navigate the subframe to an isolated |
| // suborigin. |
| GURL test_url(https_server()->GetURL("foo.com", |
| "/cross_site_iframe_factory.html?" |
| "foo.com(foo.com)")); |
| GURL isolated_suborigin_url( |
| https_server()->GetURL("isolated.foo.com", "/isolate_origin")); |
| EXPECT_TRUE(NavigateToURL(shell(), test_url)); |
| |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| FrameTreeNode* child = root->child_at(0); |
| EXPECT_TRUE(NavigateToURLFromRenderer(child, isolated_suborigin_url)); |
| EXPECT_NE(root->current_frame_host()->GetSiteInstance(), |
| child->current_frame_host()->GetSiteInstance()); |
| EXPECT_TRUE(child->current_frame_host() |
| ->GetSiteInstance() |
| ->GetSiteInfo() |
| .requires_origin_keyed_process()); |
| |
| // Open an unrelated window and set up the same frame hierarchy there. |
| Shell* new_shell = CreateBrowser(); |
| EXPECT_TRUE(NavigateToURL(new_shell, test_url)); |
| FrameTreeNode* new_root = |
| static_cast<WebContentsImpl*>(new_shell->web_contents()) |
| ->GetPrimaryFrameTree() |
| .root(); |
| FrameTreeNode* new_child = new_root->child_at(0); |
| EXPECT_TRUE(NavigateToURLFromRenderer(new_child, isolated_suborigin_url)); |
| EXPECT_NE(new_root->current_frame_host()->GetSiteInstance(), |
| new_child->current_frame_host()->GetSiteInstance()); |
| |
| // Even though the two subframes should be in different BrowsingInstances, |
| // they should share the same process due to the subframe process reuse |
| // policy. |
| EXPECT_FALSE( |
| child->current_frame_host()->GetSiteInstance()->IsRelatedSiteInstance( |
| new_child->current_frame_host()->GetSiteInstance())); |
| EXPECT_EQ(child->current_frame_host()->GetProcess(), |
| new_child->current_frame_host()->GetProcess()); |
| } |
| |
| // In this test the sub-origin is isolated because the header requests it. It |
| // will have the same site instance as the main frame, and it will be in the |
| // same process. |
| IN_PROC_BROWSER_TEST_F(SameProcessOriginIsolationOptInHeaderTest, |
| SimpleSubOriginIsolationTest) { |
| base::HistogramTester histograms; |
| SetHeaderValue("?1"); |
| // Start off with an a(a) page, then navigate the subframe to an isolated sub |
| // origin. |
| GURL test_url(https_server()->GetURL("foo.com", |
| "/cross_site_iframe_factory.html?" |
| "foo.com(foo.com)")); |
| GURL isolated_suborigin_url( |
| https_server()->GetURL("isolated.foo.com", "/isolate_origin")); |
| GURL origin_url = url::Origin::Create(isolated_suborigin_url).GetURL(); |
| EXPECT_FALSE( |
| SiteIsolationPolicy::IsProcessIsolationForOriginAgentClusterEnabled()); |
| EXPECT_TRUE(NavigateToURL(shell(), test_url)); |
| EXPECT_EQ(2u, CollectAllRenderFrameHosts(shell()->web_contents()).size()); |
| |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| FrameTreeNode* child_frame_node = root->child_at(0); |
| EXPECT_TRUE( |
| NavigateToURLFromRenderer(child_frame_node, isolated_suborigin_url)); |
| EXPECT_EQ(root->current_frame_host()->GetSiteInstance(), |
| child_frame_node->current_frame_host()->GetSiteInstance()); |
| EXPECT_FALSE(child_frame_node->current_frame_host() |
| ->GetSiteInstance() |
| ->RequiresDedicatedProcess()); |
| auto* policy = ChildProcessSecurityPolicyImpl::GetInstance(); |
| EXPECT_TRUE(policy |
| ->DetermineOriginAgentClusterIsolation( |
| root->current_frame_host() |
| ->GetSiteInstance() |
| ->GetIsolationContext(), |
| url::Origin::Create(isolated_suborigin_url), |
| MakeOACIsolationState(false)) |
| .is_origin_agent_cluster()); |
| EXPECT_FALSE(policy |
| ->DetermineOriginAgentClusterIsolation( |
| root->current_frame_host() |
| ->GetSiteInstance() |
| ->GetIsolationContext(), |
| url::Origin::Create(isolated_suborigin_url), |
| MakeOACIsolationState(false)) |
| .requires_origin_keyed_process()); |
| EXPECT_TRUE(policy->HasOriginEverRequestedOriginAgentClusterValue( |
| web_contents()->GetBrowserContext(), |
| url::Origin::Create(isolated_suborigin_url))); |
| |
| EXPECT_THAT( |
| histograms.GetAllSamples("Navigation.OriginAgentCluster.Result"), |
| testing::ElementsAre( |
| base::Bucket( |
| static_cast<int>(NavigationRequest::OriginAgentClusterEndResult:: |
| kNotRequestedAndNotOriginKeyed), |
| 2), |
| base::Bucket( |
| static_cast<int>(NavigationRequest::OriginAgentClusterEndResult:: |
| kRequestedAndOriginKeyed), |
| 1))); |
| } |
| |
| // This test is *nearly* the same as SameProcessOriginIsolationOptInHeaderTest. |
| // SimpleSubOriginIsolationTest, but here we have command-line isolated foo.com |
| // so it will be in a site instance with a non-empty ProcessLock. But the |
| // same-process OAC isolated.foo.com will still be in the same SiteInstance, |
| // and checks on the expected ProcessLock for isolated.foo.com should pass, |
| // i.e. it should be the same as for the foo.com process. |
| IN_PROC_BROWSER_TEST_F( |
| SameProcessOriginIsolationOptInHeaderWithIsolatedOriginTest, |
| SimpleSubOriginIsolationTest) { |
| base::HistogramTester histograms; |
| SetHeaderValue("?1"); |
| // Start off with a foo(foo) page, then navigate the subframe to an isolated |
| // sub origin. foo.com is isolated from the command line. |
| GURL test_url(https_server()->GetURL("foo.com", |
| "/cross_site_iframe_factory.html?" |
| "foo.com(foo.com)")); |
| GURL isolated_suborigin_url( |
| https_server()->GetURL("isolated.foo.com", "/isolate_origin")); |
| GURL origin_url = url::Origin::Create(isolated_suborigin_url).GetURL(); |
| EXPECT_FALSE( |
| SiteIsolationPolicy::IsProcessIsolationForOriginAgentClusterEnabled()); |
| EXPECT_TRUE(NavigateToURL(shell(), test_url)); |
| EXPECT_EQ(2u, CollectAllRenderFrameHosts(shell()->web_contents()).size()); |
| |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| FrameTreeNode* child_frame_node = root->child_at(0); |
| EXPECT_TRUE( |
| NavigateToURLFromRenderer(child_frame_node, isolated_suborigin_url)); |
| EXPECT_EQ(root->current_frame_host()->GetSiteInstance(), |
| child_frame_node->current_frame_host()->GetSiteInstance()); |
| EXPECT_TRUE(root->current_frame_host() |
| ->GetSiteInstance() |
| ->RequiresDedicatedProcess()); |
| EXPECT_TRUE(child_frame_node->current_frame_host() |
| ->GetSiteInstance() |
| ->RequiresDedicatedProcess()); |
| ProcessLock root_process_lock = ProcessLock::FromSiteInfo( |
| root->current_frame_host()->GetSiteInstance()->GetSiteInfo()); |
| EXPECT_TRUE(root_process_lock.is_locked_to_site()); |
| EXPECT_EQ(root_process_lock.lock_url(), GURL("https://foo.com/")); |
| auto* policy = ChildProcessSecurityPolicyImpl::GetInstance(); |
| EXPECT_TRUE(policy |
| ->DetermineOriginAgentClusterIsolation( |
| root->current_frame_host() |
| ->GetSiteInstance() |
| ->GetIsolationContext(), |
| url::Origin::Create(isolated_suborigin_url), |
| MakeOACIsolationState(false)) |
| .is_origin_agent_cluster()); |
| EXPECT_FALSE(policy |
| ->DetermineOriginAgentClusterIsolation( |
| root->current_frame_host() |
| ->GetSiteInstance() |
| ->GetIsolationContext(), |
| url::Origin::Create(isolated_suborigin_url), |
| MakeOACIsolationState(false)) |
| .requires_origin_keyed_process()); |
| EXPECT_TRUE(policy->HasOriginEverRequestedOriginAgentClusterValue( |
| web_contents()->GetBrowserContext(), |
| url::Origin::Create(isolated_suborigin_url))); |
| |
| EXPECT_THAT( |
| histograms.GetAllSamples("Navigation.OriginAgentCluster.Result"), |
| testing::ElementsAre( |
| base::Bucket( |
| static_cast<int>(NavigationRequest::OriginAgentClusterEndResult:: |
| kNotRequestedAndNotOriginKeyed), |
| 2), |
| base::Bucket( |
| static_cast<int>(NavigationRequest::OriginAgentClusterEndResult:: |
| kRequestedAndOriginKeyed), |
| 1))); |
| } |
| |
| // Verify OAC is calculated using the base URL when using LoadDataWithBaseURL() |
| // (analogous to Android WebView's loadDataWithBaseURL()) when the actual site |
| // does not specify an Origin-Agent-Cluster value. |
| IN_PROC_BROWSER_TEST_F(SameProcessOriginIsolationOptInHeaderTest, |
| LoadDataWithBaseURLNoOAC) { |
| const GURL test_url = https_server()->GetURL("foo.com", "/title1.html"); |
| |
| TestNavigationObserver navigation_observer(shell()->web_contents(), 1); |
| shell()->LoadDataWithBaseURL( |
| test_url, "<!DOCTYPE html><html><body></body></html>", test_url); |
| navigation_observer.Wait(); |
| |
| // Even though this internally navigates to a data: URL (which would imply |
| // `window.originAgentCluster === true`, the base URL should be used for the |
| // OAC calculation. |
| EXPECT_EQ(false, EvalJs(shell(), "window.originAgentCluster")); |
| EXPECT_TRUE(ExecJs( |
| shell(), "document.body.appendChild(document.createElement('iframe'))")); |
| |
| EXPECT_TRUE(NavigateToURLFromRenderer( |
| ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0), test_url)); |
| EXPECT_EQ(false, |
| EvalJs(ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0), |
| "window.originAgentCluster")); |
| |
| // If OAC is incorrectly calculated for `LoadDataWithBaseURL()`, this will |
| // fail the access checks in Blink because the two browsing contexts will be |
| // treated as cross-origin. |
| EXPECT_EQ("This page has no title.\n\n", |
| EvalJs(shell(), "window[0].document.body.textContent")); |
| } |
| |
| // Verify OAC is calculated using the base URL when using LoadDataWithBaseURL() |
| // (analogous to Android WebView's loadDataWithBaseURL()). Unlike the previous |
| // test, the actual site specifies an Origin-Agent-Cluster value, which should |
| // be ignored. |
| IN_PROC_BROWSER_TEST_F(SameProcessOriginIsolationOptInHeaderTest, |
| LoadDataWithBaseURLWithOAC) { |
| const GURL test_url = https_server()->GetURL("foo.com", "/isolate_origin"); |
| SetHeaderValue("?1"); |
| |
| // `tab2` and `shell()` will be in separate browsing instances. As an |
| // optimization, browsing instances only track OAC consistency if an origin |
| // has ever sent OAC headers. Once an origin has sent OAC headers, this is |
| // tracked globally. |
| // |
| // This navigation marks "foo.com" as having sent OAC headers. This is |
| // important to validate that `LoadDataWithBaseURL()` uses the origin |
| // calculated from the base URL to update the non-isolated origin list in |
| // `shell()`'s browsing instance. If this is not done correctly, then loading |
| // "foo.com/isolate_origin" in the subframe will incorrectly use OAC in the |
| // subframe, which will be inconsistent with the main frame loaded via |
| // `LoadDataWithBaseURL()`. |
| Shell* tab2 = CreateBrowser(); |
| EXPECT_TRUE(NavigateToURL(tab2, test_url)); |
| |
| TestNavigationObserver navigation_observer(shell()->web_contents(), 1); |
| shell()->LoadDataWithBaseURL( |
| test_url, "<!DOCTYPE html><html><body></body></html>", test_url); |
| navigation_observer.Wait(); |
| |
| // Even though this internally navigates to a data: URL (which would imply |
| // `window.originAgentCluster === true`, the base URL should be used for the |
| // OAC calculation. |
| EXPECT_EQ(false, EvalJs(shell(), "window.originAgentCluster")); |
| EXPECT_TRUE(ExecJs( |
| shell(), "document.body.appendChild(document.createElement('iframe'))")); |
| |
| // Even though this navigation sets the OAC header value, it should be |
| // ignored, since the SiteInstance for foo.com is already site-keyed. |
| EXPECT_TRUE(NavigateToURLFromRenderer( |
| ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0), test_url)); |
| EXPECT_EQ(false, |
| EvalJs(ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0), |
| "window.originAgentCluster")); |
| |
| // The two frames should be same-origin to each other, since the OAC header |
| // value should be ignored. |
| EXPECT_EQ("isolate me!", |
| EvalJs(shell(), "window[0].document.body.textContent")); |
| } |
| |
| // This test checks that same-process OriginAgentCluster won't crash and will |
| // apply properly when used on a localhost URL. See https://crbug.com/1276155. |
| IN_PROC_BROWSER_TEST_F(SameProcessOriginIsolationOptInHeaderTest, Localhost) { |
| SetHeaderValue("?1"); |
| GURL url(https_server()->GetURL("localhost", "/isolate_origin")); |
| url::Origin origin(url::Origin::Create(url)); |
| |
| EXPECT_TRUE(SiteIsolationPolicy::IsOriginAgentClusterEnabled()); |
| EXPECT_FALSE( |
| SiteIsolationPolicy::IsProcessIsolationForOriginAgentClusterEnabled()); |
| EXPECT_FALSE(ShouldOriginGetOptInProcessIsolation(origin)); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| EXPECT_FALSE(root->current_frame_host() |
| ->GetSiteInstance() |
| ->RequiresDedicatedProcess()); |
| EXPECT_FALSE(ShouldOriginGetOptInProcessIsolation(origin)); |
| auto* policy = ChildProcessSecurityPolicyImpl::GetInstance(); |
| auto isolation_result = policy->DetermineOriginAgentClusterIsolation( |
| root->current_frame_host()->GetSiteInstance()->GetIsolationContext(), |
| origin, MakeOACIsolationState(false)); |
| EXPECT_TRUE(isolation_result.is_origin_agent_cluster()); |
| EXPECT_FALSE(isolation_result.requires_origin_keyed_process()); |
| EXPECT_TRUE(policy->HasOriginEverRequestedOriginAgentClusterValue( |
| web_contents()->GetBrowserContext(), origin)); |
| } |
| |
| // This test verifies that --disable-web-security overrides same-process |
| // OriginAgentCluster (i.e. disables it). |
| IN_PROC_BROWSER_TEST_F(SameProcessNoWebSecurityOriginIsolationOptInHeaderTest, |
| DisableWebSecurityDisablesOriginAgentCluster) { |
| // Make sure we request the header for OriginAgentCluster for the child; the |
| // fact that this test uses --disable-web-security will override the header. |
| SetHeaderValue("?1"); |
| GURL main_url(https_server()->GetURL("foo.com", |
| "/cross_site_iframe_factory.html?" |
| "foo.com(foo.com)")); |
| GURL isolated_suborigin_url( |
| https_server()->GetURL("isolated.foo.com", "/isolate_origin")); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| EXPECT_EQ(2u, CollectAllRenderFrameHosts(shell()->web_contents()).size()); |
| |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| FrameTreeNode* child_frame_node = root->child_at(0); |
| EXPECT_TRUE( |
| NavigateToURLFromRenderer(child_frame_node, isolated_suborigin_url)); |
| |
| // Web security is disabled so everything should be same-origin and |
| // accessible across browsing contexts. |
| EXPECT_EQ(false, EvalJs(child_frame_node, "window.originAgentCluster")); |
| |
| std::string parent_body_content = |
| EvalJs(root, "document.body.textContent").ExtractString(); |
| // Make sure that the child frame doesn't think it's isolated. |
| EXPECT_EQ(parent_body_content, |
| EvalJs(child_frame_node, "window.parent.document.body.textContent") |
| .ExtractString()); |
| } |
| |
| // In this test the sub-origin isn't isolated because no header is set. It will |
| // have the same site instance as the main frame. |
| IN_PROC_BROWSER_TEST_F(OriginIsolationOptInHeaderTest, |
| SimpleSubOriginNonIsolationTest) { |
| base::HistogramTester histograms; |
| // Start off with an a(a) page, then navigate the subframe to an isolated sub |
| // origin. |
| GURL test_url(https_server()->GetURL("foo.com", |
| "/cross_site_iframe_factory.html?" |
| "foo.com(foo.com)")); |
| GURL isolated_suborigin_url( |
| https_server()->GetURL("isolated.foo.com", "/isolate_origin")); |
| EXPECT_TRUE(NavigateToURL(shell(), test_url)); |
| EXPECT_EQ(2u, CollectAllRenderFrameHosts(shell()->web_contents()).size()); |
| |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| FrameTreeNode* child_frame_node = root->child_at(0); |
| EXPECT_TRUE( |
| NavigateToURLFromRenderer(child_frame_node, isolated_suborigin_url)); |
| EXPECT_EQ(root->current_frame_host()->GetSiteInstance(), |
| child_frame_node->current_frame_host()->GetSiteInstance()); |
| EXPECT_THAT( |
| histograms.GetAllSamples("Navigation.OriginAgentCluster.Result"), |
| testing::ElementsAre(base::Bucket( |
| static_cast<int>(NavigationRequest::OriginAgentClusterEndResult:: |
| kNotRequestedAndNotOriginKeyed), |
| 3))); |
| } |
| |
| // This test verifies that renderer-initiated navigations to/from isolated |
| // sub-origins works as expected. |
| IN_PROC_BROWSER_TEST_F(OriginIsolationOptInHeaderTest, |
| RendererInitiatedNavigations) { |
| SetHeaderValue("?1"); |
| GURL test_url(https_server()->GetURL("foo.com", |
| "/cross_site_iframe_factory.html?" |
| "foo.com(foo.com)")); |
| EXPECT_TRUE(NavigateToURL(shell(), test_url)); |
| EXPECT_EQ(2u, CollectAllRenderFrameHosts(shell()->web_contents()).size()); |
| |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| FrameTreeNode* child = root->child_at(0); |
| |
| GURL isolated_sub_origin_url( |
| https_server()->GetURL("isolated.foo.com", "/isolate_origin")); |
| { |
| // Navigate the child to an isolated origin. |
| TestFrameNavigationObserver observer(child); |
| EXPECT_TRUE(ExecJs( |
| child, "location.href = '" + isolated_sub_origin_url.spec() + "';")); |
| observer.Wait(); |
| } |
| EXPECT_NE(root->current_frame_host()->GetSiteInstance(), |
| child->current_frame_host()->GetSiteInstance()); |
| |
| GURL non_isolated_sub_origin_url( |
| https_server()->GetURL("bar.foo.com", "/title1.html")); |
| { |
| // Navigate the child to a non-isolated origin. |
| TestFrameNavigationObserver observer(child); |
| EXPECT_TRUE(ExecJs(child, "location.href = '" + |
| non_isolated_sub_origin_url.spec() + "';")); |
| observer.Wait(); |
| } |
| EXPECT_EQ(root->current_frame_host()->GetSiteInstance(), |
| child->current_frame_host()->GetSiteInstance()); |
| } |
| |
| // Check that navigating a main frame from an non-isolated origin to an |
| // isolated origin and vice versa swaps processes and uses a new SiteInstance, |
| // both for renderer-initiated and browser-initiated navigations. |
| // Note: this test is essentially identical to |
| // IsolatedOriginTest.MainFrameNavigation. |
| IN_PROC_BROWSER_TEST_F(OriginIsolationOptInHeaderTest, MainFrameNavigation) { |
| SetHeaderValue("?1"); |
| GURL unisolated_url(https_server()->GetURL("www.foo.com", "/title1.html")); |
| GURL isolated_url( |
| https_server()->GetURL("isolated.foo.com", "/isolate_origin")); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), unisolated_url)); |
| |
| // Open a same-site popup to keep the www.foo.com process alive. |
| Shell* popup = OpenPopup(shell(), GURL(url::kAboutBlankURL), "foo"); |
| SiteInstance* unisolated_instance = |
| popup->web_contents()->GetPrimaryMainFrame()->GetSiteInstance(); |
| RenderProcessHost* unisolated_process = |
| popup->web_contents()->GetPrimaryMainFrame()->GetProcess(); |
| |
| // Go to isolated.foo.com with a renderer-initiated navigation. |
| EXPECT_TRUE(NavigateToURLFromRenderer(web_contents(), isolated_url)); |
| scoped_refptr<SiteInstance> isolated_instance = |
| web_contents()->GetSiteInstance(); |
| RenderProcessHost* isolated_process = |
| web_contents()->GetPrimaryMainFrame()->GetProcess(); |
| |
| EXPECT_NE(unisolated_instance, isolated_instance); |
| EXPECT_NE(unisolated_process, isolated_process); |
| |
| // The site URL for isolated.foo.com should be the full origin rather than |
| // scheme and eTLD+1. |
| EXPECT_EQ(https_server()->GetURL("isolated.foo.com", "/"), |
| isolated_instance->GetSiteURL()); |
| |
| // Now use a renderer-initiated navigation to go to an unisolated origin, |
| // www.foo.com. This should end up back in the `popup`'s process. |
| EXPECT_TRUE(NavigateToURLFromRenderer(web_contents(), unisolated_url)); |
| EXPECT_EQ(unisolated_instance, web_contents()->GetSiteInstance()); |
| EXPECT_EQ(unisolated_process, |
| web_contents()->GetPrimaryMainFrame()->GetProcess()); |
| |
| // Now, perform a browser-initiated navigation to an isolated origin and |
| // ensure that this ends up in a new process and SiteInstance for |
| // isolated.foo.com. |
| EXPECT_TRUE(NavigateToURL(shell(), isolated_url)); |
| scoped_refptr<SiteInstance> isolated_instance2 = |
| web_contents()->GetSiteInstance(); |
| RenderProcessHost* isolated_process2 = |
| web_contents()->GetPrimaryMainFrame()->GetProcess(); |
| EXPECT_NE(unisolated_instance, isolated_instance2); |
| EXPECT_NE(isolated_instance, isolated_instance2); |
| EXPECT_NE(unisolated_process, isolated_process2); |
| |
| // Go back to www.foo.com: this should end up in the unisolated process. |
| { |
| TestNavigationObserver back_observer(web_contents()); |
| web_contents()->GetController().GoBack(); |
| back_observer.Wait(); |
| } |
| |
| EXPECT_EQ(unisolated_instance, web_contents()->GetSiteInstance()); |
| EXPECT_EQ(unisolated_process, |
| web_contents()->GetPrimaryMainFrame()->GetProcess()); |
| |
| // Go back again. This should go to isolated.foo.com in an isolated process. |
| { |
| TestNavigationObserver back_observer(web_contents()); |
| web_contents()->GetController().GoBack(); |
| back_observer.Wait(); |
| } |
| |
| EXPECT_EQ(isolated_instance, web_contents()->GetSiteInstance()); |
| EXPECT_NE(unisolated_process, |
| web_contents()->GetPrimaryMainFrame()->GetProcess()); |
| |
| // Do a renderer-initiated navigation from isolated.foo.com to another |
| // isolated origin and ensure there is a different isolated process. |
| GURL second_isolated_url( |
| https_server()->GetURL("isolated.bar.com", "/isolate_origin")); |
| EXPECT_TRUE(NavigateToURLFromRenderer(web_contents(), second_isolated_url)); |
| EXPECT_EQ(https_server()->GetURL("isolated.bar.com", "/"), |
| web_contents()->GetSiteInstance()->GetSiteURL()); |
| EXPECT_NE(isolated_instance, web_contents()->GetSiteInstance()); |
| EXPECT_NE(unisolated_instance, web_contents()->GetSiteInstance()); |
| } |
| |
| // This test ensures that if an origin starts off being isolated in a |
| // BrowsingInstance, it continues that way within the BrowsingInstance, even |
| // if a new policy is received that removes the opt-in request. |
| IN_PROC_BROWSER_TEST_F(OriginIsolationOptInHeaderTest, |
| OriginIsolationStateRetainedForBrowsingInstance) { |
| base::HistogramTester histograms; |
| SetHeaderValue("?1"); |
| // Start off with an a(a,a) page, then navigate the subframe to an isolated |
| // sub origin. |
| GURL test_url(https_server()->GetURL("foo.com", |
| "/cross_site_iframe_factory.html?" |
| "foo.com(foo.com, foo.com)")); |
| GURL isolated_suborigin_url( |
| https_server()->GetURL("isolated.foo.com", "/isolate_origin")); |
| EXPECT_TRUE(NavigateToURL(shell(), test_url)); |
| EXPECT_EQ(3u, CollectAllRenderFrameHosts(shell()->web_contents()).size()); |
| |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| FrameTreeNode* child_frame_node0 = root->child_at(0); |
| FrameTreeNode* child_frame_node1 = root->child_at(1); |
| |
| EXPECT_TRUE( |
| NavigateToURLFromRenderer(child_frame_node0, isolated_suborigin_url)); |
| EXPECT_NE(root->current_frame_host()->GetSiteInstance(), |
| child_frame_node0->current_frame_host()->GetSiteInstance()); |
| |
| // Change the server's responses to stop isolating the sub-origin. It should |
| // still be isolated, to remain consistent with the other frame. |
| SetHeaderValue("?0"); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| console_observer.SetPattern( |
| "The page did not request an origin-keyed agent cluster, but was put in " |
| "one anyway*"); |
| |
| EXPECT_TRUE( |
| NavigateToURLFromRenderer(child_frame_node1, isolated_suborigin_url)); |
| |
| ASSERT_TRUE(console_observer.Wait()); |
| |
| EXPECT_NE(root->current_frame_host()->GetSiteInstance(), |
| child_frame_node1->current_frame_host()->GetSiteInstance()); |
| |
| // The two sub-frames should be in the same site instance. |
| EXPECT_EQ(child_frame_node0->current_frame_host()->GetSiteInstance(), |
| child_frame_node1->current_frame_host()->GetSiteInstance()); |
| |
| // Make sure the master opt-in list still has the origin tracked. |
| auto* policy = ChildProcessSecurityPolicyImpl::GetInstance(); |
| EXPECT_TRUE(policy->HasOriginEverRequestedOriginAgentClusterValue( |
| web_contents()->GetBrowserContext(), |
| url::Origin::Create(isolated_suborigin_url))); |
| |
| EXPECT_THAT( |
| histograms.GetAllSamples("Navigation.OriginAgentCluster.Result"), |
| testing::ElementsAre( |
| // Original loads of a(a,a) go here. |
| base::Bucket( |
| static_cast<int>(NavigationRequest::OriginAgentClusterEndResult:: |
| kNotRequestedAndNotOriginKeyed), |
| 3), |
| // Second isolated subframe load goes here. |
| base::Bucket( |
| static_cast<int>(NavigationRequest::OriginAgentClusterEndResult:: |
| kNotRequestedButOriginKeyed), |
| 1), |
| // First isolated subframe load goes here. |
| base::Bucket( |
| static_cast<int>(NavigationRequest::OriginAgentClusterEndResult:: |
| kRequestedAndOriginKeyed), |
| 1))); |
| } |
| |
| // This test ensures that if an origin starts off not being isolated in a |
| // BrowsingInstance, it continues that way within the BrowsingInstance, even |
| // if the header starts being sent. |
| // Case #1 where the non-opted-in origin is currently in the frame tree. |
| IN_PROC_BROWSER_TEST_F(OriginIsolationOptInHeaderTest, |
| OriginNonIsolationStateRetainedForBrowsingInstance1) { |
| base::HistogramTester histograms; |
| SetHeaderValue("?0"); |
| // Start off with an a(a,a) page, then navigate the subframe to an isolated |
| // sub origin. |
| GURL test_url(https_server()->GetURL("foo.com", |
| "/cross_site_iframe_factory.html?" |
| "foo.com(foo.com, foo.com)")); |
| GURL isolated_suborigin_url( |
| https_server()->GetURL("isolated.foo.com", "/isolate_origin")); |
| EXPECT_TRUE(NavigateToURL(shell(), test_url)); |
| EXPECT_EQ(3u, CollectAllRenderFrameHosts(shell()->web_contents()).size()); |
| |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| FrameTreeNode* child_frame_node0 = root->child_at(0); |
| FrameTreeNode* child_frame_node1 = root->child_at(1); |
| |
| EXPECT_TRUE( |
| NavigateToURLFromRenderer(child_frame_node0, isolated_suborigin_url)); |
| EXPECT_EQ(root->current_frame_host()->GetSiteInstance(), |
| child_frame_node0->current_frame_host()->GetSiteInstance()); |
| |
| // Change the server responses to start isolating the sub-origin. It should |
| // still be not-isolated, to remain consistent with the other frame. |
| SetHeaderValue("?1"); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| console_observer.SetPattern( |
| "The page requested an origin-keyed agent cluster using the " |
| "Origin-Agent-Cluster header, but could not be origin-keyed*"); |
| |
| EXPECT_TRUE( |
| NavigateToURLFromRenderer(child_frame_node1, isolated_suborigin_url)); |
| |
| ASSERT_TRUE(console_observer.Wait()); |
| |
| EXPECT_EQ(root->current_frame_host()->GetSiteInstance(), |
| child_frame_node1->current_frame_host()->GetSiteInstance()); |
| |
| // Make sure the master opt-in list has the origin listed. |
| auto* policy = ChildProcessSecurityPolicyImpl::GetInstance(); |
| EXPECT_TRUE(policy->HasOriginEverRequestedOriginAgentClusterValue( |
| web_contents()->GetBrowserContext(), |
| url::Origin::Create(isolated_suborigin_url))); |
| |
| EXPECT_THAT( |
| histograms.GetAllSamples("Navigation.OriginAgentCluster.Result"), |
| testing::ElementsAre( |
| // Original loads of a(a,a) go here. |
| base::Bucket( |
| static_cast<int>(NavigationRequest::OriginAgentClusterEndResult:: |
| kNotRequestedAndNotOriginKeyed), |
| 4), |
| base::Bucket( |
| static_cast<int>(NavigationRequest::OriginAgentClusterEndResult:: |
| kRequestedButNotOriginKeyed), |
| 1))); |
| } |
| |
| // This test ensures that if an origin starts off not being isolated in a |
| // BrowsingInstance, it continues that way within the BrowsingInstance, even |
| // if the header starts being sent. |
| // Case #2 where the non-opted-in origin is currently not in the frame tree. |
| IN_PROC_BROWSER_TEST_F(OriginIsolationOptInHeaderTest, |
| OriginNonIsolationStateRetainedForBrowsingInstance2) { |
| SetHeaderValue("?0"); |
| // Start off with an a(a) page, then navigate the subframe to an isolated sub |
| // origin. |
| GURL test_url(https_server()->GetURL("foo.com", |
| "/cross_site_iframe_factory.html?" |
| "foo.com(foo.com)")); |
| GURL isolated_suborigin_url( |
| https_server()->GetURL("isolated.foo.com", "/isolate_origin")); |
| EXPECT_TRUE(NavigateToURL(shell(), test_url)); |
| EXPECT_EQ(2u, CollectAllRenderFrameHosts(shell()->web_contents()).size()); |
| |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| FrameTreeNode* child_frame_node0 = root->child_at(0); |
| |
| // Even though we're navigating to isolated.foo.com, there's no manifest |
| // requesting opt-in, so it should end up in the same SiteInstance as the |
| // main frame. |
| EXPECT_TRUE( |
| NavigateToURLFromRenderer(child_frame_node0, isolated_suborigin_url)); |
| EXPECT_EQ(root->current_frame_host()->GetSiteInstance(), |
| child_frame_node0->current_frame_host()->GetSiteInstance()); |
| |
| // This navigation removes isolated_suborigin_url from the frame tree, but it |
| // should still be in the session history. |
| EXPECT_TRUE(NavigateToURLFromRenderer( |
| child_frame_node0, https_server()->GetURL("foo.com", "/title1.html"))); |
| EXPECT_EQ(root->current_frame_host()->GetSiteInstance(), |
| child_frame_node0->current_frame_host()->GetSiteInstance()); |
| |
| // Change the server to start isolating the sub-origin. It should |
| // still be not isolated, to remain consistent with the other frame. |
| SetHeaderValue("?1"); |
| EXPECT_TRUE( |
| NavigateToURLFromRenderer(child_frame_node0, isolated_suborigin_url)); |
| EXPECT_EQ(root->current_frame_host()->GetSiteInstance(), |
| child_frame_node0->current_frame_host()->GetSiteInstance()); |
| |
| // Make sure the master opt-in list has the origin listed. |
| auto* policy = ChildProcessSecurityPolicyImpl::GetInstance(); |
| EXPECT_TRUE(policy->HasOriginEverRequestedOriginAgentClusterValue( |
| web_contents()->GetBrowserContext(), |
| url::Origin::Create(isolated_suborigin_url))); |
| |
| // Make sure the current browsing instance does *not* isolate the origin. |
| EXPECT_FALSE(policy |
| ->DetermineOriginAgentClusterIsolation( |
| root->current_frame_host() |
| ->GetSiteInstance() |
| ->GetIsolationContext(), |
| url::Origin::Create(isolated_suborigin_url), |
| MakeOACIsolationState(false)) |
| .requires_origin_keyed_process()); |
| } |
| |
| // This test makes sure that a different tab in the same BrowsingInstance where |
| // an origin originally did not opt-in respects that state even if the |
| // server sends a different header. |
| IN_PROC_BROWSER_TEST_F(OriginIsolationOptInHeaderTest, |
| OriginNonIsolationStateRetainedForPopup) { |
| SetHeaderValue("?0"); |
| // Start off with an a(a,a) page, then navigate the subframe to an isolated |
| // sub origin. |
| GURL test_url(https_server()->GetURL("foo.com", |
| "/cross_site_iframe_factory.html?" |
| "foo.com(foo.com)")); |
| GURL isolated_suborigin_url( |
| https_server()->GetURL("isolated.foo.com", "/isolate_origin")); |
| EXPECT_TRUE(NavigateToURL(shell(), test_url)); |
| EXPECT_EQ(2u, CollectAllRenderFrameHosts(shell()->web_contents()).size()); |
| |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| FrameTreeNode* child_frame_node0 = root->child_at(0); |
| |
| EXPECT_TRUE( |
| NavigateToURLFromRenderer(child_frame_node0, isolated_suborigin_url)); |
| EXPECT_EQ(root->current_frame_host()->GetSiteInstance(), |
| child_frame_node0->current_frame_host()->GetSiteInstance()); |
| |
| // Change the server to start isolating the sub-origin. It should |
| // not be isolated, to remain consistent with the other frame. |
| SetHeaderValue("?1"); |
| |
| // Open a popup in the same browsing instance, and navigate it to the |
| // not-opted-in origin. Even though the manifest now requests isolation, it |
| // should not opt-in since it's in the same BrowsingInstance where it |
| // originally wasn't opted in. |
| Shell* popup = OpenPopup(shell(), isolated_suborigin_url, "foo"); |
| auto* popup_web_contents = popup->web_contents(); |
| EXPECT_TRUE( |
| NavigateToURLFromRenderer(popup_web_contents, isolated_suborigin_url)); |
| |
| EXPECT_EQ(shell()->web_contents()->GetSiteInstance()->GetBrowsingInstanceId(), |
| popup_web_contents->GetSiteInstance()->GetBrowsingInstanceId()); |
| |
| // Make sure the current browsing instance does *not* isolate the origin. |
| auto* policy = ChildProcessSecurityPolicyImpl::GetInstance(); |
| EXPECT_FALSE(policy |
| ->DetermineOriginAgentClusterIsolation( |
| root->current_frame_host() |
| ->GetSiteInstance() |
| ->GetIsolationContext(), |
| url::Origin::Create(isolated_suborigin_url), |
| MakeOACIsolationState(false)) |
| .requires_origin_keyed_process()); |
| } |
| |
| // This test creates a no-opener popup that is origin-isolated, and has two |
| // same-sub-origin iframes, one of which requests isolation and one that |
| // doesn't. The non-isolated child commits first, so the second child shouldn't |
| // get isolation, but more importantly we shouldn't crash on a NOTREACHED() in |
| // RenderFrameHostManager that is verifying that the second child frame was |
| // put in a compatible renderer process. |
| // https://crbug.com/1099718 |
| IN_PROC_BROWSER_TEST_F(OriginIsolationOptInHeaderTest, |
| NoKillForBrowsingInstanceDifferencesInProcess) { |
| SetHeaderValue("?1"); |
| GURL opener_url(https_server()->GetURL("foo.com", "/title1.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), opener_url)); |
| |
| // Create content for popup. The first subframe is in a sub-domain of the |
| // popup mainframe, which is an isolated base-origin. The second subframe is |
| // in the same sub-origin as the first, but requests isolation. The isolation |
| // request will fail, and both subframes will end up in the same site-locked |
| // process as the opener document (due to subframe process reuse). |
| GURL popup_subframe1_url( |
| https_server()->GetURL("sub.foo.com", "/title1.html")); |
| GURL popup_subframe2_url( |
| https_server()->GetURL("sub.foo.com", "/isolate_origin")); |
| // This is the HTML content for the popup mainframe. |
| std::string popup_content = base::StringPrintf( |
| R"(<!DOCTYPE html> |
| <html><head> |
| <meta charset="utf-8"> |
| <title>This page should not crash when window.open()ed</title> |
| </head><body> |
| <iframe src="%s"></iframe> |
| <iframe></iframe> |
| </body></html>)", |
| popup_subframe1_url.spec().c_str()); |
| // The next navigation with relative URL = "/isolate_origin" should serve this |
| // content. |
| AddContentToQueue(popup_content); |
| |
| // Open popup. |
| GURL isolated_popup_url(https_server()->GetURL("foo.com", "/isolate_origin")); |
| // Opening the popup with "noopener" guarantees that the isolated popup is in |
| // a different BrowsingInstance from the opener. |
| Shell* popup = |
| OpenPopup(shell(), isolated_popup_url, "windowName1", "noopener", |
| false /* expect_return_from_window_open */); |
| |
| // If we got here without crashing, all that remains is to verify everything |
| // is isolated/not-isolated as expected. |
| ASSERT_NE(nullptr, popup); |
| RenderFrameHostImpl* popup_root = |
| static_cast<WebContentsImpl*>(popup->web_contents()) |
| ->GetPrimaryMainFrame(); |
| EXPECT_EQ(2U, popup_root->child_count()); |
| FrameTreeNode* popup_child1 = popup_root->child_at(0); |
| FrameTreeNode* popup_child2 = popup_root->child_at(1); |
| |
| // Navigate the second child iframe after the first one has loaded. |
| EXPECT_TRUE(NavigateFrameToURL(popup_child2, popup_subframe2_url)); |
| |
| // Set cookie on `popup_child1` to make sure we don't get a renderer kill in |
| // the process with the opener. |
| EXPECT_TRUE(ExecJs(popup_child1, "document.cookie = 'foo=bar';")); |
| EXPECT_EQ("foo=bar", EvalJs(popup_child1, "document.cookie")); |
| |
| // Verify state of various SiteIstances, BrowsingInstances and processes. |
| SiteInstanceImpl* root_instance = popup_root->GetSiteInstance(); |
| EXPECT_TRUE(root_instance->GetSiteInfo().requires_origin_keyed_process()); |
| SiteInstanceImpl* child1_instance = |
| popup_child1->current_frame_host()->GetSiteInstance(); |
| SiteInstanceImpl* child2_instance = |
| popup_child2->current_frame_host()->GetSiteInstance(); |
| EXPECT_EQ(child1_instance, child2_instance); |
| EXPECT_NE(child1_instance, root_instance); |
| |
| // Make sure child1 and the opener share the same process, but different |
| // BrowsingInstances. |
| SiteInstanceImpl* opener_instance = |
| static_cast<WebContentsImpl*>(shell()->web_contents())->GetSiteInstance(); |
| EXPECT_NE(child1_instance->GetBrowsingInstanceId(), |
| opener_instance->GetBrowsingInstanceId()); |
| EXPECT_EQ(child1_instance->GetProcess(), opener_instance->GetProcess()); |
| EXPECT_FALSE(child2_instance->GetSiteInfo().requires_origin_keyed_process()); |
| } |
| |
| // Same as NoKillForBrowsingInstanceDifferencesInProcess, except the starting |
| // page has an isolated iframe that matches the origin that won't get isolation |
| // in the popup's BrowsingInstance. Since this means that the first |
| // BrowsingInstance will show sub.foo.com as isolated, then if |
| // CanAccessDataForOrigin only checks the first BrowsingInstance it will get the |
| // wrong result. |
| IN_PROC_BROWSER_TEST_F(OriginIsolationOptInHeaderTest, |
| NoKillForBrowsingInstanceDifferencesInProcess2) { |
| SetHeaderValue("?1"); |
| // Start on a page with same-site iframe. |
| GURL opener_url(https_server()->GetURL("foo.com", "/page_with_iframe.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), opener_url)); |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| FrameTreeNode* child = root->child_at(0); |
| GURL isolated_opener_iframe_url( |
| https_server()->GetURL("sub.foo.com", "/isolate_origin")); |
| EXPECT_TRUE(NavigateFrameToURL(child, isolated_opener_iframe_url)); |
| EXPECT_NE(root->current_frame_host()->GetSiteInstance(), |
| child->current_frame_host()->GetSiteInstance()); |
| EXPECT_TRUE(child->current_frame_host() |
| ->GetSiteInstance() |
| ->GetSiteInfo() |
| .requires_origin_keyed_process()); |
| |
| // Create content for popup. The first subframe is in a sub-domain of the |
| // popup mainframe, which is an isolated base-origin. The second subframe is |
| // in the same sub-origin as the first, but requests isolation. The isolation |
| // request will fail, and both subframes will end up in the same site-locked |
| // process as the opener document (due to subframe process reuse). |
| GURL popup_subframe1_url( |
| https_server()->GetURL("sub.foo.com", "/title1.html")); |
| GURL popup_subframe2_url( |
| https_server()->GetURL("sub.foo.com", "/isolate_origin")); |
| // This is the HTML content for the popup mainframe. |
| std::string popup_content = base::StringPrintf( |
| R"(<!DOCTYPE html> |
| <html><head> |
| <meta charset="utf-8"> |
| <title>This page should not crash when window.open()ed</title> |
| </head><body> |
| <iframe src="%s"></iframe> |
| <iframe></iframe> |
| </body></html>)", |
| popup_subframe1_url.spec().c_str()); |
| // The next navigation with relative URL = "/isolate_origin" should serve this |
| // content. |
| AddContentToQueue(popup_content); |
| |
| // Open popup. |
| GURL isolated_popup_url(https_server()->GetURL("foo.com", "/isolate_origin")); |
| // Opening the popup with "noopener" guarantees that the isolated popup is in |
| // a different BrowsingInstance from the opener. |
| Shell* popup = |
| OpenPopup(shell(), isolated_popup_url, "windowName1", "noopener", |
| false /* expect_return_from_window_open */); |
| |
| // If we got here without crashing, all that remains is to verify everything |
| // is isolated/not-isolated as expected. |
| ASSERT_NE(nullptr, popup); |
| RenderFrameHostImpl* popup_root = |
| static_cast<WebContentsImpl*>(popup->web_contents()) |
| ->GetPrimaryMainFrame(); |
| EXPECT_EQ(2U, popup_root->child_count()); |
| FrameTreeNode* popup_child1 = popup_root->child_at(0); |
| FrameTreeNode* popup_child2 = popup_root->child_at(1); |
| |
| // Navigate the second child iframe after the first one has loaded. |
| EXPECT_TRUE(NavigateFrameToURL(popup_child2, popup_subframe2_url)); |
| |
| // Set cookie on `popup_child1` to make sure we don't get a renderer kill in |
| // the process with the opener. |
| EXPECT_TRUE(ExecJs(popup_child1, "document.cookie = 'foo=bar';")); |
| EXPECT_EQ("foo=bar", EvalJs(popup_child1, "document.cookie")); |
| |
| // Verify state of various SiteIstances, BrowsingInstances and processes. |
| SiteInstanceImpl* root_instance = popup_root->GetSiteInstance(); |
| EXPECT_TRUE(root_instance->GetSiteInfo().requires_origin_keyed_process()); |
| SiteInstanceImpl* child1_instance = |
| popup_child1->current_frame_host()->GetSiteInstance(); |
| SiteInstanceImpl* child2_instance = |
| popup_child2->current_frame_host()->GetSiteInstance(); |
| EXPECT_EQ(child1_instance, child2_instance); |
| EXPECT_NE(child1_instance, root_instance); |
| |
| // Make sure child1 and the opener share the same process, but different |
| // BrowsingInstances. |
| SiteInstanceImpl* opener_instance = |
| static_cast<WebContentsImpl*>(shell()->web_contents())->GetSiteInstance(); |
| EXPECT_NE(child1_instance->GetBrowsingInstanceId(), |
| opener_instance->GetBrowsingInstanceId()); |
| EXPECT_EQ(child1_instance->GetProcess(), opener_instance->GetProcess()); |
| EXPECT_FALSE(child2_instance->GetSiteInfo().requires_origin_keyed_process()); |
| } |
| |
| // This test handles the case where the base origin is isolated, but a |
| // sub-origin isn't. In this case we need to place the sub-origin in a site- |
| // keyed SiteInstance with the same site URL as the origin-keyed SiteInstance |
| // used for the isolated base origin. Note: only the isolated base origin will |
| // have a port in this test, as the non-isolated sub-origin will have its port |
| // value stripped. The test IsolatedBaseOriginNoPorts tests the case where |
| // neither the isolated base origin nor the non-isolated sub-origin has a port |
| // value. |
| IN_PROC_BROWSER_TEST_F(OriginIsolationOptInHeaderTest, IsolatedBaseOrigin) { |
| base::HistogramTester histograms; |
| SetHeaderValue("?1"); |
| // Start off with an isolated base-origin in an a(a) configuration, then |
| // navigate the subframe to a sub-origin no requesting isolation. |
| GURL test_url(https_server()->GetURL( |
| "foo.com", "/isolated_base_origin_with_subframe.html")); |
| GURL non_isolated_sub_origin1( |
| https_server()->GetURL("non_isolated1.foo.com", "/title1.html")); |
| GURL non_isolated_sub_origin2( |
| https_server()->GetURL("non_isolated2.foo.com", "/title1.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), test_url)); |
| EXPECT_EQ(3u, CollectAllRenderFrameHosts(shell()->web_contents()).size()); |
| |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| FrameTreeNode* child_frame_node1 = root->child_at(0); |
| FrameTreeNode* child_frame_node2 = root->child_at(1); |
| EXPECT_TRUE( |
| NavigateToURLFromRenderer(child_frame_node1, non_isolated_sub_origin1)); |
| EXPECT_TRUE( |
| NavigateToURLFromRenderer(child_frame_node2, non_isolated_sub_origin2)); |
| |
| auto* policy = ChildProcessSecurityPolicyImpl::GetInstance(); |
| EXPECT_TRUE( |
| policy |
| ->DetermineOriginAgentClusterIsolation(root->current_frame_host() |
| ->GetSiteInstance() |
| ->GetIsolationContext(), |
| url::Origin::Create(test_url), |
| MakeOACIsolationState(false)) |
| .requires_origin_keyed_process()); |
| EXPECT_FALSE(policy |
| ->DetermineOriginAgentClusterIsolation( |
| child_frame_node1->current_frame_host() |
| ->GetSiteInstance() |
| ->GetIsolationContext(), |
| url::Origin::Create(non_isolated_sub_origin1), |
| MakeOACIsolationState(false)) |
| .requires_origin_keyed_process()); |
| EXPECT_FALSE(policy |
| ->DetermineOriginAgentClusterIsolation( |
| child_frame_node2->current_frame_host() |
| ->GetSiteInstance() |
| ->GetIsolationContext(), |
| url::Origin::Create(non_isolated_sub_origin2), |
| MakeOACIsolationState(false)) |
| .requires_origin_keyed_process()); |
| |
| // Base origin and subdomains should have different SiteInstances. |
| EXPECT_NE(root->current_frame_host()->GetSiteInstance(), |
| child_frame_node1->current_frame_host()->GetSiteInstance()); |
| EXPECT_TRUE(root->current_frame_host() |
| ->GetSiteInstance() |
| ->GetSiteInfo() |
| .requires_origin_keyed_process()); |
| EXPECT_FALSE(child_frame_node1->current_frame_host() |
| ->GetSiteInstance() |
| ->GetSiteInfo() |
| .requires_origin_keyed_process()); |
| |
| // Both non-isolated subdomains are in the same SiteInstance. |
| EXPECT_EQ(child_frame_node1->current_frame_host()->GetSiteInstance(), |
| child_frame_node2->current_frame_host()->GetSiteInstance()); |
| EXPECT_EQ( |
| GURL("https://foo.com"), |
| child_frame_node1->current_frame_host()->GetSiteInstance()->GetSiteURL()); |
| |
| // The base-origin and the children are in different processes. |
| EXPECT_NE( |
| root->current_frame_host()->GetSiteInstance()->GetProcess(), |
| child_frame_node1->current_frame_host()->GetSiteInstance()->GetProcess()); |
| |
| // Make sure the master opt-in list has the base origin as isolated, but not |
| // the sub-origins. |
| BrowserContext* browser_context = web_contents()->GetBrowserContext(); |
| EXPECT_TRUE(policy->HasOriginEverRequestedOriginAgentClusterValue( |
| browser_context, url::Origin::Create(test_url))); |
| EXPECT_FALSE(policy->HasOriginEverRequestedOriginAgentClusterValue( |
| browser_context, url::Origin::Create(non_isolated_sub_origin1))); |
| EXPECT_FALSE(policy->HasOriginEverRequestedOriginAgentClusterValue( |
| browser_context, url::Origin::Create(non_isolated_sub_origin2))); |
| |
| EXPECT_THAT( |
| histograms.GetAllSamples("Navigation.OriginAgentCluster.Result"), |
| testing::ElementsAre( |
| base::Bucket( |
| static_cast<int>(NavigationRequest::OriginAgentClusterEndResult:: |
| kNotRequestedAndNotOriginKeyed), |
| 2), |
| base::Bucket( |
| static_cast<int>(NavigationRequest::OriginAgentClusterEndResult:: |
| kRequestedAndOriginKeyed), |
| 1))); |
| } |
| |
| // This test is the same as OriginIsolationOptInHeaderTest |
| // .IsolatedBaseOrigin except it uses port-free URLs. This is critical since we |
| // can have two SiteInstances with the same SiteURL as long as one is |
| // origin-keyed and the other isn't. Site URLs used to be used as map-keys but |
| // with opt-in origin isolation we need to also consider the keying flag. |
| // When the URLs all have non-default ports, we will never have duplicate |
| // site URLs since the site-keyed one will have the port stripped. |
| IN_PROC_BROWSER_TEST_F(OriginIsolationOptInHeaderTest, |
| IsolatedBaseOriginNoPorts) { |
| GURL isolated_base_origin_url("https://foo.com"); |
| GURL non_isolated_sub_origin_url_a("https://a.foo.com"); |
| GURL non_isolated_sub_origin_url_b("https://b.foo.com"); |
| |
| // Since the embedded test server only works for URLs with non-default ports, |
| // use a URLLoaderInterceptor to mimic port-free operation. This allows the |
| // rest of the test to operate as if all URLs are using the default ports. |
| URLLoaderInterceptor interceptor(base::BindLambdaForTesting( |
| [&](URLLoaderInterceptor::RequestParams* params) { |
| if (params->url_request.url.host() == "foo.com") { |
| if (params->url_request.url.path() != "/") |
| return false; |
| |
| const std::string headers = |
| "HTTP/1.1 200 OK\n" |
| "Content-Type: text/html\n" |
| "Origin-Agent-Cluster: ?1\n"; |
| // Note: this call would normally get the headers from |
| // isolated_base_origin_with_subframe.html.mock-http-headers, |
| // but those are meant for use with a |
| // OriginIsolationOptInHeaderTest. and won't work here, so we |
| // override them. |
| URLLoaderInterceptor::WriteResponse( |
| "content/test/data/isolated_base_origin_with_subframe.html", |
| params->client.get(), &headers, absl::optional<net::SSLInfo>()); |
| return true; |
| } |
| if (params->url_request.url.host() == "a.foo.com" || |
| params->url_request.url.host() == "b.foo.com") { |
| URLLoaderInterceptor::WriteResponse("content/test/data/title1.html", |
| params->client.get()); |
| return true; |
| } |
| // Not handled by us. |
| return false; |
| })); |
| |
| // Load the isolated base url. |
| EXPECT_TRUE(NavigateToURL(shell(), isolated_base_origin_url)); |
| EXPECT_EQ(3u, CollectAllRenderFrameHosts(shell()->web_contents()).size()); |
| |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| FrameTreeNode* child_frame_node1 = root->child_at(0); |
| FrameTreeNode* child_frame_node2 = root->child_at(1); |
| EXPECT_TRUE(NavigateToURLFromRenderer(child_frame_node1, |
| non_isolated_sub_origin_url_a)); |
| EXPECT_TRUE(NavigateToURLFromRenderer(child_frame_node2, |
| non_isolated_sub_origin_url_b)); |
| |
| auto* policy = ChildProcessSecurityPolicyImpl::GetInstance(); |
| EXPECT_TRUE(policy |
| ->DetermineOriginAgentClusterIsolation( |
| root->current_frame_host() |
| ->GetSiteInstance() |
| ->GetIsolationContext(), |
| url::Origin::Create(isolated_base_origin_url), |
| MakeOACIsolationState(false)) |
| .requires_origin_keyed_process()); |
| EXPECT_FALSE(policy |
| ->DetermineOriginAgentClusterIsolation( |
| child_frame_node1->current_frame_host() |
| ->GetSiteInstance() |
| ->GetIsolationContext(), |
| url::Origin::Create(non_isolated_sub_origin_url_a), |
| MakeOACIsolationState(false)) |
| .requires_origin_keyed_process()); |
| EXPECT_FALSE(policy |
| ->DetermineOriginAgentClusterIsolation( |
| child_frame_node2->current_frame_host() |
| ->GetSiteInstance() |
| ->GetIsolationContext(), |
| url::Origin::Create(non_isolated_sub_origin_url_b), |
| MakeOACIsolationState(false)) |
| .requires_origin_keyed_process()); |
| // Base origin and subdomains should have different SiteInstances. |
| EXPECT_NE(root->current_frame_host()->GetSiteInstance(), |
| child_frame_node1->current_frame_host()->GetSiteInstance()); |
| EXPECT_TRUE(root->current_frame_host() |
| ->GetSiteInstance() |
| ->GetSiteInfo() |
| .requires_origin_keyed_process()); |
| EXPECT_FALSE(child_frame_node1->current_frame_host() |
| ->GetSiteInstance() |
| ->GetSiteInfo() |
| .requires_origin_keyed_process()); |
| |
| // Both SiteInstances should have the same site URL, because they have no |
| // port. |
| EXPECT_EQ( |
| root->current_frame_host()->GetSiteInstance()->GetSiteURL(), |
| child_frame_node1->current_frame_host()->GetSiteInstance()->GetSiteURL()); |
| EXPECT_NE(root->current_frame_host()->GetSiteInstance()->GetSiteInfo(), |
| child_frame_node1->current_frame_host() |
| ->GetSiteInstance() |
| ->GetSiteInfo()); |
| |
| // Both non-isolated subdomains are in the same SiteInstance. |
| EXPECT_EQ(child_frame_node1->current_frame_host()->GetSiteInstance(), |
| child_frame_node2->current_frame_host()->GetSiteInstance()); |
| |
| // The base-origin and the children are in different processes. |
| EXPECT_NE( |
| root->current_frame_host()->GetSiteInstance()->GetProcess(), |
| child_frame_node1->current_frame_host()->GetSiteInstance()->GetProcess()); |
| |
| // Make sure the master opt-in list has the base origin isolated and the sub |
| // origins both not isolated. |
| BrowserContext* browser_context = web_contents()->GetBrowserContext(); |
| EXPECT_TRUE(policy->HasOriginEverRequestedOriginAgentClusterValue( |
| browser_context, url::Origin::Create(isolated_base_origin_url))); |
| EXPECT_FALSE(policy->HasOriginEverRequestedOriginAgentClusterValue( |
| browser_context, url::Origin::Create(non_isolated_sub_origin_url_a))); |
| EXPECT_FALSE(policy->HasOriginEverRequestedOriginAgentClusterValue( |
| browser_context, url::Origin::Create(non_isolated_sub_origin_url_b))); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(OriginIsolationOptInHeaderTest, |
| SeparateBrowserContextTest) { |
| GURL isolated_origin_url( |
| https_server()->GetURL("isolated.foo.com", "/isolate_origin")); |
| Shell* shell_otr = CreateOffTheRecordBrowser(); |
| |
| EXPECT_NE(shell()->web_contents()->GetBrowserContext(), |
| shell_otr->web_contents()->GetBrowserContext()); |
| |
| // The isolation header is not present, so this navigation will result in a |
| // site-keyed instance. |
| EXPECT_TRUE(NavigateToURL(shell_otr, isolated_origin_url)); |
| WebContentsImpl* web_contents_shell_otr = |
| static_cast<WebContentsImpl*>(shell_otr->web_contents()); |
| SiteInstanceImpl* site_instance_shell_otr = |
| web_contents_shell_otr->GetPrimaryFrameTree() |
| .root() |
| ->current_frame_host() |
| ->GetSiteInstance(); |
| EXPECT_FALSE( |
| site_instance_shell_otr->GetSiteInfo().requires_origin_keyed_process()); |
| |
| url::Origin isolated_origin = url::Origin::Create(isolated_origin_url); |
| auto* policy = ChildProcessSecurityPolicyImpl::GetInstance(); |
| |
| // Now navigate a different BrowserContext to the same origin, but this time |
| // requesting isolation. The presence of the site-keyed instance in a |
| // different BrowsingInstance shouldn't prevent this navigation from being |
| // isolated. The presence of the site-keyed instance in a different |
| // BrowsingInstance (whether in the same BrowserContext or a different one) |
| // shouldn't prevent this navigation from being isolated. We'll test |
| // cross-BrowserContext interactions below. |
| SetHeaderValue("?1"); |
| EXPECT_TRUE(NavigateToURL(shell(), isolated_origin_url)); |
| EXPECT_TRUE(policy |
| ->DetermineOriginAgentClusterIsolation( |
| static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetPrimaryFrameTree() |
| .root() |
| ->current_frame_host() |
| ->GetSiteInstance() |
| ->GetIsolationContext(), |
| isolated_origin, MakeOACIsolationState(false)) |
| .requires_origin_keyed_process()); |
| |
| // Make sure isolating the origin in the main context didn't affect it in the |
| // off-the-record context. Specifically, if the opting-in in shell() did leak |
| // to shell_otr, then `isolated_origin` will be recorded as non-opted in in |
| // that BrowsingInstance. The following check makes sure that |
| // `isolated_origin` is not in the non-opt-in list, verifying that the |
| // internal bookkeeping is specific to each BrowserContext. Isolating the |
| // bookkeeping by BrowserContext prevents timing attacks from detecting |
| // whether an origin has been visited in another BrowserContext by detecting |
| // the global walk. |
| // At this stage, `isolated_origin` is not in the non-opt-in list for this |
| // BrowsingInstance, since we haven't yet done a global walk in the OTR |
| // BrowserContext, so DetermineOriginAgentClusterIsolation will return true. |
| // However, during the navigation by the OpenPopup call below that global walk |
| // will be triggered before the url's isolation status is set. This walk is |
| // triggered by the call to CheckForIsolationOptIn() in |
| // NavigationRequest::OnResponseStarted(). |
| EXPECT_TRUE(policy |
| ->DetermineOriginAgentClusterIsolation( |
| static_cast<WebContentsImpl*>(shell_otr->web_contents()) |
| ->GetPrimaryFrameTree() |
| .root() |
| ->current_frame_host() |
| ->GetSiteInstance() |
| ->GetIsolationContext(), |
| isolated_origin, MakeOACIsolationState(true)) |
| .requires_origin_keyed_process()); |
| |
| // Make sure the OTR context does a global (i.e. profile) walk if we attempt |
| // to now opt-in when we didn't before. |
| Shell* popup = OpenPopup(shell_otr, isolated_origin_url, "popup_otr"); |
| WebContentsImpl* web_contents_popup = |
| static_cast<WebContentsImpl*>(popup->web_contents()); |
| SiteInstanceImpl* site_instance_popup = |
| web_contents_popup->GetPrimaryFrameTree() |
| .root() |
| ->current_frame_host() |
| ->GetSiteInstance(); |
| // This shouldn't be isolated because we already have a non-isolated version |
| // of this origin in shell_otr's main frame, in the same BrowsingInstance. |
| EXPECT_FALSE( |
| site_instance_popup->GetSiteInfo().requires_origin_keyed_process()); |
| // Since the OpenPopup navigation triggered a global walk, `isolated_origin` |
| // was added to the non-opt-in list, so now calling |
| // DetermineOriginAgentClusterIsolation will return false. |
| EXPECT_FALSE(policy |
| ->DetermineOriginAgentClusterIsolation( |
| site_instance_popup->GetIsolationContext(), |
| isolated_origin, MakeOACIsolationState(true)) |
| .requires_origin_keyed_process()); |
| |
| // Opening a new tab in the OTR profile, which will create a new |
| // BrowsingInstance, should be allowed to isolate. |
| Shell* shell_otr_tab2 = CreateOffTheRecordBrowser(); |
| EXPECT_TRUE(NavigateToURL(shell_otr_tab2, isolated_origin_url)); |
| WebContentsImpl* web_contenst_shell_otr_tab2 = |
| static_cast<WebContentsImpl*>(shell_otr_tab2->web_contents()); |
| SiteInstanceImpl* site_instance_shell_otr_tab2 = |
| web_contenst_shell_otr_tab2->GetPrimaryFrameTree() |
| .root() |
| ->current_frame_host() |
| ->GetSiteInstance(); |
| EXPECT_TRUE(site_instance_shell_otr_tab2->GetSiteInfo() |
| .requires_origin_keyed_process()); |
| EXPECT_TRUE(policy |
| ->DetermineOriginAgentClusterIsolation( |
| site_instance_shell_otr_tab2->GetIsolationContext(), |
| isolated_origin, MakeOACIsolationState(true)) |
| .requires_origin_keyed_process()); |
| } |
| |
| // This test creates a scenario where we have a frame that is on the initial |
| // NavigationEntry, and then we created another frame with the same origin |
| // that opts-in to isolation. The opt-in triggers a walk of the session history |
| // and the frame tree ... the session history won't pick up the first frame, but |
| // the frame-tree walk should. |
| // TODO(https://crbug.com/608402): Once every created frame is guaranteed to |
| // have a FrameNavigationEntry and thus represented in the sesion history, we |
| // probably can remove the frame-tree walk. |
| IN_PROC_BROWSER_TEST_F(OriginIsolationOptInHeaderTest, FrameTreeTest) { |
| EXPECT_TRUE(NavigateToURL(shell(), |
| https_server()->GetURL("bar.com", "/title1.html"))); |
| // Have tab1 call window.open() to create blank tab2. |
| FrameTreeNode* tab1_root = web_contents()->GetPrimaryFrameTree().root(); |
| ShellAddedObserver new_shell_observer; |
| ASSERT_TRUE(ExecJs(tab1_root->current_frame_host(), |
| "window.w = window.open('/nocontent')")); |
| Shell* tab2_shell = new_shell_observer.GetShell(); |
| |
| // Create iframe in tab2. |
| FrameTreeNode* tab2_root = |
| static_cast<WebContentsImpl*>(tab2_shell->web_contents()) |
| ->GetPrimaryFrameTree() |
| .root(); |
| ASSERT_TRUE(ExecJs(tab2_root->current_frame_host(), |
| "var iframe = document.createElement('iframe');" |
| "document.body.appendChild(iframe);")); |
| EXPECT_EQ(1U, tab2_root->child_count()); |
| FrameTreeNode* tab2_child = tab2_root->child_at(0); |
| GURL isolated_origin_url( |
| https_server()->GetURL("isolated.foo.com", "/isolate_origin")); |
| // Navigate the iframe in tab2 to `isolated_origin_url` without requesting |
| // isolation, so it won't be isolated. |
| EXPECT_TRUE(NavigateFrameToURL(tab2_child, isolated_origin_url)); |
| |
| // Do a browser-initiated navigation of tab1 to the same origin, but isolate |
| // it this time. This should place the two frames with `isolated_origin_url` |
| // into different BrowsingInstances. |
| SetHeaderValue("?1"); |
| EXPECT_TRUE(NavigateToURL(shell(), isolated_origin_url)); |
| |
| // Since the same origin exists in two tabs, but one is isolated and the other |
| // isn't, we expect them to be in different BrowsingInstances. |
| EXPECT_NE(tab1_root->current_frame_host()->GetSiteInstance(), |
| tab2_child->current_frame_host()->GetSiteInstance()); |
| EXPECT_NE(tab1_root->current_frame_host() |
| ->GetSiteInstance() |
| ->GetIsolationContext() |
| .browsing_instance_id(), |
| tab2_child->current_frame_host() |
| ->GetSiteInstance() |
| ->GetIsolationContext() |
| .browsing_instance_id()); |
| |
| url::Origin isolated_origin = url::Origin::Create(isolated_origin_url); |
| auto* policy = ChildProcessSecurityPolicyImpl::GetInstance(); |
| // Verify that `isolated origin` is in the non-opt-in list for tab2's |
| // child's BrowsingInstance. We do this by requesting opt-in for the origin, |
| // then verifying that it is denied by DoesOriginRequestOptInIsolation. |
| EXPECT_FALSE(policy |
| ->DetermineOriginAgentClusterIsolation( |
| tab2_child->current_frame_host() |
| ->GetSiteInstance() |
| ->GetIsolationContext(), |
| isolated_origin, MakeOACIsolationState(true)) |
| .requires_origin_keyed_process()); |
| // Verify that `isolated_origin` in tab1 is indeed isolated. |
| EXPECT_TRUE(policy |
| ->DetermineOriginAgentClusterIsolation( |
| tab1_root->current_frame_host() |
| ->GetSiteInstance() |
| ->GetIsolationContext(), |
| isolated_origin, MakeOACIsolationState(false)) |
| .requires_origin_keyed_process()); |
| // Verify that the tab2 child frame is on the initial NavigationEntry. |
| EXPECT_TRUE(tab2_shell->web_contents() |
| ->GetController() |
| .GetLastCommittedEntry() |
| ->IsInitialEntry()); |
| |
| // Now, create a second frame in tab2 and navigate it to |
| // `isolated_origin_url`. Even though isolation is requested, it should not |
| // be isolated. |
| ASSERT_TRUE(ExecJs(tab2_root->current_frame_host(), |
| "var iframe = document.createElement('iframe');" |
| "document.body.appendChild(iframe);")); |
| EXPECT_EQ(2U, tab2_root->child_count()); |
| FrameTreeNode* tab2_child2 = tab2_root->child_at(1); |
| NavigateFrameToURL(tab2_child2, isolated_origin_url); |
| EXPECT_EQ(tab2_child->current_frame_host()->GetSiteInstance(), |
| tab2_child2->current_frame_host()->GetSiteInstance()); |
| |
| // Check that the two child frames can script each other. |
| EXPECT_TRUE(ExecJs(tab2_child2, R"( |
| parent.frames[0].cross_frame_property_test = 'hello from t2c2'; )")); |
| EXPECT_EQ("hello from t2c2", |
| EvalJs(tab2_child, "window.cross_frame_property_test;")); |
| } |
| |
| // Similar to FrameTreeTest, but we stop the navigation that's not requesting |
| // isolation at the pending commit state in tab2, then verify that the FrameTree |
| // walk has correctly registered the origin as non-isolated in tab2, but |
| // isolated in tab1. |
| IN_PROC_BROWSER_TEST_F(OriginIsolationOptInHeaderTest, |
| FrameTreeTestPendingCommit) { |
| GURL isolated_origin_url( |
| https_server()->GetURL("isolated.foo.com", "/isolate_origin")); |
| TestNavigationManager non_isolated_delayer(shell()->web_contents(), |
| isolated_origin_url); |
| shell()->web_contents()->GetController().LoadURL( |
| isolated_origin_url, Referrer(), ui::PAGE_TRANSITION_LINK, std::string()); |
| EXPECT_TRUE(non_isolated_delayer.WaitForResponse()); |
| |
| Shell* tab2 = CreateBrowser(); |
| // Do a browser-initiated navigation of tab2 to the same origin, but isolate |
| // it this time. This should place the two frames with `isolated_origin_url` |
| // into different BrowsingInstances. |
| SetHeaderValue("?1"); |
| EXPECT_TRUE(NavigateToURL(tab2, isolated_origin_url)); |
| |
| // Now commit the non-isolated navigation. |
| ASSERT_TRUE(non_isolated_delayer.WaitForNavigationFinished()); |
| |
| FrameTreeNode* tab1_root = web_contents()->GetPrimaryFrameTree().root(); |
| SiteInstanceImpl* tab1_site_instance = |
| tab1_root->current_frame_host()->GetSiteInstance(); |
| FrameTreeNode* tab2_root = static_cast<WebContentsImpl*>(tab2->web_contents()) |
| ->GetPrimaryFrameTree() |
| .root(); |
| SiteInstanceImpl* tab2_site_instance = |
| tab2_root->current_frame_host()->GetSiteInstance(); |
| EXPECT_NE(tab1_site_instance, tab2_site_instance); |
| EXPECT_NE(tab1_site_instance->GetIsolationContext().browsing_instance_id(), |
| tab2_site_instance->GetIsolationContext().browsing_instance_id()); |
| |
| // Despite the non-isolated navigation only being at pending-commit when we |
| // got the response for the isolated navigation, it should be properly |
| // registered as non-isolated in its browsing instance. |
| |
| url::Origin isolated_origin = url::Origin::Create(isolated_origin_url); |
| auto* policy = ChildProcessSecurityPolicyImpl::GetInstance(); |
| // Verify that `isolated origin` is in the non-opt-in list for tab1's |
| // BrowsingInstance. We do this by requesting opt-in for the origin, then |
| // verifying that it is denied by DetermineOriginAgentClusterIsolation. |
| EXPECT_FALSE(policy |
| ->DetermineOriginAgentClusterIsolation( |
| tab1_site_instance->GetIsolationContext(), |
| isolated_origin, MakeOACIsolationState(true)) |
| .requires_origin_keyed_process()); |
| |
| // Verify that `isolated_origin` in tab2 is indeed isolated. |
| EXPECT_TRUE(policy |
| ->DetermineOriginAgentClusterIsolation( |
| tab2_site_instance->GetIsolationContext(), |
| isolated_origin, MakeOACIsolationState(false)) |
| .requires_origin_keyed_process()); |
| } |
| |
| // Helper class to navigate a second tab to a specified URL that requests opt-in |
| // origin isolation just before the first tab processes the next |
| // DidCommitProvisionalLoad message. |
| class InjectIsolationRequestingNavigation |
| : public DidCommitNavigationInterceptor { |
| public: |
| InjectIsolationRequestingNavigation( |
| OriginIsolationOptInHeaderTest* test_framework, |
| WebContents* tab1_web_contents, |
| Shell* tab2, |
| const GURL& url) |
| : DidCommitNavigationInterceptor(tab1_web_contents), |
| test_framework_(test_framework), |
| tab2_(tab2), |
| url_(url) {} |
| |
| InjectIsolationRequestingNavigation( |
| const InjectIsolationRequestingNavigation&) = delete; |
| InjectIsolationRequestingNavigation& operator=( |
| const InjectIsolationRequestingNavigation&) = delete; |
| |
| bool was_called() { return was_called_; } |
| |
| private: |
| // DidCommitNavigationInterceptor implementation. |
| bool WillProcessDidCommitNavigation( |
| RenderFrameHost* render_frame_host, |
| NavigationRequest* navigation_request, |
| mojom::DidCommitProvisionalLoadParamsPtr*, |
| mojom::DidCommitProvisionalLoadInterfaceParamsPtr* interface_params) |
| override { |
| was_called_ = true; |
| |
| // Performa a navigation of `tab2_` to `url_`. `url_` should request |
| // isolation. |
| test_framework_->SetHeaderValue("?1"); |
| EXPECT_TRUE(NavigateToURL(tab2_, *url_)); |
| |
| return true; |
| } |
| |
| raw_ptr<OriginIsolationOptInHeaderTest> test_framework_; |
| raw_ptr<Shell, DanglingUntriaged> tab2_; |
| const raw_ref<const GURL> url_; |
| bool was_called_ = false; |
| }; |
| |
| // TODO(crbug.com/1110767): flaky on Android builders since 2020-07-28. |
| #if BUILDFLAG(IS_ANDROID) |
| #define MAYBE_FrameTreeTestBeforeDidCommit DISABLED_FrameTreeTestBeforeDidCommit |
| #else |
| #define MAYBE_FrameTreeTestBeforeDidCommit FrameTreeTestBeforeDidCommit |
| #endif |
| // This test is similar to the one above, but exercises the pending navigation |
| // when it's at a different stage, namely between the CommitNavigation and |
| // DidCommitProvisionalLoad, rather than at WillProcessResponse. |
| IN_PROC_BROWSER_TEST_F(OriginIsolationOptInHeaderTest, |
| MAYBE_FrameTreeTestBeforeDidCommit) { |
| GURL isolated_origin_url( |
| https_server()->GetURL("isolated.foo.com", "/isolate_origin")); |
| |
| FrameTreeNode* tab1_root = web_contents()->GetPrimaryFrameTree().root(); |
| // We use the following, slightly more verbose, code instead of |
| // CreateBrowser() in order to avoid issues with NavigateToURL() in |
| // InjectIsolationRequestingNavigation::WillProcessDidCommitNavigation() |
| // getting stuck when it calls for WaitForLoadStop internally. |
| Shell* tab2 = |
| Shell::CreateNewWindow(shell()->web_contents()->GetBrowserContext(), |
| GURL(), nullptr, gfx::Size()); |
| |
| InjectIsolationRequestingNavigation injector(this, web_contents(), tab2, |
| isolated_origin_url); |
| { |
| TestNavigationObserver tab1_navigation_observer(shell()->web_contents(), 1); |
| tab1_navigation_observer.set_expected_initial_url(isolated_origin_url); |
| shell()->LoadURL(isolated_origin_url); |
| |
| // Waiting for DidNavigationFinished is sufficient to ensure that |
| // `injector.was_called()`. We can't waiting for DidStopLoading, because |
| // running a nested message loop in the injector confuses |
| // TestNavigationObserver by changing the order of notifications. |
| tab1_navigation_observer.WaitForNavigationFinished(); |
| } |
| EXPECT_TRUE(injector.was_called()); |
| |
| SiteInstanceImpl* tab1_site_instance = |
| tab1_root->current_frame_host()->GetSiteInstance(); |
| FrameTreeNode* tab2_root = static_cast<WebContentsImpl*>(tab2->web_contents()) |
| ->GetPrimaryFrameTree() |
| .root(); |
| SiteInstanceImpl* tab2_site_instance = |
| tab2_root->current_frame_host()->GetSiteInstance(); |
| EXPECT_NE(tab1_site_instance, tab2_site_instance); |
| EXPECT_NE(tab1_site_instance->GetIsolationContext().browsing_instance_id(), |
| tab2_site_instance->GetIsolationContext().browsing_instance_id()); |
| |
| // Despite the non-isolated navigation only being at pending-commit when we |
| // got the response for the isolated navigation, it should be properly |
| // registered as non-isolated in its browsing instance. |
| auto* policy = ChildProcessSecurityPolicyImpl::GetInstance(); |
| url::Origin isolated_origin = url::Origin::Create(isolated_origin_url); |
| // Verify that `isolated origin` is in the non-opt-in list for tab1's |
| // BrowsingInstance. We do this by requesting opt-in for the origin, then |
| // verifying that it is denied by DoesOriginRequestOptInIsolation. |
| EXPECT_FALSE(policy |
| ->DetermineOriginAgentClusterIsolation( |
| tab1_site_instance->GetIsolationContext(), |
| isolated_origin, MakeOACIsolationState(true)) |
| .requires_origin_keyed_process()); |
| |
| // Verify that `isolated_origin` in tab2 is indeed isolated. |
| EXPECT_TRUE(policy |
| ->DetermineOriginAgentClusterIsolation( |
| tab2_site_instance->GetIsolationContext(), |
| isolated_origin, MakeOACIsolationState(false)) |
| .requires_origin_keyed_process()); |
| } |
| |
| class StrictOriginIsolationTest : public IsolatedOriginTestBase { |
| public: |
| StrictOriginIsolationTest() = default; |
| ~StrictOriginIsolationTest() override = default; |
| |
| StrictOriginIsolationTest(const StrictOriginIsolationTest&) = delete; |
| StrictOriginIsolationTest& operator=(const StrictOriginIsolationTest&) = |
| delete; |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| IsolatedOriginTestBase::SetUpCommandLine(command_line); |
| ASSERT_TRUE(embedded_test_server()->InitializeAndListen()); |
| |
| // This is needed for this test to run properly on platforms where |
| // --site-per-process isn't the default, such as Android. |
| IsolateAllSitesForTesting(command_line); |
| feature_list_.InitAndEnableFeature(features::kStrictOriginIsolation); |
| } |
| |
| void SetUpOnMainThread() override { |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| embedded_test_server()->StartAcceptingConnections(); |
| } |
| |
| // Helper function that creates an http URL for `host` that includes the test |
| // server's port and returns the strict ProcessLock for that URL. |
| ProcessLock GetStrictProcessLockForHost(const std::string& host) { |
| return GetStrictProcessLock(embedded_test_server()->GetURL(host, "/")); |
| } |
| |
| private: |
| base::test::ScopedFeatureList feature_list_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(StrictOriginIsolationTest, SubframesAreIsolated) { |
| GURL test_url(embedded_test_server()->GetURL( |
| "foo.com", |
| "/cross_site_iframe_factory.html?" |
| "foo.com(mail.foo.com,bar.foo.com(foo.com),foo.com)")); |
| EXPECT_TRUE(NavigateToURL(shell(), test_url)); |
| EXPECT_EQ(5u, CollectAllRenderFrameHosts(shell()->web_contents()).size()); |
| |
| // Make sure we have three separate processes. |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| RenderFrameHost* main_frame = root->current_frame_host(); |
| int main_frame_id = main_frame->GetProcess()->GetID(); |
| RenderFrameHost* child_frame0 = root->child_at(0)->current_frame_host(); |
| RenderFrameHost* child_frame1 = root->child_at(1)->current_frame_host(); |
| RenderFrameHost* child_frame2 = root->child_at(2)->current_frame_host(); |
| RenderFrameHost* grandchild_frame0 = |
| root->child_at(1)->child_at(0)->current_frame_host(); |
| EXPECT_NE(main_frame_id, child_frame0->GetProcess()->GetID()); |
| EXPECT_NE(main_frame_id, child_frame1->GetProcess()->GetID()); |
| EXPECT_EQ(main_frame_id, child_frame2->GetProcess()->GetID()); |
| EXPECT_EQ(main_frame_id, grandchild_frame0->GetProcess()->GetID()); |
| |
| EXPECT_EQ(GetStrictProcessLockForHost("foo.com"), |
| main_frame->GetProcess()->GetProcessLock()); |
| EXPECT_EQ(GetStrictProcessLockForHost("mail.foo.com"), |
| child_frame0->GetProcess()->GetProcessLock()); |
| EXPECT_EQ(GetStrictProcessLockForHost("bar.foo.com"), |
| child_frame1->GetProcess()->GetProcessLock()); |
| EXPECT_EQ(GetStrictProcessLockForHost("foo.com"), |
| child_frame2->GetProcess()->GetProcessLock()); |
| EXPECT_EQ(GetStrictProcessLockForHost("foo.com"), |
| grandchild_frame0->GetProcess()->GetProcessLock()); |
| |
| // Navigate child_frame1 to a new origin ... it should get its own process. |
| FrameTreeNode* child_frame2_node = root->child_at(2); |
| GURL foo_url(embedded_test_server()->GetURL("www.foo.com", "/title1.html")); |
| const auto expected_foo_lock = GetStrictProcessLock(foo_url); |
| EXPECT_TRUE(NavigateToURLFromRenderer(child_frame2_node, foo_url)); |
| EXPECT_NE(root->current_frame_host()->GetSiteInstance(), |
| child_frame2_node->current_frame_host()->GetSiteInstance()); |
| // The old RenderFrameHost for subframe3 will no longer be valid, so get the |
| // new one. |
| child_frame2 = root->child_at(2)->current_frame_host(); |
| EXPECT_NE(main_frame->GetProcess()->GetID(), |
| child_frame2->GetProcess()->GetID()); |
| EXPECT_EQ(expected_foo_lock, child_frame2->GetProcess()->GetProcessLock()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(StrictOriginIsolationTest, MainframesAreIsolated) { |
| GURL foo_url(embedded_test_server()->GetURL("foo.com", "/title1.html")); |
| const auto expected_foo_lock = GetStrictProcessLock(foo_url); |
| EXPECT_TRUE(NavigateToURL(shell(), foo_url)); |
| EXPECT_EQ(1u, CollectAllRenderFrameHosts(shell()->web_contents()).size()); |
| auto* policy = ChildProcessSecurityPolicyImpl::GetInstance(); |
| |
| auto foo_process_id = |
| web_contents()->GetPrimaryMainFrame()->GetProcess()->GetID(); |
| SiteInstanceImpl* foo_site_instance = web_contents()->GetSiteInstance(); |
| EXPECT_EQ(expected_foo_lock, |
| ProcessLock::FromSiteInfo(foo_site_instance->GetSiteInfo())); |
| EXPECT_EQ(ProcessLock::FromSiteInfo(foo_site_instance->GetSiteInfo()), |
| policy->GetProcessLock(foo_process_id)); |
| |
| GURL sub_foo_url = |
| embedded_test_server()->GetURL("sub.foo.com", "/title1.html"); |
| const auto expected_sub_foo_lock = GetStrictProcessLock(sub_foo_url); |
| EXPECT_TRUE(NavigateToURL(shell(), sub_foo_url)); |
| auto sub_foo_process_id = |
| shell()->web_contents()->GetPrimaryMainFrame()->GetProcess()->GetID(); |
| SiteInstanceImpl* sub_foo_site_instance = web_contents()->GetSiteInstance(); |
| EXPECT_EQ(expected_sub_foo_lock, |
| ProcessLock::FromSiteInfo(sub_foo_site_instance->GetSiteInfo())); |
| EXPECT_EQ(ProcessLock::FromSiteInfo(sub_foo_site_instance->GetSiteInfo()), |
| policy->GetProcessLock(sub_foo_process_id)); |
| |
| EXPECT_NE(foo_process_id, sub_foo_process_id); |
| EXPECT_NE(foo_site_instance->GetSiteURL(), |
| sub_foo_site_instance->GetSiteURL()); |
| |
| // Now verify with a renderer-initiated navigation. |
| GURL another_foo_url( |
| embedded_test_server()->GetURL("another.foo.com", "/title2.html")); |
| const auto expected_another_foo_lock = GetStrictProcessLock(another_foo_url); |
| EXPECT_TRUE(NavigateToURLFromRenderer(shell(), another_foo_url)); |
| auto another_foo_process_id = |
| shell()->web_contents()->GetPrimaryMainFrame()->GetProcess()->GetID(); |
| SiteInstanceImpl* another_foo_site_instance = |
| web_contents()->GetSiteInstance(); |
| EXPECT_NE(another_foo_process_id, sub_foo_process_id); |
| EXPECT_NE(another_foo_process_id, foo_process_id); |
| EXPECT_EQ( |
| expected_another_foo_lock, |
| ProcessLock::FromSiteInfo(another_foo_site_instance->GetSiteInfo())); |
| EXPECT_EQ(ProcessLock::FromSiteInfo(another_foo_site_instance->GetSiteInfo()), |
| policy->GetProcessLock(another_foo_process_id)); |
| EXPECT_NE(another_foo_site_instance, foo_site_instance); |
| |
| EXPECT_NE(expected_foo_lock, expected_sub_foo_lock); |
| EXPECT_NE(expected_sub_foo_lock, expected_another_foo_lock); |
| EXPECT_NE(expected_another_foo_lock, expected_foo_lock); |
| } |
| |
| // Ensure that navigations across two URLs that resolve to the same effective |
| // URL won't result in a renderer kill with strict origin isolation. See |
| // https://crbug.com/961386. |
| IN_PROC_BROWSER_TEST_F(StrictOriginIsolationTest, |
| NavigateToURLsWithSameEffectiveURL) { |
| GURL foo_url(embedded_test_server()->GetURL("foo.com", "/title1.html")); |
| GURL bar_url(embedded_test_server()->GetURL("bar.com", "/title1.html")); |
| GURL app_url(GetWebUIURL("translated")); |
| |
| // Set up effective URL translation that maps both `foo_url` and `bar_url` to |
| // `app_url`. |
| EffectiveURLContentBrowserTestContentBrowserClient modified_client( |
| false /* requires_dedicated_process */); |
| modified_client.AddTranslation(foo_url, app_url); |
| modified_client.AddTranslation(bar_url, app_url); |
| |
| // Calculate the expected SiteInfo for each URL. Both `foo_url` and |
| // `bar_url` should have a site URL of `app_url`, but the process locks |
| // should be foo.com and bar.com. |
| SiteInfo foo_site_info = SiteInfo::CreateForTesting( |
| web_contents()->GetSiteInstance()->GetIsolationContext(), foo_url); |
| EXPECT_EQ(app_url, foo_site_info.site_url()); |
| EXPECT_EQ(foo_url.DeprecatedGetOriginAsURL(), |
| foo_site_info.process_lock_url()); |
| SiteInfo bar_site_info = SiteInfo::CreateForTesting( |
| web_contents()->GetSiteInstance()->GetIsolationContext(), bar_url); |
| EXPECT_EQ(app_url, bar_site_info.site_url()); |
| EXPECT_EQ(bar_url.DeprecatedGetOriginAsURL(), |
| bar_site_info.process_lock_url()); |
| EXPECT_EQ(foo_site_info.site_url(), bar_site_info.site_url()); |
| |
| // Navigate to foo_url and then to bar_url. Verify that we end up with |
| // correct SiteInfo in each case. |
| EXPECT_TRUE(NavigateToURL(shell(), foo_url)); |
| scoped_refptr<SiteInstanceImpl> foo_site_instance = |
| web_contents()->GetSiteInstance(); |
| EXPECT_EQ(foo_site_info, foo_site_instance->GetSiteInfo()); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), bar_url)); |
| scoped_refptr<SiteInstanceImpl> bar_site_instance = |
| web_contents()->GetSiteInstance(); |
| EXPECT_EQ(bar_site_info, bar_site_instance->GetSiteInfo()); |
| |
| // Verify that the SiteInstances and processes are different. In |
| // https://crbug.com/961386, we didn't swap processes for the second |
| // navigation, leading to renderer kills. |
| EXPECT_NE(foo_site_instance.get(), bar_site_instance.get()); |
| EXPECT_NE(foo_site_instance->GetProcess(), bar_site_instance->GetProcess()); |
| |
| // Navigate to another site, then repeat this test with a redirect from |
| // foo.com to bar.com. The navigation should throw away the speculative RFH |
| // created for foo.com and should commit in a process locked to bar.com. |
| EXPECT_TRUE(NavigateToURL( |
| shell(), embedded_test_server()->GetURL("a.com", "/title1.html"))); |
| GURL redirect_url(embedded_test_server()->GetURL( |
| "foo.com", "/server-redirect?" + bar_url.spec())); |
| modified_client.AddTranslation(redirect_url, app_url); |
| EXPECT_TRUE(NavigateToURL(shell(), redirect_url, bar_url)); |
| EXPECT_EQ(bar_site_info, web_contents()->GetSiteInstance()->GetSiteInfo()); |
| } |
| |
| // Check that navigating a main frame from an non-isolated origin to an |
| // isolated origin and vice versa swaps processes and uses a new SiteInstance, |
| // both for renderer-initiated and browser-initiated navigations. |
| IN_PROC_BROWSER_TEST_F(IsolatedOriginTest, MainFrameNavigation) { |
| GURL unisolated_url( |
| embedded_test_server()->GetURL("www.foo.com", "/title1.html")); |
| GURL isolated_url( |
| embedded_test_server()->GetURL("isolated.foo.com", "/title2.html")); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), unisolated_url)); |
| |
| // Open a same-site popup to keep the www.foo.com process alive. |
| Shell* popup = OpenPopup(shell(), GURL(url::kAboutBlankURL), "foo"); |
| SiteInstance* unisolated_instance = |
| popup->web_contents()->GetPrimaryMainFrame()->GetSiteInstance(); |
| RenderProcessHost* unisolated_process = |
| popup->web_contents()->GetPrimaryMainFrame()->GetProcess(); |
| |
| // Go to isolated.foo.com with a renderer-initiated navigation. |
| EXPECT_TRUE(NavigateToURLFromRenderer(web_contents(), isolated_url)); |
| scoped_refptr<SiteInstance> isolated_instance = |
| web_contents()->GetSiteInstance(); |
| EXPECT_EQ(isolated_instance, web_contents()->GetSiteInstance()); |
| EXPECT_NE(unisolated_process, |
| web_contents()->GetPrimaryMainFrame()->GetProcess()); |
| |
| // The site URL for isolated.foo.com should be the full origin rather than |
| // scheme and eTLD+1. |
| EXPECT_EQ(GURL("http://isolated.foo.com/"), isolated_instance->GetSiteURL()); |
| |
| // Now use a renderer-initiated navigation to go to an unisolated origin, |
| // www.foo.com. This should end up back in the `popup`'s process. |
| EXPECT_TRUE(NavigateToURLFromRenderer(web_contents(), unisolated_url)); |
| EXPECT_EQ(unisolated_instance, web_contents()->GetSiteInstance()); |
| EXPECT_EQ(unisolated_process, |
| web_contents()->GetPrimaryMainFrame()->GetProcess()); |
| |
| // Now, perform a browser-initiated navigation to an isolated origin and |
| // ensure that this ends up in a new process and SiteInstance for |
| // isolated.foo.com. |
| EXPECT_TRUE(NavigateToURL(shell(), isolated_url)); |
| EXPECT_NE(web_contents()->GetSiteInstance(), unisolated_instance); |
| EXPECT_NE(web_contents()->GetPrimaryMainFrame()->GetProcess(), |
| unisolated_process); |
| |
| // Go back to www.foo.com: this should end up in the unisolated process. |
| { |
| TestNavigationObserver back_observer(web_contents()); |
| web_contents()->GetController().GoBack(); |
| back_observer.Wait(); |
| } |
| |
| EXPECT_EQ(unisolated_instance, web_contents()->GetSiteInstance()); |
| EXPECT_EQ(unisolated_process, |
| web_contents()->GetPrimaryMainFrame()->GetProcess()); |
| |
| // Go back again. This should go to isolated.foo.com in an isolated process. |
| { |
| TestNavigationObserver back_observer(web_contents()); |
| web_contents()->GetController().GoBack(); |
| back_observer.Wait(); |
| } |
| |
| EXPECT_EQ(isolated_instance, web_contents()->GetSiteInstance()); |
| EXPECT_NE(unisolated_process, |
| web_contents()->GetPrimaryMainFrame()->GetProcess()); |
| |
| // Do a renderer-initiated navigation from isolated.foo.com to another |
| // isolated origin and ensure there is a different isolated process. |
| GURL second_isolated_url( |
| embedded_test_server()->GetURL("isolated.bar.com", "/title3.html")); |
| EXPECT_TRUE(NavigateToURLFromRenderer(web_contents(), second_isolated_url)); |
| EXPECT_EQ(GURL("http://isolated.bar.com/"), |
| web_contents()->GetSiteInstance()->GetSiteURL()); |
| EXPECT_NE(isolated_instance, web_contents()->GetSiteInstance()); |
| EXPECT_NE(unisolated_instance, web_contents()->GetSiteInstance()); |
| } |
| |
| // Check that opening a popup for an isolated origin puts it into a new process |
| // and its own SiteInstance. |
| IN_PROC_BROWSER_TEST_F(IsolatedOriginTest, Popup) { |
| GURL unisolated_url( |
| embedded_test_server()->GetURL("foo.com", "/title1.html")); |
| GURL isolated_url( |
| embedded_test_server()->GetURL("isolated.foo.com", "/title2.html")); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), unisolated_url)); |
| |
| // Open a popup to a URL with an isolated origin and ensure that there was a |
| // process swap. |
| Shell* popup = OpenPopup(shell(), isolated_url, "foo"); |
| |
| EXPECT_NE(shell()->web_contents()->GetSiteInstance(), |
| popup->web_contents()->GetSiteInstance()); |
| |
| // The popup's site URL should match the full isolated origin. |
| EXPECT_EQ(GURL("http://isolated.foo.com/"), |
| popup->web_contents()->GetSiteInstance()->GetSiteURL()); |
| |
| // Now open a second popup from an isolated origin to a URL with an |
| // unisolated origin and ensure that there was another process swap. |
| Shell* popup2 = OpenPopup(popup, unisolated_url, "bar"); |
| EXPECT_EQ(shell()->web_contents()->GetSiteInstance(), |
| popup2->web_contents()->GetSiteInstance()); |
| EXPECT_NE(popup->web_contents()->GetSiteInstance(), |
| popup2->web_contents()->GetSiteInstance()); |
| } |
| |
| // Check that navigating a subframe to an isolated origin puts the subframe |
| // into an OOPIF and its own SiteInstance. Also check that the isolated |
| // frame's subframes also end up in correct SiteInstance. |
| IN_PROC_BROWSER_TEST_F(IsolatedOriginTest, Subframe) { |
| GURL top_url( |
| embedded_test_server()->GetURL("www.foo.com", "/page_with_iframe.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), top_url)); |
| |
| GURL isolated_url(embedded_test_server()->GetURL("isolated.foo.com", |
| "/page_with_iframe.html")); |
| |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| FrameTreeNode* child = root->child_at(0); |
| |
| NavigateIframeToURL(web_contents(), "test_iframe", isolated_url); |
| EXPECT_EQ(child->current_url(), isolated_url); |
| |
| // Verify that the child frame is an OOPIF with a different SiteInstance. |
| EXPECT_NE(web_contents()->GetSiteInstance(), |
| child->current_frame_host()->GetSiteInstance()); |
| EXPECT_TRUE(child->current_frame_host()->IsCrossProcessSubframe()); |
| EXPECT_EQ(GURL("http://isolated.foo.com/"), |
| child->current_frame_host()->GetSiteInstance()->GetSiteURL()); |
| |
| // Verify that the isolated frame's subframe (which starts out at a relative |
| // path) is kept in the isolated parent's SiteInstance. |
| FrameTreeNode* grandchild = child->child_at(0); |
| EXPECT_EQ(child->current_frame_host()->GetSiteInstance(), |
| grandchild->current_frame_host()->GetSiteInstance()); |
| |
| // Navigating the grandchild to www.foo.com should put it into the top |
| // frame's SiteInstance. |
| GURL non_isolated_url( |
| embedded_test_server()->GetURL("www.foo.com", "/title3.html")); |
| TestFrameNavigationObserver observer(grandchild); |
| EXPECT_TRUE( |
| ExecJs(grandchild, "location.href = '" + non_isolated_url.spec() + "';")); |
| observer.Wait(); |
| EXPECT_EQ(non_isolated_url, grandchild->current_url()); |
| |
| EXPECT_EQ(root->current_frame_host()->GetSiteInstance(), |
| grandchild->current_frame_host()->GetSiteInstance()); |
| EXPECT_NE(child->current_frame_host()->GetSiteInstance(), |
| grandchild->current_frame_host()->GetSiteInstance()); |
| } |
| |
| // Check that when an non-isolated origin foo.com embeds a subframe from an |
| // isolated origin, which then navigates to a non-isolated origin bar.com, |
| // bar.com goes back to the main frame's SiteInstance. See |
| // https://crbug.com/711006. |
| IN_PROC_BROWSER_TEST_F(IsolatedOriginTest, |
| NoOOPIFWhenIsolatedOriginNavigatesToNonIsolatedOrigin) { |
| if (AreAllSitesIsolatedForTesting()) |
| return; |
| |
| GURL top_url( |
| embedded_test_server()->GetURL("www.foo.com", "/page_with_iframe.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), top_url)); |
| |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| FrameTreeNode* child = root->child_at(0); |
| |
| GURL isolated_url(embedded_test_server()->GetURL("isolated.foo.com", |
| "/page_with_iframe.html")); |
| |
| NavigateIframeToURL(web_contents(), "test_iframe", isolated_url); |
| EXPECT_EQ(isolated_url, child->current_url()); |
| |
| // Verify that the child frame is an OOPIF with a different SiteInstance. |
| EXPECT_NE(web_contents()->GetSiteInstance(), |
| child->current_frame_host()->GetSiteInstance()); |
| EXPECT_TRUE(child->current_frame_host()->IsCrossProcessSubframe()); |
| EXPECT_EQ(GURL("http://isolated.foo.com/"), |
| child->current_frame_host()->GetSiteInstance()->GetSiteURL()); |
| |
| // Navigate the child frame cross-site, but to a non-isolated origin. When |
| // strict SiteInstaces are not enabled, this should bring the subframe back |
| // into the main frame's SiteInstance. If strict SiteInstances are enabled, |
| // we expect the SiteInstances to be different because a SiteInstance is not |
| // allowed to contain multiple sites in that mode. In all cases though we |
| // expect the navigation to end up in the same process. |
| GURL bar_url(embedded_test_server()->GetURL("bar.com", "/title1.html")); |
| EXPECT_FALSE(IsIsolatedOrigin(bar_url)); |
| NavigateIframeToURL(web_contents(), "test_iframe", bar_url); |
| |
| if (AreStrictSiteInstancesEnabled()) { |
| EXPECT_NE(web_contents()->GetSiteInstance(), |
| child->current_frame_host()->GetSiteInstance()); |
| } else { |
| EXPECT_EQ(web_contents()->GetSiteInstance(), |
| child->current_frame_host()->GetSiteInstance()); |
| } |
| EXPECT_EQ(web_contents()->GetSiteInstance()->GetProcess(), |
| child->current_frame_host()->GetSiteInstance()->GetProcess()); |
| } |
| |
| // Check that a new isolated origin subframe will attempt to reuse an existing |
| // process for that isolated origin, even across BrowsingInstances. Also check |
| // that main frame navigations to an isolated origin keep using the default |
| // process model and do not reuse existing processes. |
| IN_PROC_BROWSER_TEST_F(IsolatedOriginTest, SubframeReusesExistingProcess) { |
| GURL top_url( |
| embedded_test_server()->GetURL("www.foo.com", "/page_with_iframe.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), top_url)); |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| FrameTreeNode* child = root->child_at(0); |
| |
| // Open an unrelated tab in a separate BrowsingInstance, and navigate it to |
| // to an isolated origin. This SiteInstance should have a default process |
| // reuse policy - only subframes attempt process reuse. |
| GURL isolated_url(embedded_test_server()->GetURL("isolated.foo.com", |
| "/page_with_iframe.html")); |
| Shell* second_shell = CreateBrowser(); |
| EXPECT_TRUE(NavigateToURL(second_shell, isolated_url)); |
| scoped_refptr<SiteInstanceImpl> second_shell_instance = |
| static_cast<SiteInstanceImpl*>(second_shell->web_contents() |
| ->GetPrimaryMainFrame() |
| ->GetSiteInstance()); |
| EXPECT_FALSE(second_shell_instance->IsRelatedSiteInstance( |
| root->current_frame_host()->GetSiteInstance())); |
| RenderProcessHost* isolated_process = second_shell_instance->GetProcess(); |
| EXPECT_EQ(SiteInstanceImpl::ProcessReusePolicy::DEFAULT, |
| second_shell_instance->process_reuse_policy()); |
| |
| // Now navigate the first tab's subframe to an isolated origin. See that it |
| // reuses the existing `isolated_process`. |
| NavigateIframeToURL(web_contents(), "test_iframe", isolated_url); |
| EXPECT_EQ(isolated_url, child->current_url()); |
| EXPECT_EQ(isolated_process, child->current_frame_host()->GetProcess()); |
| EXPECT_EQ( |
| SiteInstanceImpl::ProcessReusePolicy::REUSE_PENDING_OR_COMMITTED_SITE, |
| child->current_frame_host()->GetSiteInstance()->process_reuse_policy()); |
| |
| EXPECT_TRUE(child->current_frame_host()->IsCrossProcessSubframe()); |
| EXPECT_EQ(GURL("http://isolated.foo.com/"), |
| child->current_frame_host()->GetSiteInstance()->GetSiteURL()); |
| |
| // The subframe's SiteInstance should still be different from second_shell's |
| // SiteInstance, and they should be in separate BrowsingInstances. |
| EXPECT_NE(second_shell_instance, |
| child->current_frame_host()->GetSiteInstance()); |
| EXPECT_FALSE(second_shell_instance->IsRelatedSiteInstance( |
| child->current_frame_host()->GetSiteInstance())); |
| |
| // Navigate the second tab to a normal URL with a same-site subframe. This |
| // leaves only the first tab's subframe in the isolated origin process. |
| EXPECT_TRUE(NavigateToURL(second_shell, top_url)); |
| EXPECT_NE(isolated_process, |
| second_shell->web_contents()->GetPrimaryMainFrame()->GetProcess()); |
| |
| // Navigate the second tab's subframe to an isolated origin, and check that |
| // this new subframe reuses the isolated process of the subframe in the first |
| // tab, even though the two are in separate BrowsingInstances. |
| NavigateIframeToURL(second_shell->web_contents(), "test_iframe", |
| isolated_url); |
| FrameTreeNode* second_subframe = |
| static_cast<WebContentsImpl*>(second_shell->web_contents()) |
| ->GetPrimaryFrameTree() |
| .root() |
| ->child_at(0); |
| EXPECT_EQ(isolated_process, |
| second_subframe->current_frame_host()->GetProcess()); |
| EXPECT_NE(child->current_frame_host()->GetSiteInstance(), |
| second_subframe->current_frame_host()->GetSiteInstance()); |
| |
| // Open a third, unrelated tab, navigate it to an isolated origin, and check |
| // that its main frame doesn't share a process with the existing isolated |
| // subframes. |
| Shell* third_shell = CreateBrowser(); |
| EXPECT_TRUE(NavigateToURL(third_shell, isolated_url)); |
| SiteInstanceImpl* third_shell_instance = static_cast<SiteInstanceImpl*>( |
| third_shell->web_contents()->GetPrimaryMainFrame()->GetSiteInstance()); |
| EXPECT_NE(third_shell_instance, |
| second_subframe->current_frame_host()->GetSiteInstance()); |
| EXPECT_NE(third_shell_instance, |
| child->current_frame_host()->GetSiteInstance()); |
| EXPECT_NE(third_shell_instance->GetProcess(), isolated_process); |
| } |
| |
| // Check that when a cross-site, non-isolated-origin iframe opens a popup, |
| // navigates it to an isolated origin, and then the popup navigates back to its |
| // opener iframe's site, the popup and the opener iframe end up in the same |
| // process and can script each other. See https://crbug.com/796912. |
| IN_PROC_BROWSER_TEST_F(IsolatedOriginTest, |
| PopupNavigatesToIsolatedOriginAndBack) { |
| // Start on a page with same-site iframe. |
| GURL foo_url( |
| embedded_test_server()->GetURL("www.foo.com", "/page_with_iframe.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), foo_url)); |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| FrameTreeNode* child = root->child_at(0); |
| |
| // Navigate iframe cross-site, but not to an isolated origin. This should |
| // stay in the main frame's SiteInstance, unless we're in a strict |
| // SiteInstance mode (including --site-per-process). (Note that the bug for |
| // which this test is written is exclusive to --isolate-origins and does not |
| // happen with --site-per-process.) |
| GURL bar_url(embedded_test_server()->GetURL("bar.com", "/title1.html")); |
| NavigateIframeToURL(web_contents(), "test_iframe", bar_url); |
| if (AreStrictSiteInstancesEnabled()) { |
| EXPECT_NE(root->current_frame_host()->GetSiteInstance(), |
| child->current_frame_host()->GetSiteInstance()); |
| } else { |
| EXPECT_EQ(root->current_frame_host()->GetSiteInstance(), |
| child->current_frame_host()->GetSiteInstance()); |
| } |
| |
| // Open a blank popup from the iframe. |
| ShellAddedObserver new_shell_observer; |
| EXPECT_TRUE(ExecJs(child, "window.w = window.open();")); |
| Shell* new_shell = new_shell_observer.GetShell(); |
| |
| // Have the opener iframe navigate the popup to an isolated origin. |
| GURL isolated_url( |
| embedded_test_server()->GetURL("isolated.foo.com", "/title1.html")); |
| { |
| TestNavigationManager manager(new_shell->web_contents(), isolated_url); |
| EXPECT_TRUE(ExecJs( |
| child, "window.w.location.href = '" + isolated_url.spec() + "';")); |
| ASSERT_TRUE(manager.WaitForNavigationFinished()); |
| } |
| |
| // Simulate the isolated origin in the popup navigating back to bar.com. |
| GURL bar_url2(embedded_test_server()->GetURL("bar.com", "/title2.html")); |
| { |
| TestNavigationManager manager(new_shell->web_contents(), bar_url2); |
| EXPECT_TRUE( |
| ExecJs(new_shell, "location.href = '" + bar_url2.spec() + "';")); |
| ASSERT_TRUE(manager.WaitForNavigationFinished()); |
| } |
| |
| // Check that the popup ended up in the same SiteInstance as its same-site |
| // opener iframe. |
| EXPECT_EQ(new_shell->web_contents()->GetPrimaryMainFrame()->GetSiteInstance(), |
| child->current_frame_host()->GetSiteInstance()); |
| |
| // Check that the opener iframe can script the popup. |
| EXPECT_EQ(bar_url2.spec(), EvalJs(child, "window.w.location.href;")); |
| } |
| |
| // Check that when a non-isolated-origin page opens a popup, navigates it |
| // to an isolated origin, and then the popup navigates to a third non-isolated |
| // origin and finally back to its opener's origin, the popup and the opener |
| // iframe end up in the same process and can script each other: |
| // |
| // foo.com |
| // | |
| // window.open() |
| // | |
| // V |
| // about:blank -> isolated.foo.com -> bar.com -> foo.com |
| // |
| // This is a variant of PopupNavigatesToIsolatedOriginAndBack where the popup |
| // navigates to a third site before coming back to the opener's site. See |
| // https://crbug.com/807184. |
| IN_PROC_BROWSER_TEST_F(IsolatedOriginTest, |
| PopupNavigatesToIsolatedOriginThenToAnotherSiteAndBack) { |
| // Start on www.foo.com. |
| GURL foo_url(embedded_test_server()->GetURL("www.foo.com", "/title1.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), foo_url)); |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| |
| // Open a blank popup. |
| ShellAddedObserver new_shell_observer; |
| EXPECT_TRUE(ExecJs(root, "window.w = window.open();")); |
| Shell* new_shell = new_shell_observer.GetShell(); |
| |
| // Have the opener navigate the popup to an isolated origin. |
| GURL isolated_url( |
| embedded_test_server()->GetURL("isolated.foo.com", "/title1.html")); |
| { |
| TestNavigationManager manager(new_shell->web_contents(), isolated_url); |
| EXPECT_TRUE(ExecJs( |
| root, "window.w.location.href = '" + isolated_url.spec() + "';")); |
| ASSERT_TRUE(manager.WaitForNavigationFinished()); |
| } |
| |
| // Simulate the isolated origin in the popup navigating to bar.com. |
| GURL bar_url(embedded_test_server()->GetURL("bar.com", "/title2.html")); |
| { |
| TestNavigationManager manager(new_shell->web_contents(), bar_url); |
| EXPECT_TRUE(ExecJs(new_shell, "location.href = '" + bar_url.spec() + "';")); |
| ASSERT_TRUE(manager.WaitForNavigationFinished()); |
| } |
| |
| const SiteInstanceImpl* const root_site_instance_impl = |
| static_cast<SiteInstanceImpl*>( |
| root->current_frame_host()->GetSiteInstance()); |
| const SiteInstanceImpl* const newshell_site_instance_impl = |
| static_cast<SiteInstanceImpl*>( |
| new_shell->web_contents()->GetPrimaryMainFrame()->GetSiteInstance()); |
| if (AreDefaultSiteInstancesEnabled()) { |
| // When default SiteInstances are enabled, all sites that do not |
| // require a dedicated process all end up in the same default SiteInstance. |
| EXPECT_EQ(newshell_site_instance_impl, root_site_instance_impl); |
| EXPECT_TRUE(newshell_site_instance_impl->IsDefaultSiteInstance()); |
| } else { |
| // At this point, the popup and the opener should still be in separate |
| // SiteInstances. |
| EXPECT_NE(newshell_site_instance_impl, root_site_instance_impl); |
| EXPECT_FALSE(newshell_site_instance_impl->IsDefaultSiteInstance()); |
| EXPECT_FALSE(root_site_instance_impl->IsDefaultSiteInstance()); |
| } |
| |
| // Simulate the isolated origin in the popup navigating to www.foo.com. |
| { |
| TestNavigationManager manager(new_shell->web_contents(), foo_url); |
| EXPECT_TRUE(ExecJs(new_shell, "location.href = '" + foo_url.spec() + "';")); |
| ASSERT_TRUE(manager.WaitForNavigationFinished()); |
| } |
| |
| // The popup should now be in the same SiteInstance as its same-site opener. |
| EXPECT_EQ(new_shell->web_contents()->GetPrimaryMainFrame()->GetSiteInstance(), |
| root->current_frame_host()->GetSiteInstance()); |
| |
| // Check that the popup can script the opener. |
| EXPECT_EQ(foo_url.spec(), EvalJs(new_shell, "window.opener.location.href;")); |
| } |
| |
| // Check that with an ABA hierarchy, where B is an isolated origin, the root |
| // and grandchild frames end up in the same process and can script each other. |
| // See https://crbug.com/796912. |
| IN_PROC_BROWSER_TEST_F(IsolatedOriginTest, |
| IsolatedOriginSubframeCreatesGrandchildInRootSite) { |
| // Start at foo.com and do a cross-site, renderer-initiated navigation to |
| // bar.com, which should stay in the same SiteInstance (outside of |
| // --site-per-process mode). This sets up the main frame such that its |
| // SiteInstance's site URL does not match its actual origin - a prerequisite |
| // for https://crbug.com/796912 to happen. |
| GURL foo_url(embedded_test_server()->GetURL("foo.com", "/title1.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), foo_url)); |
| GURL bar_url( |
| embedded_test_server()->GetURL("bar.com", "/page_with_iframe.html")); |
| TestNavigationObserver observer(web_contents()); |
| EXPECT_TRUE(ExecJs(shell(), "location.href = '" + bar_url.spec() + "';")); |
| observer.Wait(); |
| |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| FrameTreeNode* child = root->child_at(0); |
| |
| // Navigate bar.com's subframe to an isolated origin with its own subframe. |
| GURL isolated_url(embedded_test_server()->GetURL("isolated.foo.com", |
| "/page_with_iframe.html")); |
| NavigateIframeToURL(web_contents(), "test_iframe", isolated_url); |
| EXPECT_EQ(isolated_url, child->current_url()); |
| FrameTreeNode* grandchild = child->child_at(0); |
| |
| // Navigate the isolated origin's subframe back to bar.com, completing the |
| // ABA hierarchy. |
| EXPECT_TRUE(NavigateToURLFromRenderer(grandchild, bar_url)); |
| |
| // The root and grandchild should be in the same SiteInstance, and the |
| // middle child should be in a different SiteInstance. |
| EXPECT_NE(root->current_frame_host()->GetSiteInstance(), |
| child->current_frame_host()->GetSiteInstance()); |
| EXPECT_NE(child->current_frame_host()->GetSiteInstance(), |
| grandchild->current_frame_host()->GetSiteInstance()); |
| EXPECT_EQ(root->current_frame_host()->GetSiteInstance(), |
| grandchild->current_frame_host()->GetSiteInstance()); |
| |
| // Check that the root frame can script the same-site grandchild frame. |
| EXPECT_EQ(bar_url.spec(), EvalJs(root, "frames[0][0].location.href;")); |
| } |
| |
| // Check that isolated origins can access cookies. This requires cookie checks |
| // on the IO thread to be aware of isolated origins. |
| IN_PROC_BROWSER_TEST_F(IsolatedOriginTest, Cookies) { |
| GURL isolated_url( |
| embedded_test_server()->GetURL("isolated.foo.com", "/title2.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), isolated_url)); |
| |
| EXPECT_TRUE(ExecJs(web_contents(), "document.cookie = 'foo=bar';")); |
| |
| EXPECT_EQ("foo=bar", EvalJs(web_contents(), "document.cookie;")); |
| } |
| |
| // Check that isolated origins won't be placed into processes for other sites |
| // when over the process limit. |
| IN_PROC_BROWSER_TEST_F(IsolatedOriginTest, ProcessLimit) { |
| // Set the process limit to 1. |
| RenderProcessHost::SetMaxRendererProcessCount(1); |
| |
| // Navigate to an unisolated foo.com URL with an iframe. |
| GURL foo_url( |
| embedded_test_server()->GetURL("www.foo.com", "/page_with_iframe.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), foo_url)); |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| RenderProcessHost* foo_process = root->current_frame_host()->GetProcess(); |
| FrameTreeNode* child = root->child_at(0); |
| |
| // Navigate iframe to an isolated origin. |
| GURL isolated_foo_url( |
| embedded_test_server()->GetURL("isolated.foo.com", "/title2.html")); |
| NavigateIframeToURL(web_contents(), "test_iframe", isolated_foo_url); |
| |
| // Ensure that the subframe was rendered in a new process. |
| EXPECT_NE(child->current_frame_host()->GetProcess(), foo_process); |
| |
| // Sanity-check IsSuitableHost values for the current processes. |
| const IsolationContext& isolation_context = |
| root->current_frame_host()->GetSiteInstance()->GetIsolationContext(); |
| auto is_suitable_host = [&isolation_context](RenderProcessHost* process, |
| const GURL& url) { |
| return RenderProcessHostImpl::IsSuitableHost( |
| process, isolation_context, |
| SiteInfo::CreateForTesting(isolation_context, url)); |
| }; |
| EXPECT_TRUE(is_suitable_host(foo_process, foo_url)); |
| EXPECT_FALSE(is_suitable_host(foo_process, isolated_foo_url)); |
| EXPECT_TRUE(is_suitable_host(child->current_frame_host()->GetProcess(), |
| isolated_foo_url)); |
| EXPECT_FALSE( |
| is_suitable_host(child->current_frame_host()->GetProcess(), foo_url)); |
| |
| // Open a new, unrelated tab and navigate it to isolated.foo.com. This |
| // should use a new, unrelated SiteInstance that reuses the existing isolated |
| // origin process from first tab's subframe. |
| Shell* new_shell = CreateBrowser(); |
| EXPECT_TRUE(NavigateToURL(new_shell, isolated_foo_url)); |
| scoped_refptr<SiteInstance> isolated_foo_instance( |
| new_shell->web_contents()->GetPrimaryMainFrame()->GetSiteInstance()); |
| RenderProcessHost* isolated_foo_process = isolated_foo_instance->GetProcess(); |
| EXPECT_NE(child->current_frame_host()->GetSiteInstance(), |
| isolated_foo_instance); |
| EXPECT_FALSE(isolated_foo_instance->IsRelatedSiteInstance( |
| child->current_frame_host()->GetSiteInstance())); |
| // TODO(alexmos): with --site-per-process, this won't currently reuse the |
| // subframe process, because the new SiteInstance will initialize its |
| // process while it still has no site (during CreateBrowser()), and since |
| // dedicated processes can't currently be reused for a SiteInstance with no |
| // site, this creates a new process. The subsequent navigation to |
| // `isolated_foo_url` stays in that new process without consulting whether it |
| // can now reuse a different process. This should be fixed; see |
| // https://crbug.com/513036. Without --site-per-process, this works because |
| // the site-less SiteInstance is allowed to reuse the first tab's foo.com |
| // process (which isn't dedicated), and then it swaps to the isolated.foo.com |
| // process during navigation. |
| if (!AreAllSitesIsolatedForTesting()) |
| EXPECT_EQ(child->current_frame_host()->GetProcess(), isolated_foo_process); |
| |
| // Navigate iframe on the first tab to a non-isolated site. This should swap |
| // processes so that it does not reuse the isolated origin's process. |
| RenderFrameDeletedObserver deleted_observer(child->current_frame_host()); |
| NavigateIframeToURL( |
| web_contents(), "test_iframe", |
| embedded_test_server()->GetURL("www.foo.com", "/title1.html")); |
| EXPECT_EQ(foo_process, child->current_frame_host()->GetProcess()); |
| EXPECT_NE(isolated_foo_process, child->current_frame_host()->GetProcess()); |
| deleted_observer.WaitUntilDeleted(); |
| |
| // Navigate iframe back to isolated origin. See that it reuses the |
| // `new_shell` process. |
| NavigateIframeToURL(web_contents(), "test_iframe", isolated_foo_url); |
| EXPECT_NE(foo_process, child->current_frame_host()->GetProcess()); |
| EXPECT_EQ(isolated_foo_process, child->current_frame_host()->GetProcess()); |
| |
| // Navigate iframe to a different isolated origin. Ensure that this creates |
| // a third process. |
| GURL isolated_bar_url( |
| embedded_test_server()->GetURL("isolated.bar.com", "/title3.html")); |
| NavigateIframeToURL(web_contents(), "test_iframe", isolated_bar_url); |
| RenderProcessHost* isolated_bar_process = |
| child->current_frame_host()->GetProcess(); |
| EXPECT_NE(foo_process, isolated_bar_process); |
| EXPECT_NE(isolated_foo_process, isolated_bar_process); |
| |
| // The new process should only be suitable to host isolated.bar.com, not |
| // regular web URLs or other isolated origins. |
| EXPECT_TRUE(is_suitable_host(isolated_bar_process, isolated_bar_url)); |
| EXPECT_FALSE(is_suitable_host(isolated_bar_process, foo_url)); |
| EXPECT_FALSE(is_suitable_host(isolated_bar_process, isolated_foo_url)); |
| |
| // Navigate second tab (currently at isolated.foo.com) to the |
| // second isolated origin, and see that it switches processes. |
| EXPECT_TRUE(NavigateToURL(new_shell, isolated_bar_url)); |
| EXPECT_NE(foo_process, |
| new_shell->web_contents()->GetPrimaryMainFrame()->GetProcess()); |
| EXPECT_NE(isolated_foo_process, |
| new_shell->web_contents()->GetPrimaryMainFrame()->GetProcess()); |
| EXPECT_EQ(isolated_bar_process, |
| new_shell->web_contents()->GetPrimaryMainFrame()->GetProcess()); |
| |
| // Navigate second tab to a non-isolated URL and see that it goes back into |
| // the www.foo.com process, and that it does not share processes with any |
| // isolated origins. |
| EXPECT_TRUE(NavigateToURL(new_shell, foo_url)); |
| EXPECT_EQ(foo_process, |
| new_shell->web_contents()->GetPrimaryMainFrame()->GetProcess()); |
| EXPECT_NE(isolated_foo_process, |
| new_shell->web_contents()->GetPrimaryMainFrame()->GetProcess()); |
| EXPECT_NE(isolated_bar_process, |
| new_shell->web_contents()->GetPrimaryMainFrame()->GetProcess()); |
| } |
| |
| // Verify that a navigation to an non-isolated origin does not reuse a process |
| // from a pending navigation to an isolated origin. See |
| // https://crbug.com/738634. |
| IN_PROC_BROWSER_TEST_F(IsolatedOriginTest, |
| ProcessReuseWithResponseStartedFromIsolatedOrigin) { |
| // Set the process limit to 1. |
| RenderProcessHost::SetMaxRendererProcessCount(1); |
| |
| // Start, but don't commit a navigation to an unisolated foo.com URL. |
| GURL slow_url(embedded_test_server()->GetURL("www.foo.com", "/title1.html")); |
| NavigationController::LoadURLParams load_params(slow_url); |
| TestNavigationManager foo_delayer(shell()->web_contents(), slow_url); |
| shell()->web_contents()->GetController().LoadURL( |
| slow_url, Referrer(), ui::PAGE_TRANSITION_LINK, std::string()); |
| EXPECT_TRUE(foo_delayer.WaitForRequestStart()); |
| |
| // Open a new, unrelated tab and navigate it to isolated.foo.com. |
| Shell* new_shell = CreateBrowser(); |
| GURL isolated_url( |
| embedded_test_server()->GetURL("isolated.foo.com", "/title2.html")); |
| TestNavigationManager isolated_delayer(new_shell->web_contents(), |
| isolated_url); |
| new_shell->web_contents()->GetController().LoadURL( |
| isolated_url, Referrer(), ui::PAGE_TRANSITION_LINK, std::string()); |
| |
| // Wait for the response from the isolated origin. After this returns, we made |
| // the final pick for the process to use for this navigation as part of |
| // NavigationRequest::OnResponseStarted. |
| EXPECT_TRUE(isolated_delayer.WaitForResponse()); |
| |
| // Now, proceed with the response and commit the non-isolated URL. This |
| // should notice that the process that was picked for this navigation is not |
| // suitable anymore, as it should have been locked to isolated.foo.com. |
| ASSERT_TRUE(foo_delayer.WaitForNavigationFinished()); |
| |
| // Commit the isolated origin. |
| ASSERT_TRUE(isolated_delayer.WaitForNavigationFinished()); |
| |
| // Ensure that the isolated origin did not share a process with the first |
| // tab. |
| EXPECT_NE(web_contents()->GetPrimaryMainFrame()->GetProcess(), |
| new_shell->web_contents()->GetPrimaryMainFrame()->GetProcess()); |
| } |
| |
| // When a navigation uses a siteless SiteInstance, and a second navigation |
| // commits an isolated origin which reuses the siteless SiteInstance's process |
| // before the first navigation's response is received, ensure that the first |
| // navigation can still finish properly and transfer to a new process, without |
| // an origin lock mismatch. See https://crbug.com/773809. |
| IN_PROC_BROWSER_TEST_F(IsolatedOriginTest, |
| ProcessReuseWithLazilyAssignedSiteInstance) { |
| // Set the process limit to 1. |
| RenderProcessHost::SetMaxRendererProcessCount(1); |
| |
| // Start from an about:blank page, where the SiteInstance will not have a |
| // site assigned, but will have an associated process. |
| EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL))); |
| SiteInstanceImpl* starting_site_instance = static_cast<SiteInstanceImpl*>( |
| shell()->web_contents()->GetPrimaryMainFrame()->GetSiteInstance()); |
| EXPECT_FALSE(starting_site_instance->HasSite()); |
| EXPECT_TRUE(starting_site_instance->HasProcess()); |
| |
| // Inject and click a link to a non-isolated origin www.foo.com. Note that |
| // setting location.href won't work here, as that goes through OpenURL |
| // instead of OnBeginNavigation when starting from an about:blank page, and |
| // that doesn't trigger this bug. |
| GURL foo_url(embedded_test_server()->GetURL("www.foo.com", "/title1.html")); |
| TestNavigationManager manager(shell()->web_contents(), foo_url); |
| InjectAndClickLinkTo(foo_url); |
| EXPECT_TRUE(manager.WaitForRequestStart()); |
| |
| // Before response is received, open a new, unrelated tab and navigate it to |
| // isolated.foo.com. This reuses the first process, which is still considered |
| // unused at this point, and locks it to isolated.foo.com. |
| Shell* new_shell = CreateBrowser(); |
| GURL isolated_url( |
| embedded_test_server()->GetURL("isolated.foo.com", "/title2.html")); |
| EXPECT_TRUE(NavigateToURL(new_shell, isolated_url)); |
| EXPECT_EQ(web_contents()->GetPrimaryMainFrame()->GetProcess(), |
| new_shell->web_contents()->GetPrimaryMainFrame()->GetProcess()); |
| |
| // Wait for response from the first tab. This should notice that the first |
| // process is no longer suitable for the final destination (which is an |
| // unisolated URL) and transfer to another process. In |
| // https://crbug.com/773809, this led to a CHECK due to origin lock mismatch. |
| ASSERT_TRUE(manager.WaitForNavigationFinished()); |
| |
| // Ensure that the isolated origin did not share a process with the first |
| // tab. |
| EXPECT_NE(web_contents()->GetPrimaryMainFrame()->GetProcess(), |
| new_shell->web_contents()->GetPrimaryMainFrame()->GetProcess()); |
| } |
| |
| // Same as ProcessReuseWithLazilyAssignedSiteInstance above, but here the |
| // navigation with a siteless SiteInstance is for an isolated origin, and the |
| // unrelated tab loads an unisolated URL which reuses the siteless |
| // SiteInstance's process. Although the unisolated URL won't lock that process |
| // to an origin (except when running with --site-per-process), it should still |
| // mark it as used and cause the isolated origin to transfer when it receives a |
| // response. See https://crbug.com/773809. |
| IN_PROC_BROWSER_TEST_F(IsolatedOriginTest, |
| ProcessReuseWithLazilyAssignedIsolatedSiteInstance) { |
| // Set the process limit to 1. |
| RenderProcessHost::SetMaxRendererProcessCount(1); |
| |
| // Start from an about:blank page, where the SiteInstance will not have a |
| // site assigned, but will have an associated process. |
| EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL))); |
| SiteInstanceImpl* starting_site_instance = static_cast<SiteInstanceImpl*>( |
| shell()->web_contents()->GetPrimaryMainFrame()->GetSiteInstance()); |
| EXPECT_FALSE(starting_site_instance->HasSite()); |
| EXPECT_TRUE(starting_site_instance->HasProcess()); |
| EXPECT_TRUE(web_contents()->GetPrimaryMainFrame()->GetProcess()->IsUnused()); |
| |
| // Inject and click a link to an isolated origin. Note that |
| // setting location.href won't work here, as that goes through OpenURL |
| // instead of OnBeginNavigation when starting from an about:blank page, and |
| // that doesn't trigger this bug. |
| GURL isolated_url( |
| embedded_test_server()->GetURL("isolated.foo.com", "/title2.html")); |
| TestNavigationManager manager(shell()->web_contents(), isolated_url); |
| InjectAndClickLinkTo(isolated_url); |
| EXPECT_TRUE(manager.WaitForRequestStart()); |
| |
| // Before response is received, open a new, unrelated tab and navigate it to |
| // an unisolated URL. This should reuse the first process, which is still |
| // considered unused at this point, and marks it as used. |
| Shell* new_shell = CreateBrowser(); |
| GURL foo_url(embedded_test_server()->GetURL("www.foo.com", "/title1.html")); |
| EXPECT_TRUE(NavigateToURL(new_shell, foo_url)); |
| EXPECT_EQ(web_contents()->GetPrimaryMainFrame()->GetProcess(), |
| new_shell->web_contents()->GetPrimaryMainFrame()->GetProcess()); |
| EXPECT_FALSE(web_contents()->GetPrimaryMainFrame()->GetProcess()->IsUnused()); |
| |
| // Wait for response in the first tab. This should notice that the first |
| // process is no longer suitable for the isolated origin because it should |
| // already be marked as used, and transfer to another process. |
| ASSERT_TRUE(manager.WaitForNavigationFinished()); |
| |
| // Ensure that the isolated origin did not share a process with the second |
| // tab. |
| EXPECT_NE(web_contents()->GetPrimaryMainFrame()->GetProcess(), |
| new_shell->web_contents()->GetPrimaryMainFrame()->GetProcess()); |
| } |
| |
| // Verify that a navigation to an unisolated origin cannot reuse a process from |
| // a pending navigation to an isolated origin. Similar to |
| // ProcessReuseWithResponseStartedFromIsolatedOrigin, but here the non-isolated |
| // URL is the first to reach OnResponseStarted, which should mark the process |
| // as "used", so that the isolated origin can't reuse it. See |
| // https://crbug.com/738634. |
| IN_PROC_BROWSER_TEST_F(IsolatedOriginTest, |
| ProcessReuseWithResponseStartedFromUnisolatedOrigin) { |
| // Set the process limit to 1. |
| RenderProcessHost::SetMaxRendererProcessCount(1); |
| |
| // Start a navigation to an unisolated foo.com URL. |
| GURL slow_url(embedded_test_server()->GetURL("www.foo.com", "/title1.html")); |
| NavigationController::LoadURLParams load_params(slow_url); |
| TestNavigationManager foo_delayer(shell()->web_contents(), slow_url); |
| shell()->web_contents()->GetController().LoadURL( |
| slow_url, Referrer(), ui::PAGE_TRANSITION_LINK, std::string()); |
| |
| // Wait for the response for foo.com. After this returns, we should have made |
| // the final pick for the process to use for foo.com, so this should mark the |
| // process as "used" and ineligible for reuse by isolated.foo.com below. |
| EXPECT_TRUE(foo_delayer.WaitForResponse()); |
| |
| // Open a new, unrelated tab, navigate it to isolated.foo.com, and wait for |
| // the navigation to fully load. |
| Shell* new_shell = CreateBrowser(); |
| GURL isolated_url( |
| embedded_test_server()->GetURL("isolated.foo.com", "/title2.html")); |
| EXPECT_TRUE(NavigateToURL(new_shell, isolated_url)); |
| |
| // Finish loading the foo.com URL. |
| ASSERT_TRUE(foo_delayer.WaitForNavigationFinished()); |
| |
| // Ensure that the isolated origin did not share a process with the first |
| // tab. |
| EXPECT_NE(web_contents()->GetPrimaryMainFrame()->GetProcess(), |
| new_shell->web_contents()->GetPrimaryMainFrame()->GetProcess()); |
| } |
| |
| // Verify that when a process has a pending SiteProcessCountTracker entry for |
| // an isolated origin, and a navigation to a non-isolated origin reuses that |
| // process, future isolated origin subframe navigations do not reuse that |
| // process. See https://crbug.com/780661. |
| IN_PROC_BROWSER_TEST_F( |
| IsolatedOriginTest, |
| IsolatedSubframeDoesNotReuseUnsuitableProcessWithPendingSiteEntry) { |
| // Set the process limit to 1. |
| RenderProcessHost::SetMaxRendererProcessCount(1); |
| |
| // Start from an about:blank page, where the SiteInstance will not have a |
| // site assigned, but will have an associated process. |
| EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL))); |
| EXPECT_TRUE(web_contents()->GetPrimaryMainFrame()->GetProcess()->IsUnused()); |
| |
| // Inject and click a link to an isolated origin URL which never sends back a |
| // response. |
| GURL hung_isolated_url( |
| embedded_test_server()->GetURL("isolated.foo.com", "/hung")); |
| TestNavigationManager manager(web_contents(), hung_isolated_url); |
| InjectAndClickLinkTo(hung_isolated_url); |
| |
| // Wait for the request and send it. This will place |
| // isolated.foo.com on the list of pending sites for this tab's process. |
| EXPECT_TRUE(manager.WaitForRequestStart()); |
| manager.ResumeNavigation(); |
| |
| // Open a new, unrelated tab and navigate it to an unisolated URL. This |
| // should reuse the first process, which is still considered unused at this |
| // point, and mark it as used. |
| Shell* new_shell = CreateBrowser(); |
| GURL foo_url( |
| embedded_test_server()->GetURL("www.foo.com", "/page_with_iframe.html")); |
| EXPECT_TRUE(NavigateToURL(new_shell, foo_url)); |
| |
| // Navigate iframe on second tab to isolated.foo.com. This should *not* |
| // reuse the first process, even though isolated.foo.com is still in its list |
| // of pending sites (from the hung navigation in the first tab). That |
| // process is unsuitable because it now contains www.foo.com. |
| GURL isolated_url( |
| embedded_test_server()->GetURL("isolated.foo.com", "/title1.html")); |
| NavigateIframeToURL(new_shell->web_contents(), "test_iframe", isolated_url); |
| |
| FrameTreeNode* root = static_cast<WebContentsImpl*>(new_shell->web_contents()) |
| ->GetPrimaryFrameTree() |
| .root(); |
| FrameTreeNode* child = root->child_at(0); |
| EXPECT_NE(child->current_frame_host()->GetProcess(), |
| root->current_frame_host()->GetProcess()); |
| |
| // Manipulating cookies from the main frame should not result in a renderer |
| // kill. |
| EXPECT_TRUE( |
| ExecJs(root->current_frame_host(), "document.cookie = 'foo=bar';")); |
| EXPECT_EQ("foo=bar", EvalJs(root->current_frame_host(), "document.cookie;")); |
| } |
| |
| // Similar to the test above, but for a ServiceWorker. When a process has a |
| // pending SiteProcessCountTracker entry for an isolated origin, and a |
| // navigation to a non-isolated origin reuses that process, a ServiceWorker |
| // subsequently created for that isolated origin shouldn't reuse that process. |
| // See https://crbug.com/780661 and https://crbug.com/780089. |
| IN_PROC_BROWSER_TEST_F( |
| IsolatedOriginTest, |
| IsolatedServiceWorkerDoesNotReuseUnsuitableProcessWithPendingSiteEntry) { |
| // Set the process limit to 1. |
| RenderProcessHost::SetMaxRendererProcessCount(1); |
| |
| // Start from an about:blank page, where the SiteInstance will not have a |
| // site assigned, but will have an associated process. |
| EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL))); |
| EXPECT_TRUE(web_contents()->GetPrimaryMainFrame()->GetProcess()->IsUnused()); |
| |
| // Inject and click a link to an isolated origin URL which never sends back a |
| // response. |
| GURL hung_isolated_url( |
| embedded_test_server()->GetURL("isolated.foo.com", "/hung")); |
| TestNavigationManager manager(shell()->web_contents(), hung_isolated_url); |
| InjectAndClickLinkTo(hung_isolated_url); |
| |
| // Wait for the request and send it. This will place |
| // isolated.foo.com on the list of pending sites for this tab's process. |
| EXPECT_TRUE(manager.WaitForRequestStart()); |
| manager.ResumeNavigation(); |
| |
| // Open a new, unrelated tab and navigate it to an unisolated URL. This |
| // should reuse the first process, which is still considered unused at this |
| // point, and mark it as used. |
| Shell* new_shell = CreateBrowser(); |
| GURL foo_url(embedded_test_server()->GetURL("www.foo.com", "/title1.html")); |
| EXPECT_TRUE(NavigateToURL(new_shell, foo_url)); |
| |
| // A SiteInstance created for an isolated origin ServiceWorker should |
| // not reuse the unsuitable first process. |
| BrowserContext* browser_context = web_contents()->GetBrowserContext(); |
| scoped_refptr<SiteInstanceImpl> sw_site_instance = |
| SiteInstanceImpl::CreateForServiceWorker( |
| browser_context, |
| UrlInfo::CreateForTesting( |
| hung_isolated_url, |
| StoragePartitionConfig::CreateDefault(browser_context)), |
| /* can_reuse_process= */ true); |
| RenderProcessHost* sw_host = sw_site_instance->GetProcess(); |
| EXPECT_NE(new_shell->web_contents()->GetPrimaryMainFrame()->GetProcess(), |
| sw_host); |
| |
| // Cancel the hung request and commit a real navigation to an isolated |
| // origin. This should now end up in the ServiceWorker's process. |
| web_contents()->GetPrimaryFrameTree().root()->ResetNavigationRequest( |
| NavigationDiscardReason::kCancelled); |
| GURL isolated_url( |
| embedded_test_server()->GetURL("isolated.foo.com", "/title1.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), isolated_url)); |
| EXPECT_EQ(web_contents()->GetPrimaryMainFrame()->GetProcess(), sw_host); |
| } |
| |
| // Check that subdomains on an isolated origin (e.g., bar.isolated.foo.com) |
| // also end up in the isolated origin's SiteInstance. |
| IN_PROC_BROWSER_TEST_F(IsolatedOriginTest, IsolatedOriginWithSubdomain) { |
| // Start on a page with an isolated origin with a same-site iframe. |
| GURL isolated_url(embedded_test_server()->GetURL("isolated.foo.com", |
| "/page_with_iframe.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), isolated_url)); |
| |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| FrameTreeNode* child = root->child_at(0); |
| scoped_refptr<SiteInstance> isolated_instance = |
| web_contents()->GetSiteInstance(); |
| |
| // Navigate iframe to the isolated origin's subdomain. |
| GURL isolated_subdomain_url( |
| embedded_test_server()->GetURL("bar.isolated.foo.com", "/title1.html")); |
| NavigateIframeToURL(web_contents(), "test_iframe", isolated_subdomain_url); |
| EXPECT_EQ(child->current_url(), isolated_subdomain_url); |
| |
| EXPECT_EQ(isolated_instance, child->current_frame_host()->GetSiteInstance()); |
| EXPECT_FALSE(child->current_frame_host()->IsCrossProcessSubframe()); |
| EXPECT_EQ(GURL("http://isolated.foo.com/"), |
| child->current_frame_host()->GetSiteInstance()->GetSiteURL()); |
| |
| // Now try navigating the main frame (renderer-initiated) to the isolated |
| // origin's subdomain. This should not swap processes. |
| TestNavigationObserver observer(web_contents()); |
| EXPECT_TRUE(ExecJs(web_contents(), "location.href = '" + |
| isolated_subdomain_url.spec() + "'")); |
| observer.Wait(); |
| if (CanSameSiteMainFrameNavigationsChangeSiteInstances()) { |
| // If same-site ProactivelySwapBrowsingInstance is enabled, they should be |
| // in different site instances but in the same process. |
| EXPECT_NE(isolated_instance, web_contents()->GetSiteInstance()); |
| EXPECT_EQ(isolated_instance->GetProcess(), |
| web_contents()->GetSiteInstance()->GetProcess()); |
| } else { |
| EXPECT_EQ(isolated_instance, web_contents()->GetSiteInstance()); |
| } |
| } |
| |
| // This class allows intercepting the BindStorageArea and OpenLocalStorage |
| // methods in order to test what happens when parameters are changed. |
| class StoragePartitonInterceptor |
| : public blink::mojom::DomStorageInterceptorForTesting, |
| public RenderProcessHostObserver { |
| public: |
| StoragePartitonInterceptor( |
| RenderProcessHostImpl* rph, |
| mojo::PendingReceiver<blink::mojom::DomStorage> receiver, |
| absl::optional<blink::StorageKey> storage_key_to_inject, |
| absl::optional<blink::LocalFrameToken> local_frame_token_to_inject, |
| bool inject_first_local_frame_token) |
| : storage_key_to_inject_(storage_key_to_inject), |
| local_frame_token_to_inject_(local_frame_token_to_inject), |
| save_first_local_frame_token_(inject_first_local_frame_token) { |
| StoragePartitionImpl* storage_partition = |
| static_cast<StoragePartitionImpl*>(rph->GetStoragePartition()); |
| |
| // Bind the real DomStorage implementation. |
| mojo::PendingRemote<blink::mojom::DomStorageClient> unused_client; |
| std::ignore = unused_client.InitWithNewPipeAndPassReceiver(); |
| mojo::ReceiverId receiver_id = storage_partition->BindDomStorage( |
| rph->GetID(), std::move(receiver), std::move(unused_client)); |
| |
| // Now replace it with this object and keep a pointer to the real |
| // implementation. |
| dom_storage_ = storage_partition->dom_storage_receivers_for_testing() |
| .SwapImplForTesting(receiver_id, this); |
| |
| // Register the `this` as a RenderProcessHostObserver, so it can be |
| // correctly cleaned up when the process exits. |
| rph->AddObserver(this); |
| } |
| |
| StoragePartitonInterceptor(const StoragePartitonInterceptor&) = delete; |
| StoragePartitonInterceptor& operator=(const StoragePartitonInterceptor&) = |
| delete; |
| |
| // Ensure this object is cleaned up when the process goes away, since it |
| // is not owned by anyone else. |
| void RenderProcessExited(RenderProcessHost* host, |
| const ChildProcessTerminationInfo& info) override { |
| host->RemoveObserver(this); |
| delete this; |
| } |
| |
| // Allow all methods that aren't explicitly overridden to pass through |
| // unmodified. |
| blink::mojom::DomStorage* GetForwardingInterface() override { |
| return dom_storage_; |
| } |
| |
| // Override this method to allow changing the `storage_key` or |
| // `local_frame_token`. It simulates a renderer process sending incorrect |
| // data to the browser process, so security checks can be tested. |
| void OpenLocalStorage( |
| const blink::StorageKey& storage_key, |
| const blink::LocalFrameToken& local_frame_token, |
| mojo::PendingReceiver<blink::mojom::StorageArea> receiver) override { |
| if (save_first_local_frame_token_ && !saved_first_local_frame_token_) |
| saved_first_local_frame_token_ = local_frame_token; |
| if (saved_first_local_frame_token_ && !local_frame_token_to_inject_) |
| local_frame_token_to_inject_ = saved_first_local_frame_token_; |
| GetForwardingInterface()->OpenLocalStorage( |
| storage_key_to_inject_ ? *storage_key_to_inject_ : storage_key, |
| local_frame_token_to_inject_ ? *local_frame_token_to_inject_ |
| : local_frame_token, |
| std::move(receiver)); |
| } |
| |
| // Override this method to allow changing the `storage_key`. It simulates a |
| // renderer process sending incorrect data to the browser process, so |
| // security checks can be tested. |
| void BindSessionStorageArea( |
| const blink::StorageKey& storage_key, |
| const blink::LocalFrameToken& local_frame_token, |
| const std::string& namespace_id, |
| mojo::PendingReceiver<blink::mojom::StorageArea> receiver) override { |
| if (save_first_local_frame_token_ && !saved_first_local_frame_token_) |
| saved_first_local_frame_token_ = local_frame_token; |
| if (saved_first_local_frame_token_ && !local_frame_token_to_inject_) |
| local_frame_token_to_inject_ = saved_first_local_frame_token_; |
| GetForwardingInterface()->BindSessionStorageArea( |
| storage_key_to_inject_ ? *storage_key_to_inject_ : storage_key, |
| local_frame_token_to_inject_ ? *local_frame_token_to_inject_ |
| : local_frame_token, |
| namespace_id, std::move(receiver)); |
| } |
| |
| private: |
| static absl::optional<blink::LocalFrameToken> saved_first_local_frame_token_; |
| // Keep a pointer to the original implementation of the service, so all |
| // calls can be forwarded to it. |
| raw_ptr<blink::mojom::DomStorage> dom_storage_; |
| absl::optional<blink::StorageKey> storage_key_to_inject_; |
| absl::optional<blink::LocalFrameToken> local_frame_token_to_inject_; |
| bool save_first_local_frame_token_; |
| }; |
| |
| absl::optional<blink::LocalFrameToken> |
| StoragePartitonInterceptor::saved_first_local_frame_token_ = absl::nullopt; |
| |
| // Save the first LocalFrameToken seen and inject it into future calls. |
| void CreateTestDomStorageBackendToSaveFirstFrame( |
| RenderProcessHostImpl* rph, |
| mojo::PendingReceiver<blink::mojom::DomStorage> receiver) { |
| // This object will register as RenderProcessHostObserver, so it will |
| // clean itself automatically on process exit. |
| new StoragePartitonInterceptor(rph, std::move(receiver), absl::nullopt, |
| absl::nullopt, |
| /* save_first_local_frame_token_ */ true); |
| } |
| |
| // Inject (or not if null) a StorageKey and LocalFrameToken. |
| void CreateTestDomStorageBackendToInjectValues( |
| absl::optional<blink::StorageKey> storage_key_to_inject, |
| absl::optional<blink::LocalFrameToken> local_frame_token_to_inject, |
| RenderProcessHostImpl* rph, |
| mojo::PendingReceiver<blink::mojom::DomStorage> receiver) { |
| // This object will register as RenderProcessHostObserver, so it will |
| // clean itself automatically on process exit. |
| new StoragePartitonInterceptor(rph, std::move(receiver), |
| storage_key_to_inject, |
| local_frame_token_to_inject, |
| /* save_first_local_frame_token_ */ false); |
| } |
| |
| // Verify that a renderer process cannot read sessionStorage of another origin. |
| IN_PROC_BROWSER_TEST_F(IsolatedOriginTest, SessionStorage_WrongOrigin) { |
| auto mismatched_storage_key = |
| blink::StorageKey::CreateFromStringForTesting("http://bar.com"); |
| RenderProcessHostImpl::SetDomStorageBinderForTesting( |
| base::BindRepeating(&CreateTestDomStorageBackendToInjectValues, |
| mismatched_storage_key, absl::nullopt)); |
| |
| GURL isolated_url( |
| embedded_test_server()->GetURL("isolated.foo.com", "/title1.html")); |
| EXPECT_TRUE(IsIsolatedOrigin(url::Origin::Create(isolated_url))); |
| EXPECT_TRUE(NavigateToURL(shell(), isolated_url)); |
| |
| content::RenderProcessHostBadIpcMessageWaiter kill_waiter( |
| web_contents()->GetPrimaryMainFrame()->GetProcess()); |
| // Use std::ignore here, since on Android the renderer process is |
| // terminated, but ExecuteScript still returns true. It properly returns |
| // false on all other platforms. |
| std::ignore = |
| ExecJs(web_contents()->GetPrimaryMainFrame(), "sessionStorage.length;"); |
| EXPECT_EQ(bad_message::RPH_MOJO_PROCESS_ERROR, kill_waiter.Wait()); |
| } |
| |
| // Verify not fatal if the renderer reads sessionStorage from an empty |
| // LocalFrameToken. |
| IN_PROC_BROWSER_TEST_F(IsolatedOriginTest, |
| SessionStorage_EmptyLocalFrameToken) { |
| // This sets up some initial sessionStorage state for the subsequent test. |
| GURL page_url(embedded_test_server()->GetURL("foo.com", "/title1.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), page_url)); |
| EXPECT_TRUE(ExecJs(web_contents()->GetPrimaryMainFrame(), |
| "sessionStorage.setItem('key', 'value');")); |
| EXPECT_EQ(1, EvalJs(web_contents()->GetPrimaryMainFrame(), |
| "sessionStorage.length")); |
| |
| // Set up the IPC injection and crash the renderer process so that it's used. |
| // Without crashing the renderer, the default IPC will be used. |
| RenderProcessHostImpl::SetDomStorageBinderForTesting( |
| base::BindRepeating(&CreateTestDomStorageBackendToInjectValues, |
| absl::nullopt, blink::LocalFrameToken())); |
| RenderProcessHost* renderer_process = |
| web_contents()->GetPrimaryMainFrame()->GetProcess(); |
| RenderProcessHostWatcher crash_observer( |
| renderer_process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); |
| renderer_process->Shutdown(0); |
| crash_observer.Wait(); |
| |
| // Re-do tests now that injection is in place |
| EXPECT_TRUE(NavigateToURL(shell(), page_url)); |
| EXPECT_EQ(0, EvalJs(web_contents()->GetPrimaryMainFrame(), |
| "sessionStorage.length")); |
| } |
| |
| // Verify fatal error if the renderer reads sessionStorage from the wrong |
| // LocalFrameToken. |
| IN_PROC_BROWSER_TEST_F(IsolatedOriginTest, |
| SessionStorage_WrongLocalFrameToken) { |
| // This sets up some initial sessionStorage state for the subsequent test. |
| GURL isolated_url(embedded_test_server()->GetURL( |
| "isolated.foo.com", |
| "/cross_site_iframe_factory.html?isolated.foo.com(bar.com)")); |
| EXPECT_TRUE(NavigateToURL(shell(), isolated_url)); |
| EXPECT_TRUE(ExecJs(web_contents()->GetPrimaryMainFrame(), |
| "sessionStorage.setItem('key', 'value');")); |
| EXPECT_EQ(1, EvalJs(web_contents()->GetPrimaryMainFrame(), |
| "sessionStorage.length")); |
| EXPECT_TRUE(ExecJs(ChildFrameAt(shell(), 0), |
| "sessionStorage.setItem('key', 'value');")); |
| EXPECT_EQ(1, EvalJs(ChildFrameAt(shell(), 0), "sessionStorage.length")); |
| |
| // Set up the IPC injection and crash the renderer process so that it's used. |
| // Without crashing the renderer, the default IPC will be used. |
| RenderProcessHostImpl::SetDomStorageBinderForTesting( |
| base::BindRepeating(&CreateTestDomStorageBackendToSaveFirstFrame)); |
| RenderProcessHost* renderer_process_iframe = |
| ChildFrameAt(shell(), 0)->GetProcess(); |
| RenderProcessHostWatcher crash_observer_iframe( |
| renderer_process_iframe, |
| RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); |
| renderer_process_iframe->Shutdown(0); |
| crash_observer_iframe.Wait(); |
| RenderProcessHost* renderer_process_root = |
| web_contents()->GetPrimaryMainFrame()->GetProcess(); |
| RenderProcessHostWatcher crash_observer_root( |
| renderer_process_root, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); |
| renderer_process_root->Shutdown(0); |
| crash_observer_root.Wait(); |
| |
| // Re-do tests now that injection is in place |
| EXPECT_TRUE(NavigateToURL(shell(), isolated_url)); |
| EXPECT_EQ(1, EvalJs(web_contents()->GetPrimaryMainFrame(), |
| "sessionStorage.length")); |
| content::RenderProcessHostBadIpcMessageWaiter kill_waiter( |
| ChildFrameAt(shell(), 0)->GetProcess()); |
| std::ignore = ExecJs(ChildFrameAt(shell(), 0), "sessionStorage.length"); |
| EXPECT_EQ(bad_message::RPH_MOJO_PROCESS_ERROR, kill_waiter.Wait()); |
| // The subframe has crashed, but the main frame should still be alive and |
| // working. |
| EXPECT_EQ(1, EvalJs(web_contents()->GetPrimaryMainFrame(), |
| "sessionStorage.length")); |
| } |
| |
| // Verify not fatal if the renderer reads localStorage from an empty |
| // LocalFrameToken. |
| IN_PROC_BROWSER_TEST_F(IsolatedOriginTest, LocalStorage_EmptyLocalFrameToken) { |
| // This sets up some initial localStorage state for the subsequent test. |
| GURL page_url(embedded_test_server()->GetURL("foo.com", "/title1.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), page_url)); |
| EXPECT_TRUE(ExecJs(web_contents()->GetPrimaryMainFrame(), |
| "localStorage.setItem('key', 'value');")); |
| EXPECT_EQ( |
| 1, EvalJs(web_contents()->GetPrimaryMainFrame(), "localStorage.length")); |
| |
| // Set up the IPC injection and crash the renderer process so that it's used. |
| // Without crashing the renderer, the default IPC will be used. |
| RenderProcessHostImpl::SetDomStorageBinderForTesting( |
| base::BindRepeating(&CreateTestDomStorageBackendToInjectValues, |
| absl::nullopt, blink::LocalFrameToken())); |
| RenderProcessHost* renderer_process = |
| web_contents()->GetPrimaryMainFrame()->GetProcess(); |
| RenderProcessHostWatcher crash_observer( |
| renderer_process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); |
| renderer_process->Shutdown(0); |
| crash_observer.Wait(); |
| |
| // Re-do tests now that injection is in place |
| EXPECT_TRUE(NavigateToURL(shell(), page_url)); |
| EXPECT_EQ( |
| 0, EvalJs(web_contents()->GetPrimaryMainFrame(), "localStorage.length")); |
| } |
| |
| // Verify fatal error if the renderer reads localStorage from the wrong |
| // LocalFrameToken. |
| IN_PROC_BROWSER_TEST_F(IsolatedOriginTest, LocalStorage_WrongLocalFrameToken) { |
| // This sets up some initial localStorage state for the subsequent test. |
| GURL isolated_url(embedded_test_server()->GetURL( |
| "isolated.foo.com", |
| "/cross_site_iframe_factory.html?isolated.foo.com(bar.com)")); |
| EXPECT_TRUE(NavigateToURL(shell(), isolated_url)); |
| EXPECT_TRUE(ExecJs(web_contents()->GetPrimaryMainFrame(), |
| "localStorage.setItem('key', 'value');")); |
| EXPECT_EQ( |
| 1, EvalJs(web_contents()->GetPrimaryMainFrame(), "localStorage.length")); |
| EXPECT_TRUE(ExecJs(ChildFrameAt(shell(), 0), |
| "localStorage.setItem('key', 'value');")); |
| EXPECT_EQ(1, EvalJs(ChildFrameAt(shell(), 0), "localStorage.length")); |
| |
| // Set up the IPC injection and crash the renderer process so that it's used. |
| // Without crashing the renderer, the default IPC will be used. |
| RenderProcessHostImpl::SetDomStorageBinderForTesting( |
| base::BindRepeating(&CreateTestDomStorageBackendToSaveFirstFrame)); |
| RenderProcessHost* renderer_process_iframe = |
| ChildFrameAt(shell(), 0)->GetProcess(); |
| RenderProcessHostWatcher crash_observer_iframe( |
| renderer_process_iframe, |
| RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); |
| renderer_process_iframe->Shutdown(0); |
| crash_observer_iframe.Wait(); |
| RenderProcessHost* renderer_process_root = |
| web_contents()->GetPrimaryMainFrame()->GetProcess(); |
| RenderProcessHostWatcher crash_observer_root( |
| renderer_process_root, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); |
| renderer_process_root->Shutdown(0); |
| crash_observer_root.Wait(); |
| |
| // Re-do tests now that injection is in place |
| EXPECT_TRUE(NavigateToURL(shell(), isolated_url)); |
| EXPECT_EQ( |
| 1, EvalJs(web_contents()->GetPrimaryMainFrame(), "localStorage.length")); |
| content::RenderProcessHostBadIpcMessageWaiter kill_waiter( |
| ChildFrameAt(shell(), 0)->GetProcess()); |
| std::ignore = ExecJs(ChildFrameAt(shell(), 0), "localStorage.length"); |
| EXPECT_EQ(bad_message::RPH_MOJO_PROCESS_ERROR, kill_waiter.Wait()); |
| // The subframe has crashed, but the main frame should still be alive and |
| // working. |
| EXPECT_EQ( |
| 1, EvalJs(web_contents()->GetPrimaryMainFrame(), "localStorage.length")); |
| } |
| |
| // Verify that an isolated renderer process cannot read localStorage of an |
| // origin outside of its isolated site. |
| IN_PROC_BROWSER_TEST_F( |
| IsolatedOriginTest, |
| LocalStorageOriginEnforcement_IsolatedAccessingNonIsolated) { |
| auto mismatched_storage_key = |
| blink::StorageKey::CreateFromStringForTesting("http://abc.foo.com"); |
| EXPECT_FALSE(IsIsolatedOrigin(mismatched_storage_key.origin())); |
| RenderProcessHostImpl::SetDomStorageBinderForTesting( |
| base::BindRepeating(&CreateTestDomStorageBackendToInjectValues, |
| mismatched_storage_key, absl::nullopt)); |
| |
| GURL isolated_url( |
| embedded_test_server()->GetURL("isolated.foo.com", "/title1.html")); |
| EXPECT_TRUE(IsIsolatedOrigin(url::Origin::Create(isolated_url))); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), isolated_url)); |
| |
| content::RenderProcessHostBadIpcMessageWaiter kill_waiter( |
| shell()->web_contents()->GetPrimaryMainFrame()->GetProcess()); |
| // Use std::ignore here, since on Android the renderer process is |
| // terminated, but ExecuteScript still returns true. It properly returns |
| // false on all other platforms. |
| std::ignore = ExecJs(shell()->web_contents()->GetPrimaryMainFrame(), |
| "localStorage.length;"); |
| EXPECT_EQ(bad_message::RPH_MOJO_PROCESS_ERROR, kill_waiter.Wait()); |
| } |
| |
| #if BUILDFLAG(IS_ANDROID) |
| #define MAYBE_LocalStorageOriginEnforcement_NonIsolatedAccessingIsolated \ |
| LocalStorageOriginEnforcement_NonIsolatedAccessingIsolated |
| #else |
| // TODO(lukasza): https://crbug.com/566091: Once remote NTP is capable of |
| // embedding OOPIFs, start enforcing citadel-style checks on desktop |
| // platforms. |
| #define MAYBE_LocalStorageOriginEnforcement_NonIsolatedAccessingIsolated \ |
| DISABLED_LocalStorageOriginEnforcement_NonIsolatedAccessingIsolated |
| #endif |
| // Verify that a non-isolated renderer process cannot read localStorage of an |
| // isolated origin. |
| // |
| // TODO(alexmos, lukasza): https://crbug.com/764958: Replicate this test for |
| // the IO-thread case. |
| IN_PROC_BROWSER_TEST_F( |
| IsolatedOriginTest, |
| MAYBE_LocalStorageOriginEnforcement_NonIsolatedAccessingIsolated) { |
| auto isolated_storage_key = |
| blink::StorageKey::CreateFromStringForTesting("http://isolated.foo.com"); |
| EXPECT_TRUE(IsIsolatedOrigin(isolated_storage_key.origin())); |
| |
| GURL nonisolated_url( |
| embedded_test_server()->GetURL("non-isolated.com", "/title1.html")); |
| EXPECT_FALSE(IsIsolatedOrigin(url::Origin::Create(nonisolated_url))); |
| |
| RenderProcessHostImpl::SetDomStorageBinderForTesting( |
| base::BindRepeating(&CreateTestDomStorageBackendToInjectValues, |
| isolated_storage_key, absl::nullopt)); |
| EXPECT_TRUE(NavigateToURL(shell(), nonisolated_url)); |
| |
| content::RenderProcessHostBadIpcMessageWaiter kill_waiter( |
| shell()->web_contents()->GetPrimaryMainFrame()->GetProcess()); |
| // Use std::ignore here, since on Android the renderer process is |
| // terminated, but ExecuteScript still returns true. It properly returns |
| // false on all other platforms. |
| std::ignore = ExecJs(shell()->web_contents()->GetPrimaryMainFrame(), |
| "localStorage.length;"); |
| EXPECT_EQ(bad_message::RPH_MOJO_PROCESS_ERROR, kill_waiter.Wait()); |
| } |
| |
| // Verify that an IPC request for reading localStorage of an *opaque* origin |
| // will be rejected. |
| IN_PROC_BROWSER_TEST_F(IsolatedOriginTest, |
| LocalStorageOriginEnforcement_OpaqueOrigin) { |
| url::Origin precursor_origin = |
| url::Origin::Create(GURL("https://non-isolated.com")); |
| const blink::StorageKey opaque_storage_key = |
| blink::StorageKey::CreateFirstParty( |
| precursor_origin.DeriveNewOpaqueOrigin()); |
| RenderProcessHostImpl::SetDomStorageBinderForTesting( |
| base::BindRepeating(&CreateTestDomStorageBackendToInjectValues, |
| opaque_storage_key, absl::nullopt)); |
| |
| GURL isolated_url( |
| embedded_test_server()->GetURL("isolated.foo.com", "/title1.html")); |
| EXPECT_TRUE(IsIsolatedOrigin(url::Origin::Create(isolated_url))); |
| EXPECT_TRUE(NavigateToURL(shell(), isolated_url)); |
| |
| content::RenderProcessHostBadIpcMessageWaiter kill_waiter( |
| shell()->web_contents()->GetPrimaryMainFrame()->GetProcess()); |
| // Use std::ignore here, since on Android the renderer process is |
| // terminated, but ExecuteScript still returns true. It properly returns |
| // false on all other platforms. |
| std::ignore = ExecJs(shell()->web_contents()->GetPrimaryMainFrame(), |
| "localStorage.length;"); |
| EXPECT_EQ(bad_message::RPH_MOJO_PROCESS_ERROR, kill_waiter.Wait()); |
| } |
| |
| class IsolatedOriginFieldTrialTest : public IsolatedOriginTestBase { |
| public: |
| IsolatedOriginFieldTrialTest() { |
| scoped_feature_list_.InitAndEnableFeatureWithParameters( |
| features::kIsolateOrigins, |
| {{features::kIsolateOriginsFieldTrialParamName, |
| "https://field.trial.com/,https://bar.com/"}}); |
| } |
| ~IsolatedOriginFieldTrialTest() override = default; |
| |
| IsolatedOriginFieldTrialTest(const IsolatedOriginFieldTrialTest&) = delete; |
| IsolatedOriginFieldTrialTest& operator=(const IsolatedOriginFieldTrialTest&) = |
| delete; |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(IsolatedOriginFieldTrialTest, Test) { |
| bool expected_to_isolate = !base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kDisableSiteIsolation); |
| |
| EXPECT_EQ(expected_to_isolate, |
| IsIsolatedOrigin(GURL("https://field.trial.com/"))); |
| EXPECT_EQ(expected_to_isolate, IsIsolatedOrigin(GURL("https://bar.com/"))); |
| } |
| |
| class IsolatedOriginCommandLineAndFieldTrialTest |
| : public IsolatedOriginFieldTrialTest { |
| public: |
| IsolatedOriginCommandLineAndFieldTrialTest() = default; |
| IsolatedOriginCommandLineAndFieldTrialTest( |
| const IsolatedOriginCommandLineAndFieldTrialTest&) = delete; |
| IsolatedOriginCommandLineAndFieldTrialTest& operator=( |
| const IsolatedOriginCommandLineAndFieldTrialTest&) = delete; |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| IsolatedOriginFieldTrialTest::SetUpCommandLine(command_line); |
| command_line->AppendSwitchASCII( |
| switches::kIsolateOrigins, |
| "https://cmd.line.com/,https://cmdline.com/"); |
| } |
| }; |
| |
| // Verify that the lists of isolated origins specified via --isolate-origins |
| // and via field trials are merged. See https://crbug.com/894535. |
| IN_PROC_BROWSER_TEST_F(IsolatedOriginCommandLineAndFieldTrialTest, Test) { |
| // --isolate-origins should take effect regardless of the |
| // kDisableSiteIsolation opt-out flag. |
| EXPECT_TRUE(IsIsolatedOrigin(GURL("https://cmd.line.com/"))); |
| EXPECT_TRUE(IsIsolatedOrigin(GURL("https://cmdline.com/"))); |
| |
| // Field trial origins should also take effect, but only if the opt-out flag |
| // is not present. |
| bool expected_to_isolate = !base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kDisableSiteIsolation); |
| EXPECT_EQ(expected_to_isolate, |
| IsIsolatedOrigin(GURL("https://field.trial.com/"))); |
| EXPECT_EQ(expected_to_isolate, IsIsolatedOrigin(GURL("https://bar.com/"))); |
| } |
| |
| // This is a regression test for https://crbug.com/793350 - the long list of |
| // origins to isolate used to be unnecessarily propagated to the renderer |
| // process, trigerring a crash due to exceeding kZygoteMaxMessageLength. |
| class IsolatedOriginLongListTest : public IsolatedOriginTestBase { |
| public: |
| IsolatedOriginLongListTest() = default; |
| ~IsolatedOriginLongListTest() override = default; |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| ASSERT_TRUE(embedded_test_server()->InitializeAndListen()); |
| |
| std::ostringstream origin_list; |
| origin_list |
| << embedded_test_server()->GetURL("isolated.foo.com", "/").spec(); |
| for (int i = 0; i < 1000; i++) { |
| std::ostringstream hostname; |
| hostname << "foo" << i << ".com"; |
| |
| origin_list << "," |
| << embedded_test_server()->GetURL(hostname.str(), "/").spec(); |
| } |
| command_line->AppendSwitchASCII(switches::kIsolateOrigins, |
| origin_list.str()); |
| } |
| |
| void SetUpOnMainThread() override { |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| embedded_test_server()->StartAcceptingConnections(); |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(IsolatedOriginLongListTest, Test) { |
| GURL test_url(embedded_test_server()->GetURL( |
| "bar1.com", |
| "/cross_site_iframe_factory.html?" |
| "bar1.com(isolated.foo.com,foo999.com,bar2.com)")); |
| EXPECT_TRUE(NavigateToURL(shell(), test_url)); |
| |
| EXPECT_EQ(4u, CollectAllRenderFrameHosts(shell()->web_contents()).size()); |
| RenderFrameHost* main_frame = shell()->web_contents()->GetPrimaryMainFrame(); |
| RenderFrameHost* subframe1 = ChildFrameAt(main_frame, 0); |
| RenderFrameHost* subframe2 = ChildFrameAt(main_frame, 1); |
| RenderFrameHost* subframe3 = ChildFrameAt(main_frame, 2); |
| EXPECT_EQ("bar1.com", main_frame->GetLastCommittedOrigin().GetURL().host()); |
| EXPECT_EQ("isolated.foo.com", |
| subframe1->GetLastCommittedOrigin().GetURL().host()); |
| EXPECT_EQ("foo999.com", subframe2->GetLastCommittedOrigin().GetURL().host()); |
| EXPECT_EQ("bar2.com", subframe3->GetLastCommittedOrigin().GetURL().host()); |
| |
| // bar1.com and bar2.com are not on the list of origins to isolate - they |
| // should stay in the same process, unless --site-per-process has also been |
| // specified. |
| if (!AreAllSitesIsolatedForTesting()) { |
| EXPECT_EQ(main_frame->GetProcess()->GetID(), |
| subframe3->GetProcess()->GetID()); |
| if (AreStrictSiteInstancesEnabled()) { |
| EXPECT_NE(main_frame->GetSiteInstance(), subframe3->GetSiteInstance()); |
| } else { |
| EXPECT_EQ(main_frame->GetSiteInstance(), subframe3->GetSiteInstance()); |
| } |
| } |
| |
| // isolated.foo.com and foo999.com are on the list of origins to isolate - |
| // they should be isolated from everything else. |
| EXPECT_NE(main_frame->GetProcess()->GetID(), |
| subframe1->GetProcess()->GetID()); |
| EXPECT_NE(main_frame->GetSiteInstance(), subframe1->GetSiteInstance()); |
| EXPECT_NE(main_frame->GetProcess()->GetID(), |
| subframe2->GetProcess()->GetID()); |
| EXPECT_NE(main_frame->GetSiteInstance(), subframe2->GetSiteInstance()); |
| EXPECT_NE(subframe1->GetProcess()->GetID(), subframe2->GetProcess()->GetID()); |
| EXPECT_NE(subframe1->GetSiteInstance(), subframe2->GetSiteInstance()); |
| } |
| |
| // Check that navigating a subframe to an isolated origin error page puts the |
| // subframe into an OOPIF and its own SiteInstance. Also check that the error |
| // page in a subframe ends up in the correct SiteInstance. |
| IN_PROC_BROWSER_TEST_F(IsolatedOriginTest, SubframeErrorPages) { |
| GURL top_url( |
| embedded_test_server()->GetURL("/frame_tree/page_with_two_frames.html")); |
| GURL isolated_url( |
| embedded_test_server()->GetURL("isolated.foo.com", "/close-socket")); |
| GURL regular_url(embedded_test_server()->GetURL("a.com", "/close-socket")); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), top_url)); |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| EXPECT_EQ(2u, root->child_count()); |
| |
| FrameTreeNode* child1 = root->child_at(0); |
| FrameTreeNode* child2 = root->child_at(1); |
| |
| { |
| TestFrameNavigationObserver observer(child1); |
| NavigationHandleObserver handle_observer(web_contents(), isolated_url); |
| EXPECT_TRUE( |
| ExecJs(child1, "location.href = '" + isolated_url.spec() + "';")); |
| observer.Wait(); |
| EXPECT_EQ(child1->current_url(), isolated_url); |
| EXPECT_TRUE(handle_observer.is_error()); |
| |
| EXPECT_NE(root->current_frame_host()->GetSiteInstance(), |
| child1->current_frame_host()->GetSiteInstance()); |
| if (!SiteIsolationPolicy::IsErrorPageIsolationEnabled( |
| /*in_main_frame=*/false)) { |
| EXPECT_EQ(GURL("http://isolated.foo.com/"), |
| child1->current_frame_host()->GetSiteInstance()->GetSiteURL()); |
| } else { |
| EXPECT_TRUE(child1->current_frame_host() |
| ->GetSiteInstance() |
| ->GetSiteInfo() |
| .is_error_page()); |
| } |
| } |
| |
| { |
| TestFrameNavigationObserver observer(child2); |
| NavigationHandleObserver handle_observer(web_contents(), regular_url); |
| EXPECT_TRUE( |
| ExecJs(child2, "location.href = '" + regular_url.spec() + "';")); |
| observer.Wait(); |
| EXPECT_EQ(child2->current_url(), regular_url); |
| EXPECT_TRUE(handle_observer.is_error()); |
| if (AreStrictSiteInstancesEnabled()) { |
| EXPECT_NE(root->current_frame_host()->GetSiteInstance(), |
| child2->current_frame_host()->GetSiteInstance()); |
| if (!SiteIsolationPolicy::IsErrorPageIsolationEnabled( |
| /*in_main_frame=*/false)) { |
| EXPECT_EQ( |
| SiteInfo::CreateForTesting( |
| IsolationContext(web_contents()->GetBrowserContext()), |
| regular_url), |
| child2->current_frame_host()->GetSiteInstance()->GetSiteInfo()); |
| } |
| } else { |
| EXPECT_EQ(root->current_frame_host()->GetSiteInstance(), |
| child2->current_frame_host()->GetSiteInstance()); |
| } |
| EXPECT_EQ(SiteIsolationPolicy::IsErrorPageIsolationEnabled( |
| /*in_main_frame=*/false), |
| child2->current_frame_host() |
| ->GetSiteInstance() |
| ->GetSiteInfo() |
| .is_error_page()); |
| } |
| } |
| |
| namespace { |
| bool HasDefaultSiteInstance(RenderFrameHost* rfh) { |
| return static_cast<SiteInstanceImpl*>(rfh->GetSiteInstance()) |
| ->IsDefaultSiteInstance(); |
| } |
| } // namespace |
| |
| // Verify process assignment behavior for the case where a site that does not |
| // require isolation embeds a frame that does require isolation, which in turn |
| // embeds another site that does not require isolation. |
| // A (Does not require isolation) |
| // +-> B (requires isolation) |
| // +-> C (different site from A that does not require isolation.) |
| // +-> A (same site as top-level which also does not require isolation.) |
| IN_PROC_BROWSER_TEST_F(IsolatedOriginTest, AIsolatedCA) { |
| GURL main_url( |
| embedded_test_server()->GetURL("www.foo.com", |
| "/cross_site_iframe_factory.html?www.foo." |
| "com(isolated.foo.com(c(www.foo.com)))")); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| RenderFrameHost* a = root->current_frame_host(); |
| RenderFrameHost* b = root->child_at(0)->current_frame_host(); |
| RenderFrameHost* c = root->child_at(0)->child_at(0)->current_frame_host(); |
| RenderFrameHost* d = |
| root->child_at(0)->child_at(0)->child_at(0)->current_frame_host(); |
| |
| // Sanity check that the test works with the right frame tree. |
| EXPECT_FALSE(IsIsolatedOrigin(a->GetLastCommittedOrigin())); |
| EXPECT_TRUE(IsIsolatedOrigin(b->GetLastCommittedOrigin())); |
| EXPECT_FALSE(IsIsolatedOrigin(c->GetLastCommittedOrigin())); |
| EXPECT_FALSE(IsIsolatedOrigin(d->GetLastCommittedOrigin())); |
| EXPECT_EQ("www.foo.com", a->GetLastCommittedURL().host()); |
| EXPECT_EQ("isolated.foo.com", b->GetLastCommittedURL().host()); |
| EXPECT_EQ("c.com", c->GetLastCommittedURL().host()); |
| EXPECT_EQ("www.foo.com", d->GetLastCommittedURL().host()); |
| |
| // Verify that the isolated site is indeed isolated. |
| EXPECT_NE(b->GetProcess()->GetID(), a->GetProcess()->GetID()); |
| EXPECT_NE(b->GetProcess()->GetID(), c->GetProcess()->GetID()); |
| EXPECT_NE(b->GetProcess()->GetID(), d->GetProcess()->GetID()); |
| |
| // Verify that same-origin a and d frames share a process. This is |
| // necessary for correctness - otherwise a and d wouldn't be able to |
| // synchronously script each other. |
| EXPECT_EQ(a->GetProcess()->GetID(), d->GetProcess()->GetID()); |
| |
| // Verify that same-origin a and d frames can script each other. |
| EXPECT_TRUE(ExecJs(a, "window.name = 'a';")); |
| EXPECT_TRUE(ExecJs(d, R"( |
| a = window.open('', 'a'); |
| a.cross_frame_property_test = 'hello from d'; )")); |
| EXPECT_EQ("hello from d", |
| EvalJs(a, "window.cross_frame_property_test").ExtractString()); |
| |
| // The test assertions below are not strictly necessary - they just document |
| // the current behavior. In particular, consolidating www.foo.com and c.com |
| // sites into the same process is not necessary for correctness. |
| if (AreAllSitesIsolatedForTesting()) { |
| // All sites are isolated so we expect foo.com, isolated.foo.com and c.com |
| // to all be in their own processes. |
| EXPECT_NE(a->GetProcess()->GetID(), b->GetProcess()->GetID()); |
| EXPECT_NE(a->GetProcess()->GetID(), c->GetProcess()->GetID()); |
| EXPECT_NE(b->GetProcess()->GetID(), c->GetProcess()->GetID()); |
| |
| EXPECT_NE(a->GetSiteInstance(), b->GetSiteInstance()); |
| EXPECT_NE(a->GetSiteInstance(), c->GetSiteInstance()); |
| EXPECT_EQ(a->GetSiteInstance(), d->GetSiteInstance()); |
| EXPECT_NE(b->GetSiteInstance(), c->GetSiteInstance()); |
| |
| EXPECT_FALSE(HasDefaultSiteInstance(a)); |
| EXPECT_FALSE(HasDefaultSiteInstance(b)); |
| EXPECT_FALSE(HasDefaultSiteInstance(c)); |
| } else if (AreDefaultSiteInstancesEnabled()) { |
| // All sites that are not isolated should be in the same default |
| // SiteInstance process. |
| EXPECT_NE(a->GetProcess()->GetID(), b->GetProcess()->GetID()); |
| EXPECT_EQ(a->GetProcess()->GetID(), c->GetProcess()->GetID()); |
| |
| EXPECT_NE(a->GetSiteInstance(), b->GetSiteInstance()); |
| EXPECT_EQ(a->GetSiteInstance(), c->GetSiteInstance()); |
| EXPECT_EQ(a->GetSiteInstance(), d->GetSiteInstance()); |
| EXPECT_NE(b->GetSiteInstance(), c->GetSiteInstance()); |
| |
| EXPECT_TRUE(HasDefaultSiteInstance(a)); |
| EXPECT_FALSE(HasDefaultSiteInstance(b)); |
| } else if (AreStrictSiteInstancesEnabled()) { |
| // All sites have their own SiteInstance and sites that are not isolated |
| // are all placed in the same process. |
| EXPECT_NE(a->GetProcess()->GetID(), b->GetProcess()->GetID()); |
| EXPECT_EQ(a->GetProcess()->GetID(), c->GetProcess()->GetID()); |
| |
| EXPECT_NE(a->GetSiteInstance(), b->GetSiteInstance()); |
| EXPECT_NE(a->GetSiteInstance(), c->GetSiteInstance()); |
| EXPECT_EQ(a->GetSiteInstance(), d->GetSiteInstance()); |
| EXPECT_NE(b->GetSiteInstance(), c->GetSiteInstance()); |
| |
| EXPECT_FALSE(HasDefaultSiteInstance(a)); |
| EXPECT_FALSE(HasDefaultSiteInstance(b)); |
| EXPECT_FALSE(HasDefaultSiteInstance(c)); |
| } else { |
| FAIL() << "Unexpected process model configuration."; |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_F(IsolatedOriginTest, NavigateToBlobURL) { |
| GURL top_url( |
| embedded_test_server()->GetURL("www.foo.com", "/page_with_iframe.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), top_url)); |
| |
| GURL isolated_url(embedded_test_server()->GetURL("isolated.foo.com", |
| "/page_with_iframe.html")); |
| |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| FrameTreeNode* child = root->child_at(0); |
| |
| NavigateIframeToURL(web_contents(), "test_iframe", isolated_url); |
| EXPECT_EQ(child->current_url(), isolated_url); |
| EXPECT_TRUE(child->current_frame_host()->IsCrossProcessSubframe()); |
| |
| // Now navigate the child frame to a Blob URL. |
| TestNavigationObserver load_observer(shell()->web_contents()); |
| EXPECT_TRUE(ExecJs(shell()->web_contents()->GetPrimaryMainFrame(), |
| "const b = new Blob(['foo']);\n" |
| "const u = URL.createObjectURL(b);\n" |
| "frames[0].location = u;\n" |
| "URL.revokeObjectURL(u);")); |
| load_observer.Wait(); |
| EXPECT_TRUE(base::StartsWith(child->current_url().spec(), |
| "blob:http://www.foo.com", |
| base::CompareCase::SENSITIVE)); |
| EXPECT_TRUE(load_observer.last_navigation_succeeded()); |
| } |
| |
| // Ensure that --disable-site-isolation-trials disables origin isolation. |
| class IsolatedOriginTrialOverrideTest : public IsolatedOriginFieldTrialTest { |
| public: |
| IsolatedOriginTrialOverrideTest() = default; |
| ~IsolatedOriginTrialOverrideTest() override = default; |
| |
| IsolatedOriginTrialOverrideTest(const IsolatedOriginTrialOverrideTest&) = |
| delete; |
| IsolatedOriginTrialOverrideTest& operator=( |
| const IsolatedOriginTrialOverrideTest&) = delete; |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| IsolatedOriginFieldTrialTest::SetUpCommandLine(command_line); |
| command_line->AppendSwitch(switches::kDisableSiteIsolation); |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(IsolatedOriginTrialOverrideTest, Test) { |
| if (AreAllSitesIsolatedForTesting()) |
| return; |
| EXPECT_FALSE(IsIsolatedOrigin(GURL("https://field.trial.com/"))); |
| EXPECT_FALSE(IsIsolatedOrigin(GURL("https://bar.com/"))); |
| } |
| |
| // Ensure that --disable-site-isolation-trials and/or |
| // --disable-site-isolation-for-policy do not override the flag. |
| class IsolatedOriginPolicyOverrideTest : public IsolatedOriginFieldTrialTest { |
| public: |
| IsolatedOriginPolicyOverrideTest() = default; |
| ~IsolatedOriginPolicyOverrideTest() override = default; |
| |
| IsolatedOriginPolicyOverrideTest(const IsolatedOriginPolicyOverrideTest&) = |
| delete; |
| IsolatedOriginPolicyOverrideTest& operator=( |
| const IsolatedOriginPolicyOverrideTest&) = delete; |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| IsolatedOriginFieldTrialTest::SetUpCommandLine(command_line); |
| command_line->AppendSwitch(switches::kDisableSiteIsolation); |
| #if BUILDFLAG(IS_ANDROID) |
| command_line->AppendSwitch(switches::kDisableSiteIsolationForPolicy); |
| #endif |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(IsolatedOriginPolicyOverrideTest, Test) { |
| if (AreAllSitesIsolatedForTesting()) |
| return; |
| EXPECT_FALSE(IsIsolatedOrigin(GURL("https://field.trial.com/"))); |
| EXPECT_FALSE(IsIsolatedOrigin(GURL("https://bar.com/"))); |
| } |
| |
| // Ensure that --disable-site-isolation-trials and/or |
| // --disable-site-isolation-for-policy do not override the flag. |
| class IsolatedOriginNoFlagOverrideTest : public IsolatedOriginTest { |
| public: |
| IsolatedOriginNoFlagOverrideTest() = default; |
| ~IsolatedOriginNoFlagOverrideTest() override = default; |
| |
| IsolatedOriginNoFlagOverrideTest(const IsolatedOriginNoFlagOverrideTest&) = |
| delete; |
| IsolatedOriginNoFlagOverrideTest& operator=( |
| const IsolatedOriginNoFlagOverrideTest&) = delete; |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| IsolatedOriginTest::SetUpCommandLine(command_line); |
| command_line->AppendSwitch(switches::kDisableSiteIsolation); |
| #if BUILDFLAG(IS_ANDROID) |
| command_line->AppendSwitch(switches::kDisableSiteIsolationForPolicy); |
| #endif |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(IsolatedOriginNoFlagOverrideTest, Test) { |
| GURL isolated_url( |
| embedded_test_server()->GetURL("isolated.foo.com", "/title2.html")); |
| EXPECT_TRUE(IsIsolatedOrigin(isolated_url)); |
| } |
| |
| // Verify that main frame's origin isolation still keeps all same-origin frames |
| // in the same process. When allocating processes for a(b(c),d(c)), we should |
| // ensure that "c" frames are in the same process. |
| // |
| // This is a regression test for https://crbug.com/787576. |
| IN_PROC_BROWSER_TEST_F(IsolatedOriginNoFlagOverrideTest, |
| SameOriginSubframesProcessSharing) { |
| GURL main_url(embedded_test_server()->GetURL( |
| "isolated.foo.com", |
| "/cross_site_iframe_factory.html?isolated.foo.com(b(c),d(c))")); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| RenderFrameHost* a = root->current_frame_host(); |
| RenderFrameHost* b = root->child_at(0)->current_frame_host(); |
| RenderFrameHost* c1 = root->child_at(0)->child_at(0)->current_frame_host(); |
| RenderFrameHost* d = root->child_at(1)->current_frame_host(); |
| RenderFrameHost* c2 = root->child_at(1)->child_at(0)->current_frame_host(); |
| |
| // Sanity check that the test works with the right frame tree. |
| EXPECT_TRUE(IsIsolatedOrigin(a->GetLastCommittedOrigin())); |
| EXPECT_FALSE(IsIsolatedOrigin(b->GetLastCommittedOrigin())); |
| EXPECT_FALSE(IsIsolatedOrigin(d->GetLastCommittedOrigin())); |
| EXPECT_FALSE(IsIsolatedOrigin(c1->GetLastCommittedOrigin())); |
| EXPECT_FALSE(IsIsolatedOrigin(c2->GetLastCommittedOrigin())); |
| EXPECT_EQ("b.com", b->GetLastCommittedURL().host()); |
| EXPECT_EQ("d.com", d->GetLastCommittedURL().host()); |
| EXPECT_EQ("c.com", c1->GetLastCommittedURL().host()); |
| EXPECT_EQ("c.com", c2->GetLastCommittedURL().host()); |
| |
| // Verify that the isolated site is indeed isolated. |
| EXPECT_NE(a->GetProcess()->GetID(), c1->GetProcess()->GetID()); |
| EXPECT_NE(a->GetProcess()->GetID(), c2->GetProcess()->GetID()); |
| EXPECT_NE(a->GetProcess()->GetID(), b->GetProcess()->GetID()); |
| EXPECT_NE(a->GetProcess()->GetID(), d->GetProcess()->GetID()); |
| |
| // Verify that same-origin c1 and c2 frames share a process. This is |
| // necessary for correctness - otherwise c1 and c2 wouldn't be able to |
| // synchronously script each other. |
| EXPECT_EQ(c1->GetProcess()->GetID(), c2->GetProcess()->GetID()); |
| |
| // Verify that same-origin c1 and c2 frames can script each other. |
| EXPECT_TRUE(ExecJs(c1, "window.name = 'c1';")); |
| EXPECT_TRUE(ExecJs(c2, R"( |
| c1 = window.open('', 'c1'); |
| c1.cross_frame_property_test = 'hello from c2'; )")); |
| EXPECT_EQ("hello from c2", EvalJs(c1, "window.cross_frame_property_test;")); |
| |
| // The test assertions below are not strictly necessary - they just document |
| // the current behavior and might be tweaked if needed. In particular, |
| // consolidating b,c,d sites into the same process is not necessary for |
| // correctness. Consolidation might be desirable if we want to limit the |
| // number of renderer processes. OTOH, consolidation might be undesirable |
| // if we desire smaller renderer processes (even if it means more processes). |
| if (!AreAllSitesIsolatedForTesting()) { |
| EXPECT_EQ(b->GetProcess()->GetID(), c1->GetProcess()->GetID()); |
| EXPECT_EQ(b->GetProcess()->GetID(), c2->GetProcess()->GetID()); |
| EXPECT_EQ(b->GetProcess()->GetID(), d->GetProcess()->GetID()); |
| } else { |
| EXPECT_NE(b->GetProcess()->GetID(), c1->GetProcess()->GetID()); |
| EXPECT_NE(b->GetProcess()->GetID(), c2->GetProcess()->GetID()); |
| EXPECT_NE(b->GetProcess()->GetID(), d->GetProcess()->GetID()); |
| EXPECT_EQ(c1->GetProcess()->GetID(), c2->GetProcess()->GetID()); |
| } |
| } |
| |
| // Helper class for testing dynamically-added isolated origins. Tests that use |
| // this run without full --site-per-process, but with two isolated origins that |
| // are configured at startup (isolated.foo.com and isolated.bar.com). |
| class DynamicIsolatedOriginTest : public IsolatedOriginTest { |
| public: |
| DynamicIsolatedOriginTest() |
| : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {} |
| ~DynamicIsolatedOriginTest() override = default; |
| |
| DynamicIsolatedOriginTest(const DynamicIsolatedOriginTest&) = delete; |
| DynamicIsolatedOriginTest& operator=(const DynamicIsolatedOriginTest&) = |
| delete; |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| IsolatedOriginTest::SetUpCommandLine(command_line); |
| command_line->AppendSwitch(switches::kDisableSiteIsolation); |
| |
| if (AreAllSitesIsolatedForTesting()) { |
| LOG(WARNING) << "This test should be run without strict site isolation. " |
| << "It does nothing when --site-per-process is specified."; |
| } |
| } |
| |
| void SetUpOnMainThread() override { |
| https_server()->AddDefaultHandlers(GetTestDataFilePath()); |
| ASSERT_TRUE(https_server()->Start()); |
| IsolatedOriginTest::SetUpOnMainThread(); |
| } |
| |
| // Need an https server because third-party cookies are used, and |
| // SameSite=None cookies must be Secure. |
| net::EmbeddedTestServer* https_server() { return &https_server_; } |
| |
| private: |
| net::EmbeddedTestServer https_server_; |
| }; |
| |
| // Check that dynamically added isolated origins take effect for future |
| // BrowsingInstances only. |
| IN_PROC_BROWSER_TEST_F(DynamicIsolatedOriginTest, |
| IsolationAppliesToFutureBrowsingInstances) { |
| // This test is designed to run without strict site isolation. |
| if (AreAllSitesIsolatedForTesting()) |
| return; |
| |
| // Start on a non-isolated origin with same-site iframe. |
| GURL foo_url( |
| embedded_test_server()->GetURL("foo.com", "/page_with_iframe.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), foo_url)); |
| |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| FrameTreeNode* child = root->child_at(0); |
| |
| // Navigate iframe cross-site. |
| GURL bar_url(embedded_test_server()->GetURL("bar.com", "/title1.html")); |
| NavigateIframeToURL(web_contents(), "test_iframe", bar_url); |
| EXPECT_EQ(child->current_url(), bar_url); |
| |
| // The two frames should be in the same process, since neither site is |
| // isolated so far. |
| if (AreStrictSiteInstancesEnabled()) { |
| EXPECT_NE(root->current_frame_host()->GetSiteInstance(), |
| child->current_frame_host()->GetSiteInstance()); |
| } else { |
| EXPECT_EQ(root->current_frame_host()->GetSiteInstance(), |
| child->current_frame_host()->GetSiteInstance()); |
| } |
| EXPECT_EQ(root->current_frame_host()->GetProcess(), |
| child->current_frame_host()->GetProcess()); |
| |
| // Start isolating foo.com. |
| auto* policy = ChildProcessSecurityPolicyImpl::GetInstance(); |
| policy->AddFutureIsolatedOrigins({url::Origin::Create(foo_url)}, |
| IsolatedOriginSource::TEST); |
| |
| // The isolation shouldn't take effect in the current frame tree, so that it |
| // doesn't break same-site scripting. Navigate iframe to a foo.com URL and |
| // ensure it stays in the same process. |
| NavigateIframeToURL(web_contents(), "test_iframe", foo_url); |
| EXPECT_EQ(root->current_frame_host()->GetSiteInstance(), |
| child->current_frame_host()->GetSiteInstance()); |
| EXPECT_EQ(root->current_frame_host()->GetProcess(), |
| child->current_frame_host()->GetProcess()); |
| |
| // Also try a foo(bar(foo)) hierarchy and check that all frames are still in |
| // the same SiteInstance/process. |
| GURL bar_with_foo_url(embedded_test_server()->GetURL( |
| "bar.com", "/cross_site_iframe_factory.html?bar.com(foo.com)")); |
| NavigateIframeToURL(web_contents(), "test_iframe", bar_with_foo_url); |
| FrameTreeNode* grandchild = child->child_at(0); |
| if (AreStrictSiteInstancesEnabled()) { |
| EXPECT_NE(root->current_frame_host()->GetSiteInstance(), |
| child->current_frame_host()->GetSiteInstance()); |
| EXPECT_NE(child->current_frame_host()->GetSiteInstance(), |
| grandchild->current_frame_host()->GetSiteInstance()); |
| } else { |
| EXPECT_EQ(root->current_frame_host()->GetSiteInstance(), |
| child->current_frame_host()->GetSiteInstance()); |
| EXPECT_EQ(child->current_frame_host()->GetSiteInstance(), |
| grandchild->current_frame_host()->GetSiteInstance()); |
| } |
| EXPECT_EQ(root->current_frame_host()->GetSiteInstance(), |
| grandchild->current_frame_host()->GetSiteInstance()); |
| EXPECT_EQ(root->current_frame_host()->GetProcess(), |
| child->current_frame_host()->GetProcess()); |
| EXPECT_EQ(child->current_frame_host()->GetProcess(), |
| grandchild->current_frame_host()->GetProcess()); |
| |
| // Create an unrelated window, which will be in a new BrowsingInstance. |
| // Ensure that foo.com becomes an isolated origin in that window. A |
| // cross-site bar.com subframe on foo.com should now become an OOPIF. |
| Shell* second_shell = CreateBrowser(); |
| EXPECT_TRUE(NavigateToURL(second_shell, foo_url)); |
| |
| FrameTreeNode* second_root = |
| static_cast<WebContentsImpl*>(second_shell->web_contents()) |
| ->GetPrimaryFrameTree() |
| .root(); |
| FrameTreeNode* second_child = second_root->child_at(0); |
| |
| NavigateIframeToURL(second_shell->web_contents(), "test_iframe", bar_url); |
| scoped_refptr<SiteInstance> foo_instance = |
| second_root->current_frame_host()->GetSiteInstance(); |
| EXPECT_NE(foo_instance, |
| second_child->current_frame_host()->GetSiteInstance()); |
| EXPECT_NE(second_root->current_frame_host()->GetProcess(), |
| second_child->current_frame_host()->GetProcess()); |
| |
| // Now try the reverse: ensure that when bar.com embeds foo.com, foo.com |
| // becomes an OOPIF. |
| EXPECT_TRUE(NavigateToURL(second_shell, bar_with_foo_url)); |
| |
| // We should've swapped processes in the main frame, since we navigated from |
| // (isolated) foo.com to (non-isolated) bar.com. |
| EXPECT_NE(foo_instance, second_root->current_frame_host()->GetSiteInstance()); |
| |
| // Ensure the new foo.com subframe is cross-process. |
| second_child = second_root->child_at(0); |
| EXPECT_NE(second_root->current_frame_host()->GetSiteInstance(), |
| second_child->current_frame_host()->GetSiteInstance()); |
| EXPECT_NE(second_root->current_frame_host()->GetProcess(), |
| second_child->current_frame_host()->GetProcess()); |
| } |
| |
| // Check that dynamically added isolated origins take effect for future |
| // BrowsingInstances only, focusing on various main frame navigations. |
| IN_PROC_BROWSER_TEST_F(DynamicIsolatedOriginTest, MainFrameNavigations) { |
| // This test is designed to run without strict site isolation. |
| if (AreAllSitesIsolatedForTesting()) |
| return; |
| |
| // Create three windows on a non-isolated origin. |
| GURL foo_url(embedded_test_server()->GetURL("foo.com", "/title1.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), foo_url)); |
| |
| Shell* shell2 = CreateBrowser(); |
| EXPECT_TRUE(NavigateToURL(shell2, foo_url)); |
| |
| Shell* shell3 = CreateBrowser(); |
| EXPECT_TRUE(NavigateToURL(shell3, foo_url)); |
| |
| // Create window.open popups in all three windows, which would prevent a |
| // BrowsingInstance swap on renderer-initiated navigations to newly isolated |
| // origins in these windows. |
| OpenPopup(shell(), foo_url, ""); |
| OpenPopup(shell2, GURL(url::kAboutBlankURL), ""); |
| OpenPopup(shell3, embedded_test_server()->GetURL("baz.com", "/title1.html"), |
| ""); |
| |
| // Start isolating bar.com. |
| GURL bar_url(embedded_test_server()->GetURL("bar.com", "/title2.html")); |
| auto* policy = ChildProcessSecurityPolicyImpl::GetInstance(); |
| policy->AddFutureIsolatedOrigins({url::Origin::Create(bar_url)}, |
| IsolatedOriginSource::TEST); |
| |
| // Do a renderer-initiated navigation in each of the existing three windows. |
| // None of them should swap to a new process, since bar.com shouldn't be |
| // isolated in those older BrowsingInstances. |
| int old_process_id = |
| web_contents()->GetPrimaryMainFrame()->GetProcess()->GetID(); |
| EXPECT_TRUE(NavigateToURLFromRenderer(shell(), bar_url)); |
| EXPECT_EQ(old_process_id, |
| web_contents()->GetPrimaryMainFrame()->GetProcess()->GetID()); |
| |
| old_process_id = |
| shell2->web_contents()->GetPrimaryMainFrame()->GetProcess()->GetID(); |
| EXPECT_TRUE(NavigateToURLFromRenderer(shell2, bar_url)); |
| EXPECT_EQ( |
| old_process_id, |
| shell2->web_contents()->GetPrimaryMainFrame()->GetProcess()->GetID()); |
| |
| old_process_id = |
| shell3->web_contents()->GetPrimaryMainFrame()->GetProcess()->GetID(); |
| EXPECT_TRUE(NavigateToURLFromRenderer(shell3, bar_url)); |
| EXPECT_EQ( |
| old_process_id, |
| shell3->web_contents()->GetPrimaryMainFrame()->GetProcess()->GetID()); |
| |
| // Now try the same in a new window and BrowsingInstance, and ensure that the |
| // navigation to bar.com swaps processes in that case. |
| Shell* shell4 = CreateBrowser(); |
| EXPECT_TRUE(NavigateToURL(shell4, foo_url)); |
| |
| old_process_id = |
| shell4->web_contents()->GetPrimaryMainFrame()->GetProcess()->GetID(); |
| EXPECT_TRUE(NavigateToURLFromRenderer(shell4, bar_url)); |
| EXPECT_NE( |
| old_process_id, |
| shell4->web_contents()->GetPrimaryMainFrame()->GetProcess()->GetID()); |
| |
| // Go back to foo.com in window 1, ensuring this stays in the same process. |
| { |
| old_process_id = |
| web_contents()->GetPrimaryMainFrame()->GetProcess()->GetID(); |
| TestNavigationObserver back_observer(web_contents()); |
| web_contents()->GetController().GoBack(); |
| back_observer.Wait(); |
| EXPECT_EQ(old_process_id, |
| web_contents()->GetPrimaryMainFrame()->GetProcess()->GetID()); |
| } |
| |
| // Go back to foo.com in window 4, ensuring this swaps processes. |
| { |
| old_process_id = |
| shell4->web_contents()->GetPrimaryMainFrame()->GetProcess()->GetID(); |
| TestNavigationObserver back_observer(shell4->web_contents()); |
| shell4->web_contents()->GetController().GoBack(); |
| back_observer.Wait(); |
| EXPECT_NE( |
| old_process_id, |
| shell4->web_contents()->GetPrimaryMainFrame()->GetProcess()->GetID()); |
| } |
| } |
| |
| // Check that dynamically added isolated origins do not prevent older processes |
| // for the same origin from accessing cookies. |
| IN_PROC_BROWSER_TEST_F(DynamicIsolatedOriginTest, OldProcessCanAccessCookies) { |
| // This test is designed to run without strict site isolation. |
| if (AreAllSitesIsolatedForTesting()) |
| return; |
| |
| GURL foo_url(embedded_test_server()->GetURL("foo.com", "/title1.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), foo_url)); |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| |
| // Since foo.com isn't isolated yet, its process lock should allow any site. |
| auto* policy = ChildProcessSecurityPolicyImpl::GetInstance(); |
| EXPECT_TRUE(root->current_frame_host() |
| ->GetProcess() |
| ->GetProcessLock() |
| .allows_any_site()); |
| |
| // Start isolating foo.com. |
| policy->AddFutureIsolatedOrigins({url::Origin::Create(foo_url)}, |
| IsolatedOriginSource::TEST); |
| |
| // Create an unrelated window, which will be in a new BrowsingInstance. |
| // foo.com will become an isolated origin in that window. |
| Shell* second_shell = CreateBrowser(); |
| EXPECT_TRUE(NavigateToURL(second_shell, foo_url)); |
| FrameTreeNode* second_root = |
| static_cast<WebContentsImpl*>(second_shell->web_contents()) |
| ->GetPrimaryFrameTree() |
| .root(); |
| |
| // The new window's process should be locked to "foo.com". |
| int isolated_foo_com_process_id = |
| second_root->current_frame_host()->GetProcess()->GetID(); |
| EXPECT_EQ(ProcessLockFromUrl("http://foo.com"), |
| policy->GetProcessLock(isolated_foo_com_process_id)); |
| |
| // Make sure both old and new foo.com processes can access cookies without |
| // renderer kills. |
| EXPECT_TRUE(ExecJs(root, "document.cookie = 'foo=bar';")); |
| EXPECT_EQ("foo=bar", EvalJs(root, "document.cookie")); |
| EXPECT_TRUE(ExecJs(second_root, "document.cookie = 'foo=bar';")); |
| EXPECT_EQ("foo=bar", EvalJs(second_root, "document.cookie")); |
| |
| // Navigate to sub.foo.com in `second_shell`, staying in same |
| // BrowsingInstance. This should stay in the same process. |
| GURL sub_foo_url( |
| embedded_test_server()->GetURL("sub.foo.com", "/title1.html")); |
| EXPECT_TRUE(NavigateToURLInSameBrowsingInstance(second_shell, sub_foo_url)); |
| EXPECT_EQ(isolated_foo_com_process_id, |
| second_root->current_frame_host()->GetProcess()->GetID()); |
| |
| // Now, start isolating sub.foo.com. |
| policy->AddFutureIsolatedOrigins({url::Origin::Create(sub_foo_url)}, |
| IsolatedOriginSource::TEST); |
| |
| // Make sure the process locked to foo.com, which currently has sub.foo.com |
| // committed in it, can still access sub.foo.com cookies. |
| EXPECT_TRUE(ExecJs(second_root, "document.cookie = 'foo=baz';")); |
| EXPECT_EQ("foo=baz", EvalJs(second_root, "document.cookie")); |
| |
| // Now, navigate to sub.foo.com in a new BrowsingInstance. This should go |
| // into a new process, locked to sub.foo.com. |
| // TODO(alexmos): navigating to bar.com prior to navigating to sub.foo.com is |
| // currently needed since we only swap BrowsingInstances on cross-site |
| // address bar navigations. We should look into swapping BrowsingInstances |
| // even on same-site browser-initiated navigations, in cases where the sites |
| // change due to a dynamically isolated origin. |
| EXPECT_TRUE(NavigateToURL( |
| second_shell, embedded_test_server()->GetURL("bar.com", "/title2.html"))); |
| EXPECT_TRUE(NavigateToURL(second_shell, sub_foo_url)); |
| EXPECT_NE(isolated_foo_com_process_id, |
| second_root->current_frame_host()->GetProcess()->GetID()); |
| EXPECT_EQ(ProcessLockFromUrl("http://sub.foo.com"), |
| second_root->current_frame_host()->GetProcess()->GetProcessLock()); |
| |
| // Make sure that process can also access sub.foo.com cookies. |
| EXPECT_TRUE(ExecJs(second_root, "document.cookie = 'foo=qux';")); |
| EXPECT_EQ("foo=qux", EvalJs(second_root, "document.cookie")); |
| } |
| |
| // Verify that when isolating sub.foo.com dynamically, foo.com and sub.foo.com |
| // start to be treated as cross-site for process model decisions. |
| IN_PROC_BROWSER_TEST_F(DynamicIsolatedOriginTest, IsolatedSubdomain) { |
| // This test is designed to run without strict site isolation. |
| if (AreAllSitesIsolatedForTesting()) |
| return; |
| |
| GURL foo_url( |
| embedded_test_server()->GetURL("foo.com", "/page_with_iframe.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), foo_url)); |
| |
| // Start isolating sub.foo.com. |
| GURL sub_foo_url( |
| embedded_test_server()->GetURL("sub.foo.com", "/title1.html")); |
| auto* policy = ChildProcessSecurityPolicyImpl::GetInstance(); |
| policy->AddFutureIsolatedOrigins({url::Origin::Create(sub_foo_url)}, |
| IsolatedOriginSource::TEST); |
| |
| // Navigate to foo.com and then to sub.foo.com in a new BrowsingInstance. |
| // foo.com and sub.foo.com should now be considered cross-site for the |
| // purposes of process assignment, and we should swap processes. |
| Shell* new_shell = CreateBrowser(); |
| EXPECT_TRUE(NavigateToURL(new_shell, foo_url)); |
| int initial_process_id = |
| new_shell->web_contents()->GetPrimaryMainFrame()->GetProcess()->GetID(); |
| EXPECT_TRUE(NavigateToURLFromRenderer(new_shell, sub_foo_url)); |
| EXPECT_NE( |
| initial_process_id, |
| new_shell->web_contents()->GetPrimaryMainFrame()->GetProcess()->GetID()); |
| |
| // Repeat this, but now navigate a subframe on foo.com to sub.foo.com and |
| // ensure that it is rendered in an OOPIF. |
| new_shell = CreateBrowser(); |
| EXPECT_TRUE(NavigateToURL(new_shell, foo_url)); |
| NavigateIframeToURL(new_shell->web_contents(), "test_iframe", sub_foo_url); |
| FrameTreeNode* root = static_cast<WebContentsImpl*>(new_shell->web_contents()) |
| ->GetPrimaryFrameTree() |
| .root(); |
| FrameTreeNode* child = root->child_at(0); |
| |
| EXPECT_NE(root->current_frame_host()->GetSiteInstance(), |
| child->current_frame_host()->GetSiteInstance()); |
| EXPECT_NE(root->current_frame_host()->GetProcess(), |
| child->current_frame_host()->GetProcess()); |
| } |
| |
| // Check that when an isolated origin takes effect in BrowsingInstance 1, a new |
| // BrowsingInstance 2, which reuses an old process from BrowsingInstance 1 for |
| // its main frame, still applies the isolated origin to its subframe. This |
| // demonstrates that isolated origins can't be scoped purely based on process |
| // IDs. |
| IN_PROC_BROWSER_TEST_F(DynamicIsolatedOriginTest, |
| NewBrowsingInstanceInOldProcess) { |
| // This test is designed to run without strict site isolation. |
| if (AreAllSitesIsolatedForTesting()) |
| return; |
| |
| // Force process reuse for main frames in new BrowsingInstances. |
| RenderProcessHost::SetMaxRendererProcessCount(1); |
| |
| // Start on a non-isolated origin with same-site iframe. |
| GURL foo_url(https_server()->GetURL("foo.com", "/page_with_iframe.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), foo_url)); |
| |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| FrameTreeNode* child = root->child_at(0); |
| |
| // Navigate iframe cross-site. |
| GURL bar_url(https_server()->GetURL("bar.com", "/title1.html")); |
| NavigateIframeToURL(web_contents(), "test_iframe", bar_url); |
| EXPECT_EQ(child->current_url(), bar_url); |
| |
| // The iframe should not be in an OOPIF yet. |
| if (AreStrictSiteInstancesEnabled()) { |
| EXPECT_NE(root->current_frame_host()->GetSiteInstance(), |
| child->current_frame_host()->GetSiteInstance()); |
| |
| } else { |
| EXPECT_EQ(root->current_frame_host()->GetSiteInstance(), |
| child->current_frame_host()->GetSiteInstance()); |
| } |
| EXPECT_EQ(root->current_frame_host()->GetProcess(), |
| child->current_frame_host()->GetProcess()); |
| |
| // Start isolating bar.com. |
| auto* policy = ChildProcessSecurityPolicyImpl::GetInstance(); |
| policy->AddFutureIsolatedOrigins({url::Origin::Create(bar_url)}, |
| IsolatedOriginSource::TEST); |
| |
| // Open a new window in a new BrowsingInstance. Navigate to foo.com and |
| // check that the old foo.com process is reused. |
| Shell* new_shell = CreateBrowser(); |
| EXPECT_TRUE(NavigateToURL(new_shell, foo_url)); |
| FrameTreeNode* new_root = |
| static_cast<WebContentsImpl*>(new_shell->web_contents()) |
| ->GetPrimaryFrameTree() |
| .root(); |
| FrameTreeNode* new_child = new_root->child_at(0); |
| |
| EXPECT_EQ(new_root->current_frame_host()->GetProcess(), |
| root->current_frame_host()->GetProcess()); |
| EXPECT_NE(new_root->current_frame_host()->GetSiteInstance(), |
| root->current_frame_host()->GetSiteInstance()); |
| EXPECT_FALSE( |
| new_root->current_frame_host()->GetSiteInstance()->IsRelatedSiteInstance( |
| root->current_frame_host()->GetSiteInstance())); |
| |
| // Navigate iframe in the second window to bar.com, and check that it becomes |
| // an OOPIF in its own process. |
| NavigateIframeToURL(new_shell->web_contents(), "test_iframe", bar_url); |
| EXPECT_EQ(new_child->current_url(), bar_url); |
| |
| EXPECT_NE(new_child->current_frame_host()->GetProcess(), |
| new_root->current_frame_host()->GetProcess()); |
| EXPECT_NE(new_child->current_frame_host()->GetProcess(), |
| root->current_frame_host()->GetProcess()); |
| EXPECT_NE(new_child->current_frame_host()->GetProcess(), |
| child->current_frame_host()->GetProcess()); |
| |
| EXPECT_NE(new_child->current_frame_host()->GetSiteInstance(), |
| new_root->current_frame_host()->GetSiteInstance()); |
| EXPECT_NE(new_child->current_frame_host()->GetSiteInstance(), |
| child->current_frame_host()->GetSiteInstance()); |
| |
| // The old foo.com process should still be able to access bar.com data, |
| // since it isn't locked to a specific site. |
| int old_process_id = root->current_frame_host()->GetProcess()->GetID(); |
| EXPECT_TRUE(policy->CanAccessDataForOrigin(old_process_id, |
| url::Origin::Create(bar_url))); |
| |
| // In particular, make sure the bar.com iframe in the old foo.com process can |
| // still access bar.com cookies. |
| EXPECT_TRUE( |
| ExecJs(child, "document.cookie = 'foo=bar;SameSite=None;Secure';")); |
| EXPECT_EQ("foo=bar", EvalJs(child, "document.cookie")); |
| |
| // Make sure the BrowsingInstanceId is cleaned up immediately. |
| policy->SetBrowsingInstanceCleanupDelayForTesting(0); |
| |
| // Now close the first window. This destroys the first BrowsingInstance and |
| // leaves only the newer BrowsingInstance (with a foo.com main frame) in the |
| // old process. |
| shell()->Close(); |
| |
| // Now that the process only contains a BrowsingInstance where bar.com is |
| // considered isolated and cannot reuse the old process, it should lose access |
| // to bar.com's data due to citadel enforcement in CanAccessDataForOrigin. |
| if (base::FeatureList::IsEnabled(kSiteIsolationCitadelEnforcement)) { |
| EXPECT_FALSE(policy->CanAccessDataForOrigin(old_process_id, |
| url::Origin::Create(bar_url))); |
| } else { |
| EXPECT_TRUE(policy->CanAccessDataForOrigin(old_process_id, |
| url::Origin::Create(bar_url))); |
| } |
| } |
| |
| // Verify that a process locked to foo.com is not reused for a navigation to |
| // foo.com that does not require a dedicated process. See |
| // https://crbug.com/950453. |
| IN_PROC_BROWSER_TEST_F(DynamicIsolatedOriginTest, |
| LockedProcessNotReusedForNonisolatedSameSiteNavigation) { |
| // This test is designed to run without strict site isolation. |
| if (AreAllSitesIsolatedForTesting()) |
| return; |
| |
| // Set the process limit to 1. |
| RenderProcessHost::SetMaxRendererProcessCount(1); |
| |
| // Start on a non-isolated foo.com URL. |
| GURL foo_url(embedded_test_server()->GetURL("foo.com", "/title1.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), foo_url)); |
| |
| // Navigate to a different isolated origin and wait for the original foo.com |
| // process to shut down. Note that the foo.com SiteInstance will stick |
| // around in session history. |
| RenderProcessHostWatcher foo_process_observer( |
| web_contents()->GetPrimaryMainFrame()->GetProcess(), |
| RenderProcessHostWatcher::WATCH_FOR_HOST_DESTRUCTION); |
| |
| // Disable the BackForwardCache to ensure the old process is going to be |
| // released. |
| DisableBackForwardCacheForTesting(web_contents(), |
| BackForwardCache::TEST_REQUIRES_NO_CACHING); |
| |
| GURL isolated_bar_url( |
| embedded_test_server()->GetURL("isolated.bar.com", "/title1.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), isolated_bar_url)); |
| foo_process_observer.Wait(); |
| EXPECT_TRUE(foo_process_observer.did_exit_normally()); |
| |
| // Start isolating foo.com. |
| auto* policy = ChildProcessSecurityPolicyImpl::GetInstance(); |
| policy->AddFutureIsolatedOrigins({url::Origin::Create(foo_url)}, |
| IsolatedOriginSource::TEST); |
| |
| // Create a new window, forcing a new BrowsingInstance, and navigate it to |
| // foo.com, which will spin up a process locked to foo.com. |
| Shell* new_shell = CreateBrowser(); |
| EXPECT_TRUE(NavigateToURL(new_shell, foo_url)); |
| RenderProcessHost* new_process = |
| new_shell->web_contents()->GetPrimaryMainFrame()->GetProcess(); |
| EXPECT_EQ(ProcessLockFromUrl("http://foo.com"), |
| new_process->GetProcessLock()); |
| |
| // Go to foo.com in the older first tab, where foo.com does not require a |
| // dedicated process. Ensure that the existing locked foo.com process is |
| // *not* reused in that case (if that were the case, LockProcessIfNeeded |
| // would trigger a CHECK here). Using a history navigation here ensures that |
| // the SiteInstance (from session history) will have a foo.com site URL, |
| // rather than a default site URL, since this case isn't yet handled by the |
| // default SiteInstance (see crbug.com/787576). |
| TestNavigationObserver observer(web_contents()); |
| web_contents()->GetController().GoBack(); |
| observer.Wait(); |
| EXPECT_NE(web_contents()->GetPrimaryMainFrame()->GetProcess(), new_process); |
| } |
| |
| // Checks that isolated origins can be added only for a specific profile, |
| // and that they don't apply to other profiles. |
| IN_PROC_BROWSER_TEST_F(DynamicIsolatedOriginTest, PerProfileIsolation) { |
| // This test is designed to run without strict site isolation. |
| if (AreAllSitesIsolatedForTesting()) |
| return; |
| |
| // Create a browser in a different profile. |
| BrowserContext* main_context = shell()->web_contents()->GetBrowserContext(); |
| Shell* other_shell = CreateOffTheRecordBrowser(); |
| BrowserContext* other_context = |
| other_shell->web_contents()->GetBrowserContext(); |
| ASSERT_NE(main_context, other_context); |
| |
| // Start on bar.com in both browsers. |
| GURL bar_url(embedded_test_server()->GetURL("bar.com", "/title1.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), bar_url)); |
| EXPECT_TRUE(NavigateToURL(other_shell, bar_url)); |
| |
| // Start isolating foo.com in `other_context` only. |
| GURL foo_url( |
| embedded_test_server()->GetURL("foo.com", "/page_with_iframe.html")); |
| auto* policy = ChildProcessSecurityPolicyImpl::GetInstance(); |
| policy->AddFutureIsolatedOrigins({url::Origin::Create(foo_url)}, |
| IsolatedOriginSource::TEST, other_context); |
| |
| // Verify that foo.com is indeed isolated in `other_shell`, by navigating to |
| // it in a new BrowsingInstance and checking that a bar.com subframe becomes |
| // an OOPIF. |
| EXPECT_TRUE(NavigateToURL(other_shell, foo_url)); |
| WebContentsImpl* other_contents = |
| static_cast<WebContentsImpl*>(other_shell->web_contents()); |
| NavigateIframeToURL(other_contents, "test_iframe", bar_url); |
| FrameTreeNode* root = other_contents->GetPrimaryFrameTree().root(); |
| FrameTreeNode* child = root->child_at(0); |
| EXPECT_EQ(child->current_url(), bar_url); |
| EXPECT_NE(root->current_frame_host()->GetSiteInstance(), |
| child->current_frame_host()->GetSiteInstance()); |
| EXPECT_NE(root->current_frame_host()->GetProcess(), |
| child->current_frame_host()->GetProcess()); |
| |
| // Verify that foo.com is *not* isolated in the regular shell, due to a |
| // different profile. |
| EXPECT_TRUE(NavigateToURL(shell(), foo_url)); |
| NavigateIframeToURL(web_contents(), "test_iframe", bar_url); |
| root = web_contents()->GetPrimaryFrameTree().root(); |
| child = root->child_at(0); |
| EXPECT_EQ(child->current_url(), bar_url); |
| if (AreStrictSiteInstancesEnabled()) { |
| EXPECT_NE(root->current_frame_host()->GetSiteInstance(), |
| child->current_frame_host()->GetSiteInstance()); |
| } else { |
| EXPECT_EQ(root->current_frame_host()->GetSiteInstance(), |
| child->current_frame_host()->GetSiteInstance()); |
| } |
| EXPECT_EQ(root->current_frame_host()->GetProcess(), |
| child->current_frame_host()->GetProcess()); |
| } |
| |
| // Check that a dynamically added isolated origin can take effect on the next |
| // main frame navigation by forcing a BrowsingInstance swap, in the case that |
| // there are no script references to the frame being navigated. |
| IN_PROC_BROWSER_TEST_F(DynamicIsolatedOriginTest, ForceBrowsingInstanceSwap) { |
| // This test is designed to run without strict site isolation. |
| if (AreAllSitesIsolatedForTesting()) |
| return; |
| |
| // Navigate to a non-isolated page with a cross-site iframe. The frame |
| // shouldn't be in an OOPIF. |
| GURL foo_url(embedded_test_server()->GetURL( |
| "foo.com", "/cross_site_iframe_factory.html?foo.com(bar.com)")); |
| EXPECT_TRUE(NavigateToURL(shell(), foo_url)); |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| FrameTreeNode* child = root->child_at(0); |
| scoped_refptr<SiteInstance> first_instance = |
| root->current_frame_host()->GetSiteInstance(); |
| |
| if (AreStrictSiteInstancesEnabled()) { |
| EXPECT_NE(first_instance, child->current_frame_host()->GetSiteInstance()); |
| } else { |
| EXPECT_EQ(first_instance, child->current_frame_host()->GetSiteInstance()); |
| } |
| EXPECT_EQ(root->current_frame_host()->GetProcess(), |
| child->current_frame_host()->GetProcess()); |
| auto* policy = ChildProcessSecurityPolicyImpl::GetInstance(); |
| EXPECT_TRUE(first_instance->GetProcess()->GetProcessLock().allows_any_site()); |
| |
| // Start isolating foo.com. |
| BrowserContext* context = shell()->web_contents()->GetBrowserContext(); |
| policy->AddFutureIsolatedOrigins({url::Origin::Create(foo_url)}, |
| IsolatedOriginSource::TEST, context); |
| |
| // Try navigating to another foo URL. |
| GURL foo2_url(embedded_test_server()->GetURL( |
| "foo.com", "/cross_site_iframe_factory.html?foo.com(baz.com)")); |
| EXPECT_TRUE(NavigateToURL(shell(), foo2_url)); |
| |
| // Verify that this navigation ended up in a dedicated process, and that we |
| // swapped BrowsingInstances in the process. |
| scoped_refptr<SiteInstance> second_instance = |
| root->current_frame_host()->GetSiteInstance(); |
| EXPECT_NE(first_instance, second_instance); |
| EXPECT_FALSE(first_instance->IsRelatedSiteInstance(second_instance.get())); |
| EXPECT_NE(first_instance->GetProcess(), second_instance->GetProcess()); |
| EXPECT_EQ(ProcessLockFromUrl("http://foo.com"), |
| second_instance->GetProcess()->GetProcessLock()); |
| |
| // The frame on that page should now be an OOPIF. |
| child = root->child_at(0); |
| EXPECT_NE(second_instance, child->current_frame_host()->GetSiteInstance()); |
| EXPECT_NE(root->current_frame_host()->GetProcess(), |
| child->current_frame_host()->GetProcess()); |
| } |
| |
| // Same as the test above, but using a renderer-initiated navigation. Check |
| // that a dynamically added isolated origin can take effect on the next main |
| // frame navigation by forcing a BrowsingInstance swap, in the case that there |
| // are no script references to the frame being navigated. |
| IN_PROC_BROWSER_TEST_F(DynamicIsolatedOriginTest, |
| ForceBrowsingInstanceSwap_RendererInitiated) { |
| // This test is designed to run without strict site isolation. |
| if (AreAllSitesIsolatedForTesting()) |
| return; |
| |
| // Navigate to a foo.com page. |
| GURL foo_url(embedded_test_server()->GetURL("foo.com", "/title1.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), foo_url)); |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| scoped_refptr<SiteInstance> first_instance = |
| root->current_frame_host()->GetSiteInstance(); |
| EXPECT_FALSE(first_instance->RequiresDedicatedProcess()); |
| auto* policy = ChildProcessSecurityPolicyImpl::GetInstance(); |
| EXPECT_TRUE(first_instance->GetProcess()->GetProcessLock().allows_any_site()); |
| |
| // Set a sessionStorage value, to sanity check that foo.com's session storage |
| // will still be accessible after the BrowsingInstance swap. |
| EXPECT_TRUE(ExecJs(root, "window.sessionStorage['foo'] = 'bar';")); |
| |
| // Start isolating foo.com. |
| BrowserContext* context = shell()->web_contents()->GetBrowserContext(); |
| policy->AddFutureIsolatedOrigins({url::Origin::Create(foo_url)}, |
| IsolatedOriginSource::TEST, context); |
| |
| // Do a renderer-initiated navigation to another foo URL. |
| GURL foo2_url(embedded_test_server()->GetURL( |
| "foo.com", "/cross_site_iframe_factory.html?foo.com(baz.com)")); |
| EXPECT_TRUE(NavigateToURLFromRenderer(shell(), foo2_url)); |
| |
| // Verify that this navigation ended up in a dedicated process, and that we |
| // swapped BrowsingInstances in the process. |
| scoped_refptr<SiteInstance> second_instance = |
| root->current_frame_host()->GetSiteInstance(); |
| EXPECT_NE(first_instance, second_instance); |
| EXPECT_FALSE(first_instance->IsRelatedSiteInstance(second_instance.get())); |
| EXPECT_NE(first_instance->GetProcess(), second_instance->GetProcess()); |
| EXPECT_EQ(ProcessLockFromUrl("http://foo.com"), |
| second_instance->GetProcess()->GetProcessLock()); |
| |
| // The frame on that page should be an OOPIF. |
| FrameTreeNode* child = root->child_at(0); |
| EXPECT_NE(second_instance, child->current_frame_host()->GetSiteInstance()); |
| EXPECT_NE(root->current_frame_host()->GetProcess(), |
| child->current_frame_host()->GetProcess()); |
| |
| // Verify that the isolated foo.com page can still access session storage set |
| // by the previous foo.com page. |
| EXPECT_EQ("bar", EvalJs(root, "window.sessionStorage['foo']")); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(DynamicIsolatedOriginTest, |
| DontForceBrowsingInstanceSwapWhenScriptReferencesExist) { |
| // This test is designed to run without strict site isolation. |
| if (AreAllSitesIsolatedForTesting()) |
| return; |
| |
| // Navigate to a page that won't be in a dedicated process. |
| GURL foo_url(embedded_test_server()->GetURL("foo.com", "/title1.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), foo_url)); |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| scoped_refptr<SiteInstance> first_instance = |
| root->current_frame_host()->GetSiteInstance(); |
| EXPECT_FALSE(first_instance->RequiresDedicatedProcess()); |
| |
| // Start isolating foo.com. |
| BrowserContext* context = shell()->web_contents()->GetBrowserContext(); |
| auto* policy = ChildProcessSecurityPolicyImpl::GetInstance(); |
| policy->AddFutureIsolatedOrigins({url::Origin::Create(foo_url)}, |
| IsolatedOriginSource::TEST, context); |
| |
| // Open a popup. |
| GURL popup_url(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| OpenPopup(shell(), popup_url, ""); |
| |
| // Try navigating the main frame to another foo URL. |
| GURL foo2_url(embedded_test_server()->GetURL("foo.com", "/title2.html")); |
| EXPECT_TRUE(NavigateToURLFromRenderer(shell(), foo2_url)); |
| |
| // This navigation should not end up in a dedicated process. The popup |
| // should prevent the BrowsingInstance swap heuristic from applying, since it |
| // should still be able to communicate with the opener after the navigation. |
| EXPECT_EQ(first_instance, root->current_frame_host()->GetSiteInstance()); |
| EXPECT_FALSE(first_instance->RequiresDedicatedProcess()); |
| EXPECT_TRUE(first_instance->GetProcess()->GetProcessLock().allows_any_site()); |
| } |
| |
| // This test ensures that when a page becomes isolated in the middle of |
| // creating and navigating a new window, the new window prevents a |
| // BrowsingInstance swap. |
| IN_PROC_BROWSER_TEST_F( |
| DynamicIsolatedOriginTest, |
| DontForceBrowsingInstanceSwapWithPendingNavigationInNewWindow) { |
| // This test is designed to run without strict site isolation. |
| if (AreAllSitesIsolatedForTesting()) |
| return; |
| |
| // Navigate to a page that won't be in a dedicated process. |
| GURL foo_url(embedded_test_server()->GetURL("foo.com", "/title1.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), foo_url)); |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| scoped_refptr<SiteInstance> first_instance = |
| root->current_frame_host()->GetSiteInstance(); |
| EXPECT_FALSE(first_instance->RequiresDedicatedProcess()); |
| |
| // Open and start navigating a popup to a URL that never finishes loading. |
| GURL popup_url(embedded_test_server()->GetURL("a.com", "/hung")); |
| EXPECT_TRUE(ExecJs(root, JsReplace("window.open($1);", popup_url))); |
| |
| // Start isolating foo.com. |
| BrowserContext* context = shell()->web_contents()->GetBrowserContext(); |
| auto* policy = ChildProcessSecurityPolicyImpl::GetInstance(); |
| policy->AddFutureIsolatedOrigins({url::Origin::Create(foo_url)}, |
| IsolatedOriginSource::TEST, context); |
| |
| // Navigate the main frame to another foo URL. |
| GURL foo2_url(embedded_test_server()->GetURL("foo.com", "/title2.html")); |
| EXPECT_TRUE(NavigateToURLFromRenderer(shell(), foo2_url)); |
| |
| // This navigation should not end up in a dedicated process. The pending |
| // navigation in the popup should prevent the BrowsingInstance swap heuristic |
| // from applying, since it should still be able to communicate with the |
| // opener after the navigation. |
| EXPECT_EQ(first_instance, root->current_frame_host()->GetSiteInstance()); |
| EXPECT_FALSE(first_instance->RequiresDedicatedProcess()); |
| EXPECT_TRUE(first_instance->GetProcess()->GetProcessLock().allows_any_site()); |
| } |
| |
| class IsolatedOriginTestWithStrictSiteInstances : public IsolatedOriginTest { |
| public: |
| IsolatedOriginTestWithStrictSiteInstances() { |
| scoped_feature_list_.InitAndEnableFeature( |
| features::kProcessSharingWithStrictSiteInstances); |
| } |
| ~IsolatedOriginTestWithStrictSiteInstances() override = default; |
| |
| IsolatedOriginTestWithStrictSiteInstances( |
| const IsolatedOriginTestWithStrictSiteInstances&) = delete; |
| IsolatedOriginTestWithStrictSiteInstances& operator=( |
| const IsolatedOriginTestWithStrictSiteInstances&) = delete; |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| IsolatedOriginTest::SetUpCommandLine(command_line); |
| command_line->AppendSwitch(switches::kDisableSiteIsolation); |
| |
| if (AreAllSitesIsolatedForTesting()) { |
| LOG(WARNING) << "This test should be run without strict site isolation. " |
| << "It does nothing when --site-per-process is specified."; |
| } |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(IsolatedOriginTestWithStrictSiteInstances, |
| NonIsolatedFramesCanShareDefaultProcess) { |
| // This test is designed to run without strict site isolation. |
| if (AreAllSitesIsolatedForTesting()) |
| return; |
| |
| GURL top_url( |
| embedded_test_server()->GetURL("/frame_tree/page_with_two_frames.html")); |
| ASSERT_FALSE(IsIsolatedOrigin(url::Origin::Create(top_url))); |
| EXPECT_TRUE(NavigateToURL(shell(), top_url)); |
| |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| FrameTreeNode* child1 = root->child_at(0); |
| FrameTreeNode* child2 = root->child_at(1); |
| |
| GURL bar_url(embedded_test_server()->GetURL("www.bar.com", "/title3.html")); |
| ASSERT_FALSE(IsIsolatedOrigin(url::Origin::Create(bar_url))); |
| { |
| TestFrameNavigationObserver observer(child1); |
| NavigationHandleObserver handle_observer(web_contents(), bar_url); |
| EXPECT_TRUE(ExecJs(child1, "location.href = '" + bar_url.spec() + "';")); |
| observer.Wait(); |
| } |
| |
| GURL baz_url(embedded_test_server()->GetURL("www.baz.com", "/title3.html")); |
| ASSERT_FALSE(IsIsolatedOrigin(url::Origin::Create(baz_url))); |
| { |
| TestFrameNavigationObserver observer(child2); |
| NavigationHandleObserver handle_observer(web_contents(), baz_url); |
| EXPECT_TRUE(ExecJs(child2, "location.href = '" + baz_url.spec() + "';")); |
| observer.Wait(); |
| } |
| |
| // All 3 frames are from different sites, so each should have its own |
| // SiteInstance. |
| EXPECT_NE(root->current_frame_host()->GetSiteInstance(), |
| child1->current_frame_host()->GetSiteInstance()); |
| EXPECT_NE(root->current_frame_host()->GetSiteInstance(), |
| child2->current_frame_host()->GetSiteInstance()); |
| EXPECT_NE(child1->current_frame_host()->GetSiteInstance(), |
| child2->current_frame_host()->GetSiteInstance()); |
| EXPECT_EQ( |
| " Site A ------------ proxies for B C\n" |
| " |--Site B ------- proxies for A C\n" |
| " +--Site C ------- proxies for A B\n" |
| "Where A = http://127.0.0.1/\n" |
| " B = http://bar.com/\n" |
| " C = http://baz.com/", |
| DepictFrameTree(*root)); |
| |
| // But none are isolated, so all should share the default process for their |
| // BrowsingInstance. |
| RenderProcessHost* host = root->current_frame_host()->GetProcess(); |
| EXPECT_EQ(host, child1->current_frame_host()->GetProcess()); |
| EXPECT_EQ(host, child2->current_frame_host()->GetProcess()); |
| EXPECT_TRUE(host->GetProcessLock().allows_any_site()); |
| } |
| |
| // Creates a non-isolated main frame with an isolated child and non-isolated |
| // grandchild. With strict site isolation disabled and |
| // kProcessSharingWithStrictSiteInstances enabled, the main frame and the |
| // grandchild should be in the same process even though they have different |
| // SiteInstances. |
| IN_PROC_BROWSER_TEST_F(IsolatedOriginTestWithStrictSiteInstances, |
| IsolatedChildWithNonIsolatedGrandchild) { |
| // This test is designed to run without strict site isolation. |
| if (AreAllSitesIsolatedForTesting()) |
| return; |
| |
| GURL top_url( |
| embedded_test_server()->GetURL("www.foo.com", "/page_with_iframe.html")); |
| ASSERT_FALSE(IsIsolatedOrigin(url::Origin::Create(top_url))); |
| EXPECT_TRUE(NavigateToURL(shell(), top_url)); |
| |
| GURL isolated_url(embedded_test_server()->GetURL("isolated.foo.com", |
| "/page_with_iframe.html")); |
| ASSERT_TRUE(IsIsolatedOrigin(url::Origin::Create(isolated_url))); |
| |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| FrameTreeNode* child = root->child_at(0); |
| |
| NavigateIframeToURL(web_contents(), "test_iframe", isolated_url); |
| EXPECT_EQ(child->current_url(), isolated_url); |
| |
| // Verify that the child frame is an OOPIF with a different SiteInstance. |
| EXPECT_NE(web_contents()->GetSiteInstance(), |
| child->current_frame_host()->GetSiteInstance()); |
| EXPECT_TRUE(child->current_frame_host()->IsCrossProcessSubframe()); |
| EXPECT_EQ(GURL("http://isolated.foo.com/"), |
| child->current_frame_host()->GetSiteInstance()->GetSiteURL()); |
| |
| // Verify that the isolated frame's subframe (which starts out at a relative |
| // path) is kept in the isolated parent's SiteInstance. |
| FrameTreeNode* grandchild = child->child_at(0); |
| EXPECT_EQ(child->current_frame_host()->GetSiteInstance(), |
| grandchild->current_frame_host()->GetSiteInstance()); |
| |
| // Navigating the grandchild to www.bar.com should put it into the top |
| // frame's process, but not its SiteInstance. |
| GURL non_isolated_url( |
| embedded_test_server()->GetURL("www.bar.com", "/title3.html")); |
| ASSERT_FALSE(IsIsolatedOrigin(url::Origin::Create(non_isolated_url))); |
| TestFrameNavigationObserver observer(grandchild); |
| EXPECT_TRUE( |
| ExecJs(grandchild, "location.href = '" + non_isolated_url.spec() + "';")); |
| observer.Wait(); |
| EXPECT_EQ(non_isolated_url, grandchild->current_url()); |
| |
| EXPECT_NE(root->current_frame_host()->GetSiteInstance(), |
| grandchild->current_frame_host()->GetSiteInstance()); |
| EXPECT_NE(child->current_frame_host()->GetSiteInstance(), |
| grandchild->current_frame_host()->GetSiteInstance()); |
| EXPECT_EQ(root->current_frame_host()->GetProcess(), |
| grandchild->current_frame_host()->GetProcess()); |
| EXPECT_EQ( |
| " Site A ------------ proxies for B C\n" |
| " +--Site B ------- proxies for A C\n" |
| " +--Site C -- proxies for A B\n" |
| "Where A = http://foo.com/\n" |
| " B = http://isolated.foo.com/\n" |
| " C = http://bar.com/", |
| DepictFrameTree(*root)); |
| } |
| |
| // Navigate a frame into and out of an isolated origin. This should not |
| // confuse BrowsingInstance into holding onto a stale default_process_. |
| IN_PROC_BROWSER_TEST_F(IsolatedOriginTestWithStrictSiteInstances, |
| SubframeNavigatesOutofIsolationThenToIsolation) { |
| // This test is designed to run without strict site isolation. |
| if (AreAllSitesIsolatedForTesting()) |
| return; |
| |
| GURL isolated_url(embedded_test_server()->GetURL("isolated.foo.com", |
| "/page_with_iframe.html")); |
| ASSERT_TRUE(IsIsolatedOrigin(url::Origin::Create(isolated_url))); |
| EXPECT_TRUE(NavigateToURL(shell(), isolated_url)); |
| |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| FrameTreeNode* child = root->child_at(0); |
| EXPECT_EQ(web_contents()->GetSiteInstance(), |
| child->current_frame_host()->GetSiteInstance()); |
| EXPECT_FALSE(child->current_frame_host()->IsCrossProcessSubframe()); |
| |
| GURL non_isolated_url( |
| embedded_test_server()->GetURL("www.foo.com", "/title3.html")); |
| ASSERT_FALSE(IsIsolatedOrigin(url::Origin::Create(non_isolated_url))); |
| NavigateIframeToURL(web_contents(), "test_iframe", non_isolated_url); |
| EXPECT_EQ(child->current_url(), non_isolated_url); |
| |
| // Verify that the child frame is an OOPIF with a different SiteInstance. |
| EXPECT_NE(web_contents()->GetSiteInstance(), |
| child->current_frame_host()->GetSiteInstance()); |
| EXPECT_NE(root->current_frame_host()->GetProcess(), |
| child->current_frame_host()->GetProcess()); |
| |
| // Navigating the child to the isolated origin again. |
| NavigateIframeToURL(web_contents(), "test_iframe", isolated_url); |
| EXPECT_EQ(child->current_url(), isolated_url); |
| EXPECT_EQ(web_contents()->GetSiteInstance(), |
| child->current_frame_host()->GetSiteInstance()); |
| |
| // And navigate out of the isolated origin one last time. |
| NavigateIframeToURL(web_contents(), "test_iframe", non_isolated_url); |
| EXPECT_EQ(child->current_url(), non_isolated_url); |
| EXPECT_NE(web_contents()->GetSiteInstance(), |
| child->current_frame_host()->GetSiteInstance()); |
| EXPECT_NE(root->current_frame_host()->GetProcess(), |
| child->current_frame_host()->GetProcess()); |
| EXPECT_EQ( |
| " Site A ------------ proxies for B\n" |
| " +--Site B ------- proxies for A\n" |
| "Where A = http://isolated.foo.com/\n" |
| " B = http://foo.com/", |
| DepictFrameTree(*root)); |
| } |
| |
| // Ensure a popup and its opener can go in the same process, even though |
| // they have different SiteInstances with kProcessSharingWithStrictSiteInstances |
| // enabled. |
| IN_PROC_BROWSER_TEST_F(IsolatedOriginTestWithStrictSiteInstances, |
| NonIsolatedPopup) { |
| // This test is designed to run without strict site isolation. |
| if (AreAllSitesIsolatedForTesting()) |
| return; |
| |
| GURL foo_url( |
| embedded_test_server()->GetURL("www.foo.com", "/page_with_iframe.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), foo_url)); |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| |
| // Open a blank popup. |
| ShellAddedObserver new_shell_observer; |
| EXPECT_TRUE(ExecJs(root, "window.w = window.open();")); |
| Shell* new_shell = new_shell_observer.GetShell(); |
| |
| // Have the opener navigate the popup to a non-isolated origin. |
| GURL isolated_url( |
| embedded_test_server()->GetURL("www.bar.com", "/title1.html")); |
| { |
| TestNavigationManager manager(new_shell->web_contents(), isolated_url); |
| EXPECT_TRUE(ExecJs( |
| root, "window.w.location.href = '" + isolated_url.spec() + "';")); |
| ASSERT_TRUE(manager.WaitForNavigationFinished()); |
| } |
| |
| // The popup and the opener should not share a SiteInstance, but should |
| // end up in the same process. |
| EXPECT_NE(new_shell->web_contents()->GetPrimaryMainFrame()->GetSiteInstance(), |
| root->current_frame_host()->GetSiteInstance()); |
| EXPECT_EQ(root->current_frame_host()->GetProcess(), |
| new_shell->web_contents()->GetPrimaryMainFrame()->GetProcess()); |
| EXPECT_EQ( |
| " Site A ------------ proxies for B\n" |
| " +--Site A ------- proxies for B\n" |
| "Where A = http://foo.com/\n" |
| " B = http://bar.com/", |
| DepictFrameTree(*root)); |
| EXPECT_EQ( |
| " Site A ------------ proxies for B\n" |
| "Where A = http://bar.com/\n" |
| " B = http://foo.com/", |
| DepictFrameTree(*static_cast<WebContentsImpl*>(new_shell->web_contents()) |
| ->GetPrimaryFrameTree() |
| .root())); |
| } |
| |
| class WildcardOriginIsolationTest : public IsolatedOriginTestBase { |
| public: |
| WildcardOriginIsolationTest() = default; |
| ~WildcardOriginIsolationTest() override = default; |
| |
| WildcardOriginIsolationTest(const WildcardOriginIsolationTest&) = delete; |
| WildcardOriginIsolationTest& operator=(const WildcardOriginIsolationTest&) = |
| delete; |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| ASSERT_TRUE(embedded_test_server()->InitializeAndListen()); |
| |
| std::string origin_list = |
| MakeWildcard(embedded_test_server()->GetURL("isolated.foo.com", "/")) + |
| "," + embedded_test_server()->GetURL("foo.com", "/").spec(); |
| |
| command_line->AppendSwitchASCII(switches::kIsolateOrigins, origin_list); |
| |
| // This is needed for this test to run properly on platforms where |
| // --site-per-process isn't the default, such as Android. |
| IsolateAllSitesForTesting(command_line); |
| } |
| |
| void SetUpOnMainThread() override { |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| embedded_test_server()->StartAcceptingConnections(); |
| } |
| |
| private: |
| const char* kAllSubdomainWildcard = "[*.]"; |
| |
| // Calling GetURL() on the embedded test server will escape any '*' characters |
| // into '%2A', so to create a wildcard origin they must be post-processed to |
| // have the string '[*.]' inserted at the correct point. |
| std::string MakeWildcard(GURL url) { |
| DCHECK(url.is_valid()); |
| return url.scheme() + url::kStandardSchemeSeparator + |
| kAllSubdomainWildcard + url.GetContent(); |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(WildcardOriginIsolationTest, MainFrameNavigation) { |
| GURL a_foo_url(embedded_test_server()->GetURL("a.foo.com", "/title1.html")); |
| GURL b_foo_url(embedded_test_server()->GetURL("b.foo.com", "/title1.html")); |
| GURL a_isolated_url( |
| embedded_test_server()->GetURL("a.isolated.foo.com", "/title1.html")); |
| GURL b_isolated_url( |
| embedded_test_server()->GetURL("b.isolated.foo.com", "/title1.html")); |
| |
| EXPECT_TRUE(IsIsolatedOrigin(a_foo_url)); |
| EXPECT_TRUE(IsIsolatedOrigin(b_foo_url)); |
| EXPECT_TRUE(IsIsolatedOrigin(a_isolated_url)); |
| EXPECT_TRUE(IsIsolatedOrigin(b_isolated_url)); |
| |
| // Navigate in the following order, all within the same shell: |
| // 1. a_foo_url |
| // 2. b_foo_url -- check (1) and (2) have the same pids / instances (*) |
| // 3. a_isolated_url |
| // 4. b_isolated_url -- check (2), (3) and (4) have distinct pids / instances |
| // 5. a_foo_url -- check (4) and (5) have distinct pids / instances |
| // 6. b_foo_url -- check (5) and (6) have the same pids / instances (*) |
| // (*) SiteInstances will be the same unless ProactivelySwapBrowsingInstances |
| // is enabled for same-site navigations. |
| EXPECT_TRUE(NavigateToURL(shell(), a_foo_url)); |
| int a_foo_pid = |
| shell()->web_contents()->GetPrimaryMainFrame()->GetProcess()->GetID(); |
| scoped_refptr<SiteInstance> a_foo_instance = |
| shell()->web_contents()->GetPrimaryMainFrame()->GetSiteInstance(); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), b_foo_url)); |
| int b_foo_pid = |
| shell()->web_contents()->GetPrimaryMainFrame()->GetProcess()->GetID(); |
| scoped_refptr<SiteInstance> b_foo_instance = |
| shell()->web_contents()->GetPrimaryMainFrame()->GetSiteInstance(); |
| |
| // Check that hosts in the wildcard subdomain (but not the wildcard subdomain |
| // itself) have their processes reused between navigation events. |
| EXPECT_EQ(a_foo_pid, b_foo_pid); |
| if (CanSameSiteMainFrameNavigationsChangeSiteInstances()) { |
| EXPECT_NE(a_foo_instance, b_foo_instance); |
| } else { |
| EXPECT_EQ(a_foo_instance, b_foo_instance); |
| } |
| |
| EXPECT_TRUE(NavigateToURL(shell(), a_isolated_url)); |
| int a_isolated_pid = |
| shell()->web_contents()->GetPrimaryMainFrame()->GetProcess()->GetID(); |
| scoped_refptr<SiteInstance> a_isolated_instance = |
| shell()->web_contents()->GetPrimaryMainFrame()->GetSiteInstance(); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), b_isolated_url)); |
| int b_isolated_pid = |
| shell()->web_contents()->GetPrimaryMainFrame()->GetProcess()->GetID(); |
| scoped_refptr<SiteInstance> b_isolated_instance = |
| shell()->web_contents()->GetPrimaryMainFrame()->GetSiteInstance(); |
| |
| // Navigating from a non-wildcard domain to a wildcard domain should result in |
| // a new process. |
| EXPECT_NE(b_foo_pid, b_isolated_pid); |
| EXPECT_NE(b_foo_instance, b_isolated_instance); |
| |
| // Navigating to another URL within the wildcard domain should always result |
| // in a new process. |
| EXPECT_NE(a_isolated_pid, b_isolated_pid); |
| EXPECT_NE(a_isolated_instance, b_isolated_instance); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), a_foo_url)); |
| a_foo_pid = |
| shell()->web_contents()->GetPrimaryMainFrame()->GetProcess()->GetID(); |
| a_foo_instance = |
| shell()->web_contents()->GetPrimaryMainFrame()->GetSiteInstance(); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), b_foo_url)); |
| b_foo_pid = |
| shell()->web_contents()->GetPrimaryMainFrame()->GetProcess()->GetID(); |
| b_foo_instance = |
| shell()->web_contents()->GetPrimaryMainFrame()->GetSiteInstance(); |
| |
| // Navigating from the wildcard subdomain to the isolated subdomain should |
| // produce a new pid. |
| EXPECT_NE(a_foo_pid, b_isolated_pid); |
| EXPECT_NE(a_foo_instance, b_isolated_instance); |
| |
| // Confirm that navigation events in the isolated domain behave the same as |
| // before visiting the wildcard subdomain. |
| EXPECT_EQ(a_foo_pid, b_foo_pid); |
| if (CanSameSiteMainFrameNavigationsChangeSiteInstances()) { |
| EXPECT_NE(a_foo_instance, b_foo_instance); |
| } else { |
| EXPECT_EQ(a_foo_instance, b_foo_instance); |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WildcardOriginIsolationTest, SubFrameNavigation) { |
| GURL url = embedded_test_server()->GetURL( |
| "a.foo.com", |
| "/cross_site_iframe_factory.html?a.foo.com(" |
| "isolated.foo.com,b.foo.com(" |
| "b.isolated.foo.com,a.foo.com,a.isolated.com))"); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| |
| EXPECT_EQ( |
| " Site A ------------ proxies for B C D\n" |
| " |--Site B ------- proxies for A C D\n" |
| " +--Site A ------- proxies for B C D\n" |
| " |--Site C -- proxies for A B D\n" |
| " |--Site A -- proxies for B C D\n" |
| " +--Site D -- proxies for A B C\n" |
| "Where A = http://foo.com/\n" |
| " B = http://isolated.foo.com/\n" |
| " C = http://b.isolated.foo.com/\n" |
| " D = http://isolated.com/", |
| DepictFrameTree(*root)); |
| } |
| |
| // Helper class for testing site isolation triggered by |
| // Cross-Origin-Opener-Policy headers. These tests disable strict site |
| // isolation by default, so that we can check whether a site becomes isolated |
| // due to COOP on both desktop and Android. |
| class COOPIsolationTest : public IsolatedOriginTestBase { |
| public: |
| // Note: the COOP header is only populated for HTTPS. |
| COOPIsolationTest() : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) { |
| scoped_feature_list_.InitAndEnableFeature( |
| features::kSiteIsolationForCrossOriginOpenerPolicy); |
| } |
| |
| ~COOPIsolationTest() override = default; |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| IsolatedOriginTestBase::SetUpCommandLine(command_line); |
| ASSERT_TRUE(embedded_test_server()->InitializeAndListen()); |
| } |
| |
| void SetUpOnMainThread() override { |
| IsolatedOriginTestBase::SetUpOnMainThread(); |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| embedded_test_server()->StartAcceptingConnections(); |
| |
| https_server()->AddDefaultHandlers(GetTestDataFilePath()); |
| ASSERT_TRUE(https_server()->Start()); |
| |
| browser_client_ = std::make_unique<NoSiteIsolationContentBrowserClient>(); |
| |
| // The custom ContentBrowserClient above 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 with strict site isolation COOP |
| // doesn't need to dynamically isolate any sites. |
| 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 { |
| IsolatedOriginTestBase::TearDownOnMainThread(); |
| browser_client_.reset(); |
| } |
| |
| net::EmbeddedTestServer* https_server() { return &https_server_; } |
| |
| // A custom ContentBrowserClient to turn off strict site isolation, since |
| // COOP isolation only matters in environments like Android where it |
| // is not used. Note that kSitePerProcess is a higher-layer feature, so we |
| // can't just disable it here. |
| class NoSiteIsolationContentBrowserClient |
| : public ContentBrowserTestContentBrowserClient { |
| public: |
| bool ShouldEnableStrictSiteIsolation() override { return false; } |
| }; |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| |
| net::EmbeddedTestServer https_server_; |
| |
| std::unique_ptr<NoSiteIsolationContentBrowserClient> browser_client_; |
| }; |
| |
| // Check that a main frame navigation to a COOP site (with no subsequent user |
| // gesture) triggers isolation for that site within the current |
| // BrowsingInstance. |
| IN_PROC_BROWSER_TEST_F(COOPIsolationTest, SameOrigin) { |
| if (AreAllSitesIsolatedForTesting()) |
| return; |
| |
| GURL no_coop_url = https_server()->GetURL("a.com", "/title1.html"); |
| EXPECT_TRUE(NavigateToURL(shell(), no_coop_url)); |
| EXPECT_EQ( |
| web_contents()->GetPrimaryMainFrame()->cross_origin_opener_policy().value, |
| network::mojom::CrossOriginOpenerPolicyValue::kUnsafeNone); |
| scoped_refptr<SiteInstance> first_instance = |
| web_contents()->GetPrimaryMainFrame()->GetSiteInstance(); |
| EXPECT_FALSE(first_instance->RequiresDedicatedProcess()); |
| |
| // Navigate to a b.com URL with COOP, swapping BrowsingInstances. |
| GURL coop_url = https_server()->GetURL( |
| "b.com", "/set-header?Cross-Origin-Opener-Policy: same-origin"); |
| EXPECT_TRUE(NavigateToURL(shell(), coop_url)); |
| EXPECT_EQ( |
| web_contents()->GetPrimaryMainFrame()->cross_origin_opener_policy().value, |
| network::mojom::CrossOriginOpenerPolicyValue::kSameOrigin); |
| SiteInstanceImpl* coop_instance = |
| web_contents()->GetPrimaryMainFrame()->GetSiteInstance(); |
| // The b.com COOP page should trigger the isolation heuristic and require a |
| // dedicated process locked to b.com. |
| EXPECT_TRUE(coop_instance->RequiresDedicatedProcess()); |
| |
| auto lock = coop_instance->GetProcess()->GetProcessLock(); |
| EXPECT_TRUE(lock.is_locked_to_site()); |
| EXPECT_EQ(ProcessLockFromUrl("https://b.com"), lock); |
| |
| // Check that a cross-site subframe in a non-isolated site becomes an OOPIF |
| // in a new, non-isolated SiteInstance. |
| ASSERT_TRUE(ExecJs(shell(), |
| "var iframe = document.createElement('iframe');" |
| "iframe.id = 'child';" |
| "document.body.appendChild(iframe);", |
| EXECUTE_SCRIPT_NO_USER_GESTURE)); |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| FrameTreeNode* child = root->child_at(0); |
| GURL c_url(https_server()->GetURL("c.com", "/title1.html")); |
| EXPECT_TRUE(NavigateIframeToURL(web_contents(), "child", c_url)); |
| SiteInstanceImpl* child_instance = |
| child->current_frame_host()->GetSiteInstance(); |
| EXPECT_NE(coop_instance, child_instance); |
| EXPECT_NE(coop_instance->GetProcess(), child_instance->GetProcess()); |
| EXPECT_FALSE(child_instance->RequiresDedicatedProcess()); |
| |
| // Navigating the subframe back to b.com should bring it back to the parent |
| // SiteInstance. |
| GURL b_url(https_server()->GetURL("b.com", "/title1.html")); |
| EXPECT_TRUE(NavigateIframeToURL(web_contents(), "child", b_url)); |
| child_instance = child->current_frame_host()->GetSiteInstance(); |
| EXPECT_EQ(coop_instance, child_instance); |
| |
| // Create a new window, forcing a new BrowsingInstance, and check that b.com |
| // is *not* isolated in it. Since b.com in `coop_instance`'s |
| // BrowsingInstance hasn't received a user gesture, the COOP isolation does |
| // not apply to other BrowsingInstances. |
| Shell* new_shell = CreateBrowser(); |
| GURL no_coop_b_url = https_server()->GetURL("b.com", "/title2.html"); |
| EXPECT_TRUE(NavigateToURL(new_shell, no_coop_b_url)); |
| SiteInstanceImpl* new_instance = static_cast<SiteInstanceImpl*>( |
| new_shell->web_contents()->GetPrimaryMainFrame()->GetSiteInstance()); |
| EXPECT_FALSE(new_instance->RequiresDedicatedProcess()); |
| } |
| |
| // Verify that the same-origin-allow-popups COOP header value triggers |
| // isolation, and that this behaves sanely with window.open(). |
| IN_PROC_BROWSER_TEST_F(COOPIsolationTest, SameOriginAllowPopups) { |
| if (AreAllSitesIsolatedForTesting()) |
| return; |
| |
| // Navigate to a coop.com URL with COOP. |
| GURL coop_url = https_server()->GetURL( |
| "coop.com", |
| "/set-header?Cross-Origin-Opener-Policy: same-origin-allow-popups"); |
| EXPECT_TRUE(NavigateToURL(shell(), coop_url)); |
| EXPECT_EQ( |
| web_contents()->GetPrimaryMainFrame()->cross_origin_opener_policy().value, |
| network::mojom::CrossOriginOpenerPolicyValue::kSameOriginAllowPopups); |
| SiteInstanceImpl* coop_instance = |
| web_contents()->GetPrimaryMainFrame()->GetSiteInstance(); |
| // The coop.com COOP page should trigger the isolation heuristic and require |
| // a dedicated process locked to coop.com. |
| EXPECT_TRUE(coop_instance->RequiresDedicatedProcess()); |
| |
| auto lock = coop_instance->GetProcess()->GetProcessLock(); |
| EXPECT_TRUE(lock.is_locked_to_site()); |
| EXPECT_EQ(ProcessLockFromUrl("https://coop.com"), lock); |
| |
| // Open a non-COOP same-site URL in a popup, which should stay in the same |
| // BrowsingInstance because of same-origin-allow-popups. Verify that the |
| // popup ends up in the same SiteInstance as the opener (which requires a |
| // dedicated process). |
| GURL popup_url(https_server()->GetURL("coop.com", "/title1.html")); |
| Shell* popup = OpenPopup(shell(), popup_url, ""); |
| RenderFrameHostImpl* popup_rfh = static_cast<RenderFrameHostImpl*>( |
| popup->web_contents()->GetPrimaryMainFrame()); |
| EXPECT_EQ(popup_rfh->cross_origin_opener_policy().value, |
| network::mojom::CrossOriginOpenerPolicyValue::kUnsafeNone); |
| EXPECT_EQ(popup_rfh->GetSiteInstance(), coop_instance); |
| |
| // Navigate the popup to another non-isolated site, staying in the same |
| // BrowsingInstance, and verify that it swaps to a new non-isolated |
| // SiteInstance. The non-isolated site has a child which is same-origin with |
| // the COOP page; verify that it's placed in the same SiteInstance as the |
| // COOP page, as they are allowed to synchronously script each other. |
| GURL a_url(https_server()->GetURL( |
| "a.com", "/cross_site_iframe_factory.html?a.com(coop.com)")); |
| EXPECT_TRUE(NavigateToURLFromRenderer(popup, a_url)); |
| SiteInstanceImpl* new_instance = static_cast<SiteInstanceImpl*>( |
| popup->web_contents()->GetPrimaryMainFrame()->GetSiteInstance()); |
| EXPECT_FALSE(new_instance->RequiresDedicatedProcess()); |
| EXPECT_NE(new_instance, coop_instance); |
| FrameTreeNode* popup_child = |
| static_cast<WebContentsImpl*>(popup->web_contents()) |
| ->GetPrimaryFrameTree() |
| .root() |
| ->child_at(0); |
| EXPECT_EQ(popup_child->current_frame_host()->GetSiteInstance(), |
| coop_instance); |
| |
| // Navigate the popup to coop.com again, staying in the same |
| // BrowsingInstance, and verify that it goes back to the opener's |
| // SiteInstance. |
| EXPECT_TRUE(NavigateToURLFromRenderer(popup, popup_url)); |
| EXPECT_EQ(popup->web_contents()->GetPrimaryMainFrame()->GetSiteInstance(), |
| coop_instance); |
| } |
| |
| // Verify that COOP isolation applies at a site (and not origin) granularity. |
| // |
| // Isolating sites rather than origins may seem counterintuitive, considering |
| // the COOP header value that triggers isolation is "same-origin". However, |
| // process isolation granularity that we can infer from COOP is quite different |
| // from what that actual COOP value controls. The COOP "same-origin" value |
| // specifies when to sever opener relationships and create a new |
| // BrowsingInstance; a COOP "same-origin" main frame document may only stay in |
| // the same BrowsingInstance as other same-origin COOP documents. However, |
| // this does not apply to iframes, and it's possible to have a |
| // foo.bar.coop.com(baz.coop.com) hierarchy where the main frame has COOP |
| // "same-origin" but both frames set document.domain to coop.com and |
| // synchronously script each other (*). Hence, in this case, we must isolate |
| // the coop.com site and place the two frames in the same process. This test |
| // covers that precise scenario. |
| // |
| // (*) In the future, COOP may disallow document.domain, in which case we may |
| // need to revisit this. See https://github.com/whatwg/html/issues/6177. |
| IN_PROC_BROWSER_TEST_F(COOPIsolationTest, SiteGranularity) { |
| if (AreAllSitesIsolatedForTesting()) |
| return; |
| |
| // Navigate to a URL with COOP, where the origin doesn't match the site. |
| GURL coop_url = https_server()->GetURL( |
| "foo.bar.coop.com", |
| "/set-header?Cross-Origin-Opener-Policy: same-origin"); |
| EXPECT_TRUE(NavigateToURL(shell(), coop_url)); |
| EXPECT_EQ( |
| web_contents()->GetPrimaryMainFrame()->cross_origin_opener_policy().value, |
| network::mojom::CrossOriginOpenerPolicyValue::kSameOrigin); |
| SiteInstanceImpl* coop_instance = |
| web_contents()->GetPrimaryMainFrame()->GetSiteInstance(); |
| EXPECT_TRUE(coop_instance->RequiresDedicatedProcess()); |
| |
| // Ensure that the process lock is for the site, not origin. |
| auto lock = coop_instance->GetProcess()->GetProcessLock(); |
| EXPECT_TRUE(lock.is_locked_to_site()); |
| EXPECT_EQ(ProcessLockFromUrl("https://coop.com"), lock); |
| |
| // Check that a same-site cross-origin subframe stays in the same |
| // SiteInstance and process. |
| ASSERT_TRUE(ExecJs(shell(), |
| "var iframe = document.createElement('iframe');" |
| "iframe.id = 'child';" |
| "document.body.appendChild(iframe);")); |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| FrameTreeNode* child = root->child_at(0); |
| GURL c_url(https_server()->GetURL("baz.coop.com", "/title1.html")); |
| EXPECT_TRUE(NavigateIframeToURL(web_contents(), "child", c_url)); |
| SiteInstanceImpl* child_instance = |
| child->current_frame_host()->GetSiteInstance(); |
| EXPECT_EQ(coop_instance, child_instance); |
| |
| // Check that ChildProcessSecurityPolicy considers coop.com (and not its |
| // subdomain) to be the matching isolated origin for `coop_url`. |
| url::Origin matching_isolated_origin; |
| ChildProcessSecurityPolicyImpl::GetInstance() |
| ->GetMatchingProcessIsolatedOrigin(coop_instance->GetIsolationContext(), |
| url::Origin::Create(GURL(coop_url)), |
| false /* origin_requests_isolation */, |
| &matching_isolated_origin); |
| EXPECT_EQ(matching_isolated_origin, |
| url::Origin::Create(GURL("https://coop.com"))); |
| } |
| |
| // Verify that COOP isolation applies when both COOP and COEP headers are set |
| // (i.e., for a cross-origin-isolated page). This results in a different COOP |
| // header value (kSameOriginPlusCoep) which should still trigger isolation. |
| IN_PROC_BROWSER_TEST_F(COOPIsolationTest, COOPAndCOEP) { |
| // Navigate to a URL with COOP + COEP. |
| GURL coop_url = https_server()->GetURL( |
| "coop.com", |
| "/set-header?Cross-Origin-Opener-Policy: same-origin&" |
| "Cross-Origin-Embedder-Policy: require-corp"); |
| EXPECT_TRUE(NavigateToURL(shell(), coop_url)); |
| EXPECT_EQ( |
| web_contents()->GetPrimaryMainFrame()->cross_origin_opener_policy().value, |
| network::mojom::CrossOriginOpenerPolicyValue::kSameOriginPlusCoep); |
| |
| // Make sure that site isolation for coop.com was triggered and that the |
| // navigation ended up in a site-locked process. |
| SiteInstanceImpl* coop_instance = |
| web_contents()->GetPrimaryMainFrame()->GetSiteInstance(); |
| EXPECT_TRUE(coop_instance->RequiresDedicatedProcess()); |
| auto lock = coop_instance->GetProcess()->GetProcessLock(); |
| EXPECT_TRUE(lock.GetWebExposedIsolationInfo().is_isolated()); |
| EXPECT_TRUE(lock.is_locked_to_site()); |
| EXPECT_TRUE( |
| lock.MatchesOrigin(url::Origin::Create(GURL("https://coop.com")))); |
| } |
| |
| // Check that when a site triggers both COOP isolation and OriginAgentCluster, |
| // both mechanisms take effect. This test uses a URL with default ports so |
| // that we can exercise the site URL being the same with both COOP and OAC. |
| IN_PROC_BROWSER_TEST_F(COOPIsolationTest, COOPAndOriginAgentClusterNoPorts) { |
| // Since the embedded test server only works for URLs with non-default ports, |
| // use a URLLoaderInterceptor to mimic port-free operation. This allows |
| // checking the site URL being identical for both COOP and OAC isolation, |
| // since otherwise OAC would include ports in the site URL. The interceptor |
| // below returns COOP and OAC headers for any page on foo.com, and returns a |
| // simple test page without any headers for a.foo.com and b.foo.com. |
| URLLoaderInterceptor interceptor(base::BindLambdaForTesting( |
| [&](URLLoaderInterceptor::RequestParams* params) { |
| if (params->url_request.url.host() == "foo.com") { |
| const std::string headers = |
| "HTTP/1.1 200 OK\n" |
| "Content-Type: text/html\n" |
| "Origin-Agent-Cluster: ?1\n" |
| "Cross-Origin-Opener-Policy: same-origin\n"; |
| URLLoaderInterceptor::WriteResponse( |
| "content/test/data" + params->url_request.url.path(), |
| params->client.get(), &headers, absl::optional<net::SSLInfo>()); |
| return true; |
| } else if (params->url_request.url.host() == "a.foo.com" || |
| params->url_request.url.host() == "b.foo.com") { |
| URLLoaderInterceptor::WriteResponse("content/test/data/title1.html", |
| params->client.get()); |
| return true; |
| } |
| // Not handled by us. |
| return false; |
| })); |
| |
| // Navigate to a URL with with COOP and OriginAgentCluster headers, embedding |
| // two iframes at a.foo.com and b.foo.com. |
| GURL coop_oac_url( |
| "https://foo.com/cross_site_iframe_factory.html?" |
| "foo.com(a.foo.com,b.foo.com)"); |
| EXPECT_TRUE(NavigateToURL(shell(), coop_oac_url)); |
| EXPECT_EQ( |
| web_contents()->GetPrimaryMainFrame()->cross_origin_opener_policy().value, |
| network::mojom::CrossOriginOpenerPolicyValue::kSameOrigin); |
| |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| FrameTreeNode* child1 = root->child_at(0); |
| FrameTreeNode* child2 = root->child_at(1); |
| |
| // The two subframes should end up in the same SiteInstance, different from |
| // the main frame's SiteInstance. Both SiteInstances should be in a process |
| // dedicated to foo.com, but the main frame's process should be for |
| // origin-keyed foo.com (strictly foo.com excluding subdomains) due to |
| // Origin-Agent-Cluster, whereas the subframe process should be for |
| // site-keyed foo.com. |
| SiteInstanceImpl* main_instance = |
| web_contents()->GetPrimaryMainFrame()->GetSiteInstance(); |
| SiteInstanceImpl* child_instance = |
| child1->current_frame_host()->GetSiteInstance(); |
| EXPECT_EQ(child_instance, child2->current_frame_host()->GetSiteInstance()); |
| EXPECT_NE(child_instance, main_instance); |
| |
| EXPECT_TRUE(main_instance->RequiresDedicatedProcess()); |
| EXPECT_TRUE(child_instance->RequiresDedicatedProcess()); |
| |
| EXPECT_TRUE(main_instance->GetSiteInfo().requires_origin_keyed_process()); |
| EXPECT_FALSE(child_instance->GetSiteInfo().requires_origin_keyed_process()); |
| EXPECT_EQ(main_instance->GetSiteInfo().site_url(), |
| child_instance->GetSiteInfo().site_url()); |
| EXPECT_EQ(main_instance->GetSiteInfo().process_lock_url(), |
| child_instance->GetSiteInfo().process_lock_url()); |
| |
| auto main_lock = main_instance->GetProcess()->GetProcessLock(); |
| auto child_lock = child_instance->GetProcess()->GetProcessLock(); |
| EXPECT_TRUE(main_lock.is_locked_to_site()); |
| EXPECT_TRUE(child_lock.is_locked_to_site()); |
| EXPECT_TRUE(main_lock.is_origin_keyed_process()); |
| EXPECT_FALSE(child_lock.is_origin_keyed_process()); |
| auto foo_origin = url::Origin::Create(GURL("https://foo.com")); |
| EXPECT_TRUE(main_lock.MatchesOrigin(foo_origin)); |
| EXPECT_TRUE(child_lock.MatchesOrigin(foo_origin)); |
| } |
| |
| // Check that when a site triggers both COOP isolation and OriginAgentCluster, |
| // both mechanisms take effect. Similar to the test above, but starts on a URL |
| // where the origin doesn't match the site. |
| IN_PROC_BROWSER_TEST_F(COOPIsolationTest, |
| COOPAndOriginAgentClusterOnSubdomain) { |
| // Navigate to a URL with with COOP and OriginAgentCluster headers. |
| GURL coop_oac_url = https_server()->GetURL( |
| "oac.coop.com", |
| "/set-header?Cross-Origin-Opener-Policy: same-origin&" |
| "Origin-Agent-Cluster: ?1"); |
| EXPECT_TRUE(NavigateToURL(shell(), coop_oac_url)); |
| EXPECT_EQ( |
| web_contents()->GetPrimaryMainFrame()->cross_origin_opener_policy().value, |
| network::mojom::CrossOriginOpenerPolicyValue::kSameOrigin); |
| |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| |
| // Add a subframe and navigate to foo.coop.com. |
| ASSERT_TRUE(ExecJs(shell(), |
| "var iframe = document.createElement('iframe');" |
| "iframe.id = 'child';" |
| "document.body.appendChild(iframe);")); |
| FrameTreeNode* child = root->child_at(0); |
| GURL child_url(https_server()->GetURL("foo.coop.com", "/title1.html")); |
| EXPECT_TRUE(NavigateIframeToURL(web_contents(), "child", child_url)); |
| |
| // The subframe should end up in a different SiteInstance from the main |
| // frame's SiteInstance. The main frame's SiteInstance should be in an |
| // origin-keyed process locked to oac.foo.com, whereas the child's |
| // SiteInstance should be in a site-keyed process locked to foo.com. |
| SiteInstanceImpl* main_instance = |
| web_contents()->GetPrimaryMainFrame()->GetSiteInstance(); |
| SiteInstanceImpl* child_instance = |
| child->current_frame_host()->GetSiteInstance(); |
| EXPECT_NE(child_instance, main_instance); |
| |
| EXPECT_TRUE(main_instance->RequiresDedicatedProcess()); |
| EXPECT_TRUE(child_instance->RequiresDedicatedProcess()); |
| |
| EXPECT_TRUE(main_instance->GetSiteInfo().requires_origin_keyed_process()); |
| EXPECT_FALSE(child_instance->GetSiteInfo().requires_origin_keyed_process()); |
| EXPECT_NE(main_instance->GetSiteInfo().site_url(), |
| child_instance->GetSiteInfo().site_url()); |
| EXPECT_NE(main_instance->GetSiteInfo().process_lock_url(), |
| child_instance->GetSiteInfo().process_lock_url()); |
| |
| auto main_lock = main_instance->GetProcess()->GetProcessLock(); |
| auto child_lock = child_instance->GetProcess()->GetProcessLock(); |
| EXPECT_TRUE(main_lock.is_locked_to_site()); |
| EXPECT_TRUE(child_lock.is_locked_to_site()); |
| EXPECT_TRUE(main_lock.is_origin_keyed_process()); |
| EXPECT_FALSE(child_lock.is_origin_keyed_process()); |
| auto oac_coop_origin = url::Origin::Create(coop_oac_url); |
| auto coop_origin = url::Origin::Create(GURL("https://coop.com")); |
| EXPECT_TRUE(main_lock.MatchesOrigin(oac_coop_origin)); |
| EXPECT_TRUE(child_lock.MatchesOrigin(coop_origin)); |
| } |
| |
| // Verify that if strict site isolation is in place, COOP isolation does not |
| // add redundant isolated origins to ChildProcessSecurityPolicy. |
| IN_PROC_BROWSER_TEST_F(COOPIsolationTest, SiteAlreadyRequiresDedicatedProcess) { |
| // Enable --site-per-process and navigate to a COOP-enabled document. |
| IsolateAllSitesForTesting(base::CommandLine::ForCurrentProcess()); |
| GURL coop_url = https_server()->GetURL( |
| "coop.com", "/set-header?Cross-Origin-Opener-Policy: same-origin"); |
| EXPECT_TRUE(NavigateToURL(shell(), coop_url)); |
| // Simulate user activation, which normally triggers COOP isolation for |
| // future BrowsingInstances. |
| EXPECT_TRUE(ExecJs(shell(), "// no-op")); |
| EXPECT_EQ( |
| web_contents()->GetPrimaryMainFrame()->cross_origin_opener_policy().value, |
| network::mojom::CrossOriginOpenerPolicyValue::kSameOrigin); |
| SiteInstanceImpl* coop_instance = |
| web_contents()->GetPrimaryMainFrame()->GetSiteInstance(); |
| |
| // The SiteInstance should require a dedicated process, but |
| // ChildProcessSecurityPolicy shouldn't have added an isolated origin |
| // for coop.com. |
| EXPECT_TRUE(coop_instance->RequiresDedicatedProcess()); |
| auto* policy = ChildProcessSecurityPolicyImpl::GetInstance(); |
| auto origins = policy->GetIsolatedOrigins( |
| ChildProcessSecurityPolicy::IsolatedOriginSource::WEB_TRIGGERED); |
| EXPECT_EQ(0U, origins.size()); |
| EXPECT_FALSE(policy->IsIsolatedOrigin(coop_instance->GetIsolationContext(), |
| url::Origin::Create(coop_url), |
| false /* origin_requests_isolation */)); |
| } |
| |
| // Verify that seeing a user activation on a COOP document triggers isolation |
| // of that document's site in future BrowsingInstances, but doesn't affect any |
| // existing BrowsingInstances. |
| IN_PROC_BROWSER_TEST_F(COOPIsolationTest, UserActivation) { |
| if (AreAllSitesIsolatedForTesting()) |
| return; |
| |
| GURL coop_url = https_server()->GetURL( |
| "b.com", "/set-header?Cross-Origin-Opener-Policy: same-origin"); |
| EXPECT_TRUE(NavigateToURL(shell(), coop_url)); |
| EXPECT_EQ( |
| web_contents()->GetPrimaryMainFrame()->cross_origin_opener_policy().value, |
| network::mojom::CrossOriginOpenerPolicyValue::kSameOrigin); |
| FrameTreeNode* coop_root = web_contents()->GetPrimaryFrameTree().root(); |
| SiteInstance* coop_instance = |
| web_contents()->GetPrimaryMainFrame()->GetSiteInstance(); |
| // The b.com COOP page should trigger the isolation heuristic and require a |
| // dedicated process locked to b.com. |
| EXPECT_TRUE(coop_instance->RequiresDedicatedProcess()); |
| |
| // At this point, the COOP page shouldn't have user activation. |
| EXPECT_FALSE(coop_root->HasTransientUserActivation()); |
| |
| // Create a new window, forcing a new BrowsingInstance, and check that b.com |
| // is *not* isolated in it. Since b.com in `coop_instance`'s |
| // BrowsingInstance hasn't been interacted with, the COOP isolation does not |
| // apply to other BrowsingInstances yet. |
| Shell* shell2 = CreateBrowser(); |
| GURL no_coop_b_url = https_server()->GetURL("b.com", "/title2.html"); |
| EXPECT_TRUE(NavigateToURL(shell2, no_coop_b_url)); |
| FrameTreeNode* shell2_root = |
| static_cast<WebContentsImpl*>(shell2->web_contents()) |
| ->GetPrimaryFrameTree() |
| .root(); |
| scoped_refptr<SiteInstance> instance2 = |
| shell2->web_contents()->GetPrimaryMainFrame()->GetSiteInstance(); |
| EXPECT_FALSE(instance2->RequiresDedicatedProcess()); |
| |
| // Simulate a user activation in the original COOP page by running a dummy |
| // script (ExecuteScript sends user activation by default). |
| EXPECT_TRUE(ExecJs(coop_root, "// no-op")); |
| EXPECT_TRUE(coop_root->HasTransientUserActivation()); |
| |
| // Create a third window in a new BrowsingInstance and navigate it to a |
| // non-COOP b.com URL. The above user activation should've forced COOP |
| // isolation for b.com to apply to future BrowsingInstances, so check that |
| // this navigation ends up requiring a dedicated process. |
| Shell* shell3 = CreateBrowser(); |
| EXPECT_TRUE(NavigateToURL(shell3, no_coop_b_url)); |
| SiteInstance* instance3 = |
| shell3->web_contents()->GetPrimaryMainFrame()->GetSiteInstance(); |
| EXPECT_TRUE(instance3->RequiresDedicatedProcess()); |
| EXPECT_FALSE(instance2->IsRelatedSiteInstance(instance3)); |
| EXPECT_FALSE(coop_instance->IsRelatedSiteInstance(instance3)); |
| |
| // Ensure that the older BrowsingInstance in the second window wasn't |
| // affected by the new isolation. Adding a b.com subframe or popup should |
| // stay in the same SiteInstance. Navigating the popup out from and back to |
| // b.com should also end up on the same SiteInstance. |
| ASSERT_TRUE(ExecJs(shell2, |
| "var iframe = document.createElement('iframe');" |
| "iframe.id = 'child';" |
| "document.body.appendChild(iframe);", |
| EXECUTE_SCRIPT_NO_USER_GESTURE)); |
| FrameTreeNode* child = shell2_root->child_at(0); |
| GURL another_b_url(https_server()->GetURL("b.com", "/title3.html")); |
| EXPECT_TRUE( |
| NavigateIframeToURL(shell2->web_contents(), "child", another_b_url)); |
| SiteInstanceImpl* child_instance = |
| child->current_frame_host()->GetSiteInstance(); |
| EXPECT_EQ(child_instance, instance2); |
| |
| Shell* popup = OpenPopup(shell2, another_b_url, ""); |
| FrameTreeNode* popup_root = |
| static_cast<WebContentsImpl*>(popup->web_contents()) |
| ->GetPrimaryFrameTree() |
| .root(); |
| EXPECT_EQ(popup_root->current_frame_host()->GetSiteInstance(), instance2); |
| |
| EXPECT_TRUE(NavigateToURLFromRenderer( |
| popup, https_server()->GetURL("c.com", "/title1.html"))); |
| EXPECT_TRUE(NavigateToURLFromRenderer(popup, another_b_url)); |
| EXPECT_EQ(popup_root->current_frame_host()->GetSiteInstance(), instance2); |
| |
| // Close the popup. |
| popup->Close(); |
| |
| // Without any related windows, navigating to b.com in the second window's |
| // main frame should trigger a proactive BrowsingInstance swap (see |
| // ShouldSwapBrowsingInstancesForDynamicIsolation()), since we notice that |
| // b.com would be isolated in a fresh BrowsingInstance, and nothing prevents |
| // the BrowsingInstance swap. Hence, in that case, the navigation should be |
| // in a new BrowsingInstance and in an isolated process. |
| EXPECT_TRUE(NavigateToURLFromRenderer( |
| shell2, https_server()->GetURL("b.com", "/title3.html"))); |
| scoped_refptr<SiteInstance> instance2_new = |
| shell2->web_contents()->GetPrimaryMainFrame()->GetSiteInstance(); |
| EXPECT_TRUE(instance2_new->RequiresDedicatedProcess()); |
| EXPECT_NE(instance2_new, instance2); |
| EXPECT_FALSE(instance2_new->IsRelatedSiteInstance(instance2.get())); |
| } |
| |
| // Similar to the test above, but verify that a user activation on a same-site |
| // subframe also triggers isolation of a COOP site in the main frame for future |
| // BrowsingInstances. |
| IN_PROC_BROWSER_TEST_F(COOPIsolationTest, UserActivationInSubframe) { |
| if (AreAllSitesIsolatedForTesting()) |
| return; |
| |
| GURL coop_url = https_server()->GetURL( |
| "b.com", "/set-header?Cross-Origin-Opener-Policy: same-origin"); |
| EXPECT_TRUE(NavigateToURL(shell(), coop_url)); |
| EXPECT_EQ( |
| web_contents()->GetPrimaryMainFrame()->cross_origin_opener_policy().value, |
| network::mojom::CrossOriginOpenerPolicyValue::kSameOrigin); |
| SiteInstance* coop_instance = |
| web_contents()->GetPrimaryMainFrame()->GetSiteInstance(); |
| EXPECT_TRUE(coop_instance->RequiresDedicatedProcess()); |
| |
| // Add a cross-site subframe. |
| ASSERT_TRUE(ExecJs(shell(), |
| "var iframe = document.createElement('iframe');" |
| "iframe.id = 'child';" |
| "document.body.appendChild(iframe);", |
| EXECUTE_SCRIPT_NO_USER_GESTURE)); |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| FrameTreeNode* child = root->child_at(0); |
| GURL c_url(https_server()->GetURL("c.com", "/title1.html")); |
| EXPECT_TRUE(NavigateIframeToURL(web_contents(), "child", c_url)); |
| |
| EXPECT_FALSE(root->HasTransientUserActivation()); |
| EXPECT_FALSE(child->HasTransientUserActivation()); |
| |
| // Simulate a user activation in the subframe by running a dummy script. |
| EXPECT_TRUE(ExecJs(child, "// no-op")); |
| EXPECT_TRUE(child->HasTransientUserActivation()); |
| |
| // Since the iframe is cross-origin, it shouldn't trigger isolation of b.com |
| // for future BrowsingInstances. |
| GURL no_coop_b_url = https_server()->GetURL("b.com", "/title2.html"); |
| { |
| Shell* new_shell = CreateBrowser(); |
| EXPECT_TRUE(NavigateToURL(new_shell, no_coop_b_url)); |
| scoped_refptr<SiteInstance> instance = |
| new_shell->web_contents()->GetPrimaryMainFrame()->GetSiteInstance(); |
| EXPECT_FALSE(instance->RequiresDedicatedProcess()); |
| } |
| |
| // Now, make the iframe same-origin and simulate a user gesture. |
| GURL b_url(https_server()->GetURL("b.com", "/title1.html")); |
| EXPECT_TRUE(NavigateIframeToURL(web_contents(), "child", b_url)); |
| |
| EXPECT_TRUE(ExecJs(child, "// no-op")); |
| |
| // Ensure that b.com is now isolated in a new tab and BrowsingInstance. |
| { |
| Shell* new_shell = CreateBrowser(); |
| EXPECT_TRUE(NavigateToURL(new_shell, no_coop_b_url)); |
| scoped_refptr<SiteInstance> instance = |
| new_shell->web_contents()->GetPrimaryMainFrame()->GetSiteInstance(); |
| EXPECT_TRUE(instance->RequiresDedicatedProcess()); |
| } |
| } |
| |
| // Similar to the test above, but verify that a user activation on a |
| // same-origin about:blank subframe triggers isolation of a COOP site in the |
| // main frame for future BrowsingInstances. |
| IN_PROC_BROWSER_TEST_F(COOPIsolationTest, UserActivationInAboutBlankSubframe) { |
| GURL coop_url = https_server()->GetURL( |
| "b.com", "/set-header?Cross-Origin-Opener-Policy: same-origin"); |
| EXPECT_TRUE(NavigateToURL(shell(), coop_url)); |
| EXPECT_EQ( |
| web_contents()->GetPrimaryMainFrame()->cross_origin_opener_policy().value, |
| network::mojom::CrossOriginOpenerPolicyValue::kSameOrigin); |
| SiteInstance* coop_instance = |
| web_contents()->GetPrimaryMainFrame()->GetSiteInstance(); |
| EXPECT_TRUE(coop_instance->RequiresDedicatedProcess()); |
| |
| // Add a cross-site blank subframe. |
| ASSERT_TRUE(ExecJs(shell(), |
| "var iframe = document.createElement('iframe');" |
| "iframe.id = 'child';" |
| "document.body.appendChild(iframe);", |
| EXECUTE_SCRIPT_NO_USER_GESTURE)); |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| FrameTreeNode* child = root->child_at(0); |
| |
| EXPECT_FALSE(root->HasTransientUserActivation()); |
| EXPECT_FALSE(child->HasTransientUserActivation()); |
| |
| // Simulate a user activation in the subframe by running a dummy script. |
| EXPECT_TRUE(ExecJs(child, "// no-op")); |
| EXPECT_TRUE(child->HasTransientUserActivation()); |
| |
| // Ensure that b.com is isolated in a new tab and BrowsingInstance. |
| { |
| Shell* new_shell = CreateBrowser(); |
| GURL no_coop_b_url = https_server()->GetURL("b.com", "/title2.html"); |
| EXPECT_TRUE(NavigateToURL(new_shell, no_coop_b_url)); |
| scoped_refptr<SiteInstance> instance = |
| new_shell->web_contents()->GetPrimaryMainFrame()->GetSiteInstance(); |
| EXPECT_TRUE(instance->RequiresDedicatedProcess()); |
| } |
| } |
| |
| // Ensure that navigating to http://localhost which has COOP+COEP headers, and |
| // hence will attempt to trigger COOP isolation, will not crash. See |
| // https://crbug.com/1276155. |
| IN_PROC_BROWSER_TEST_F(COOPIsolationTest, Localhost) { |
| // Navigate to a URL with COOP + COEP on http://localhost. |
| GURL coop_url = https_server()->GetURL( |
| "localhost", |
| "/set-header?Cross-Origin-Opener-Policy: same-origin&" |
| "Cross-Origin-Embedder-Policy: require-corp"); |
| EXPECT_TRUE(NavigateToURL(shell(), coop_url)); |
| EXPECT_EQ( |
| web_contents()->GetPrimaryMainFrame()->cross_origin_opener_policy().value, |
| network::mojom::CrossOriginOpenerPolicyValue::kSameOriginPlusCoep); |
| |
| // http://localhost isn't currently considered a valid isolated origin (since |
| // it won't work for subdomain matching), so the navigation should not |
| // trigger site isolation. Note, however, that the process lock should still |
| // reflect COOP+COEP isolation. |
| SiteInstanceImpl* coop_instance = |
| web_contents()->GetPrimaryMainFrame()->GetSiteInstance(); |
| EXPECT_FALSE(coop_instance->RequiresDedicatedProcess()); |
| auto lock = coop_instance->GetProcess()->GetProcessLock(); |
| EXPECT_TRUE(lock.GetWebExposedIsolationInfo().is_isolated()); |
| EXPECT_FALSE(lock.is_locked_to_site()); |
| } |
| |
| // Helper class for testing site isolation triggered by different JIT policies |
| // being applied. |
| class JITIsolationTest : public IsolatedOriginTest, |
| public ::testing::WithParamInterface<bool> { |
| public: |
| JITIsolationTest() = default; |
| |
| ~JITIsolationTest() override = default; |
| |
| // A custom ContentBrowserTestContentBrowserClient to selectively turn off JIT |
| // for certain sites. |
| class JitContentBrowserClient |
| : public ContentBrowserTestContentBrowserClient { |
| public: |
| JitContentBrowserClient(bool jit_disabled_default, |
| bool disable_site_isolation_entirely) |
| : is_jit_disabled_by_default_(jit_disabled_default), |
| is_site_isolation_disabled_entirely_( |
| disable_site_isolation_entirely) {} |
| |
| bool IsJitDisabledForSite(BrowserContext* browser_context, |
| const GURL& site_url) override { |
| if (site_url.is_empty()) { |
| return is_jit_disabled_by_default_; |
| } |
| if (site_url.DomainIs("jit-disabled.com")) { |
| return true; |
| } |
| if (site_url.DomainIs("jit-enabled.com")) { |
| return false; |
| } |
| return is_jit_disabled_by_default_; |
| } |
| |
| bool ShouldEnableStrictSiteIsolation() override { |
| return !is_site_isolation_disabled_entirely_; |
| } |
| |
| private: |
| bool is_jit_disabled_by_default_; |
| bool is_site_isolation_disabled_entirely_; |
| }; |
| }; |
| |
| IN_PROC_BROWSER_TEST_P(JITIsolationTest, MainFrameTest) { |
| bool jit_disabled_by_default = GetParam(); |
| JitContentBrowserClient policy(jit_disabled_by_default, |
| /* disable_site_isolation_entirely */ false); |
| |
| // Navigate to jit-disabled.com which should always have JIT disabled. |
| GURL disabled_url( |
| embedded_test_server()->GetURL("www.jit-disabled.com", "/title1.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), disabled_url)); |
| |
| EXPECT_TRUE(shell() |
| ->web_contents() |
| ->GetPrimaryMainFrame() |
| ->GetProcess() |
| ->IsJitDisabled()); |
| |
| // Navigate to jit-enabled.com which should always have JIT enabled. |
| GURL enabled_url( |
| embedded_test_server()->GetURL("www.jit-enabled.com", "/title1.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), enabled_url)); |
| |
| EXPECT_FALSE(shell() |
| ->web_contents() |
| ->GetPrimaryMainFrame() |
| ->GetProcess() |
| ->IsJitDisabled()); |
| |
| // Navigate to a site with no policy and it should match the default. |
| GURL default_url( |
| embedded_test_server()->GetURL("www.foo.com", "/title1.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), default_url)); |
| |
| EXPECT_EQ(jit_disabled_by_default, shell() |
| ->web_contents() |
| ->GetPrimaryMainFrame() |
| ->GetProcess() |
| ->IsJitDisabled()); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(JITIsolationTest, DefaultSiteTest) { |
| // Skip the test if --site-per-process is used on the command line, as the |
| // test needs to run without strict site isolation (see |
| // JitContentBrowserClient below). |
| if (AreAllSitesIsolatedForTesting()) |
| return; |
| |
| bool jit_disabled_by_default = GetParam(); |
| JitContentBrowserClient policy(jit_disabled_by_default, |
| /* disable_site_isolation_entirely */ true); |
| |
| // All three sites should have JIT enabled or disabled together, if site |
| // isolation is disabled, since they are all put into the default |
| // SiteInstance. |
| GURL disabled_url( |
| embedded_test_server()->GetURL("www.jit-disabled.com", "/title1.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), disabled_url)); |
| |
| EXPECT_EQ(jit_disabled_by_default, shell() |
| ->web_contents() |
| ->GetPrimaryMainFrame() |
| ->GetProcess() |
| ->IsJitDisabled()); |
| |
| GURL enabled_url( |
| embedded_test_server()->GetURL("www.jit-enabled.com", "/title1.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), enabled_url)); |
| |
| EXPECT_EQ(jit_disabled_by_default, shell() |
| ->web_contents() |
| ->GetPrimaryMainFrame() |
| ->GetProcess() |
| ->IsJitDisabled()); |
| |
| GURL default_url( |
| embedded_test_server()->GetURL("www.foo.com", "/title1.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), default_url)); |
| |
| EXPECT_EQ(jit_disabled_by_default, shell() |
| ->web_contents() |
| ->GetPrimaryMainFrame() |
| ->GetProcess() |
| ->IsJitDisabled()); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(JITEnabledByDefault, |
| JITIsolationTest, |
| ::testing::Values(false)); |
| INSTANTIATE_TEST_SUITE_P(JITDisabledByDefault, |
| JITIsolationTest, |
| ::testing::Values(true)); |
| |
| IN_PROC_BROWSER_TEST_F(JITIsolationTest, SubFrameTest) { |
| // Set JIT to be enabled by default. |
| JitContentBrowserClient policy( |
| /* jit_disabled_default */ false, |
| /* disable_site_isolation_entirely */ false); |
| |
| GURL default_embeds_disabled(embedded_test_server()->GetURL( |
| "foo.com", "/cross_site_iframe_factory.html?foo.com(jit-disabled.com)")); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), default_embeds_disabled)); |
| EXPECT_EQ(2u, CollectAllRenderFrameHosts(shell()->web_contents()).size()); |
| |
| // Top frame 'foo.com' should have JIT enabled as that's the default. |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| EXPECT_FALSE(root->current_frame_host()->GetProcess()->IsJitDisabled()); |
| // The frame containing jit-disabled.com should have JIT disabled. |
| FrameTreeNode* child_frame_node = root->child_at(0); |
| EXPECT_TRUE( |
| child_frame_node->current_frame_host()->GetProcess()->IsJitDisabled()); |
| |
| // And the other way round, where jit-disabled.com embeds foo.com. |
| GURL disabled_embeds_default(embedded_test_server()->GetURL( |
| "jit-disabled.com", |
| "/cross_site_iframe_factory.html?jit-disabled.com(foo.com)")); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), disabled_embeds_default)); |
| EXPECT_EQ(2u, CollectAllRenderFrameHosts( |
| shell()->web_contents()->GetPrimaryMainFrame()) |
| .size()); |
| |
| // Top frame 'jit-disabled.com' should have JIT disabled. |
| root = web_contents()->GetPrimaryFrameTree().root(); |
| EXPECT_TRUE(root->current_frame_host()->GetProcess()->IsJitDisabled()); |
| // The frame containing foo.com should have JIT enabled as that's the default. |
| child_frame_node = root->child_at(0); |
| EXPECT_FALSE( |
| child_frame_node->current_frame_host()->GetProcess()->IsJitDisabled()); |
| } |
| |
| // Check that jitless subframes obey process reuse policies. |
| IN_PROC_BROWSER_TEST_F(JITIsolationTest, SubFrameProcessReuse) { |
| // Set JIT to be enabled by default. |
| JitContentBrowserClient policy( |
| /* jit_disabled_default */ false, |
| /* disable_site_isolation_entirely */ false); |
| |
| GURL default_embeds_disabled(embedded_test_server()->GetURL( |
| "foo.com", "/cross_site_iframe_factory.html?foo.com(jit-disabled.com)")); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), default_embeds_disabled)); |
| |
| // Top frame 'foo.com' should have JIT enabled as that's the default. |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| EXPECT_FALSE(root->current_frame_host()->GetProcess()->IsJitDisabled()); |
| // The frame containing jit-disabled.com should have JIT disabled. |
| FrameTreeNode* child = root->child_at(0); |
| EXPECT_TRUE(child->current_frame_host()->GetProcess()->IsJitDisabled()); |
| |
| // Create a new window, unrelated to the current one, and set up the same |
| // frame hierarchy. |
| Shell* new_shell = CreateBrowser(); |
| EXPECT_TRUE(NavigateToURL(new_shell, default_embeds_disabled)); |
| |
| FrameTreeNode* new_root = |
| static_cast<WebContentsImpl*>(new_shell->web_contents()) |
| ->GetPrimaryFrameTree() |
| .root(); |
| EXPECT_FALSE(new_root->current_frame_host()->GetProcess()->IsJitDisabled()); |
| |
| FrameTreeNode* new_child = new_root->child_at(0); |
| EXPECT_TRUE(new_child->current_frame_host()->GetProcess()->IsJitDisabled()); |
| |
| // The subframes should be in separate BrowsingInstances, but because they |
| // have the same site, they should share 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()); |
| } |
| |
| } // namespace content |