|  | // Copyright 2019 The Chromium Authors | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "base/test/scoped_feature_list.h" | 
|  | #include "base/unguessable_token.h" | 
|  | #include "content/browser/renderer_host/frame_tree.h" | 
|  | #include "content/browser/web_contents/web_contents_impl.h" | 
|  | #include "content/common/content_navigation_policy.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/test_utils.h" | 
|  | #include "content/shell/browser/shell.h" | 
|  | #include "content/test/content_browser_test_utils_internal.h" | 
|  | #include "net/dns/mock_host_resolver.h" | 
|  | #include "net/test/embedded_test_server/embedded_test_server.h" | 
|  | #include "testing/gtest/include/gtest/gtest.h" | 
|  |  | 
|  | namespace content { | 
|  |  | 
|  | class EmbeddingTokenBrowserTest : public ContentBrowserTest { | 
|  | public: | 
|  | EmbeddingTokenBrowserTest() = default; | 
|  |  | 
|  | void SetUpCommandLine(base::CommandLine* command_line) override { | 
|  | scoped_feature_list_.InitWithFeaturesAndParameters( | 
|  | GetDefaultEnabledBackForwardCacheFeaturesForTesting( | 
|  | /*ignore_outstanding_network_request=*/false), | 
|  | GetDefaultDisabledBackForwardCacheFeaturesForTesting()); | 
|  |  | 
|  | ContentBrowserTest::SetUpCommandLine(command_line); | 
|  | IsolateAllSitesForTesting(command_line); | 
|  | } | 
|  |  | 
|  | void SetUpOnMainThread() override { | 
|  | host_resolver()->AddRule("*", "127.0.0.1"); | 
|  | SetupCrossSiteRedirector(embedded_test_server()); | 
|  | ASSERT_TRUE(embedded_test_server()->Start()); | 
|  | } | 
|  |  | 
|  | WebContents* web_contents() { return shell()->web_contents(); } | 
|  |  | 
|  | RenderFrameHostImpl* top_frame_host() { | 
|  | return static_cast<RenderFrameHostImpl*>( | 
|  | web_contents()->GetPrimaryMainFrame()); | 
|  | } | 
|  |  | 
|  | EmbeddingTokenBrowserTest(const EmbeddingTokenBrowserTest&) = delete; | 
|  | EmbeddingTokenBrowserTest& operator=(const EmbeddingTokenBrowserTest&) = | 
|  | delete; | 
|  |  | 
|  | private: | 
|  | base::test::ScopedFeatureList scoped_feature_list_; | 
|  | }; | 
|  |  | 
|  | IN_PROC_BROWSER_TEST_F(EmbeddingTokenBrowserTest, EmbeddingTokenOnMainFrame) { | 
|  | GURL a_url = embedded_test_server()->GetURL("a.com", "/site_isolation/"); | 
|  | GURL b_url = embedded_test_server()->GetURL("b.com", "/site_isolation/"); | 
|  | // Starts without an embedding token. | 
|  | EXPECT_FALSE(top_frame_host()->GetEmbeddingToken().has_value()); | 
|  |  | 
|  | // Embedding tokens should get added to the main frame. | 
|  | EXPECT_TRUE(NavigateToURL(shell(), a_url.Resolve("blank.html"))); | 
|  | EXPECT_TRUE(top_frame_host()->GetEmbeddingToken().has_value()); | 
|  | auto first_token = top_frame_host()->GetEmbeddingToken().value(); | 
|  |  | 
|  | EXPECT_TRUE(NavigateToURL(shell(), b_url.Resolve("blank.html"))); | 
|  | EXPECT_TRUE(top_frame_host()->GetEmbeddingToken().has_value()); | 
|  | EXPECT_NE(top_frame_host()->GetEmbeddingToken().value(), first_token); | 
|  | } | 
|  |  | 
|  | IN_PROC_BROWSER_TEST_F(EmbeddingTokenBrowserTest, | 
|  | EmbeddingTokensAddedToCrossDocumentIFrames) { | 
|  | EXPECT_TRUE(NavigateToURL( | 
|  | shell(), embedded_test_server()->GetURL( | 
|  | "a.com", "/cross_site_iframe_factory.html?a(b(a),c,a)"))); | 
|  |  | 
|  | ASSERT_EQ(3U, top_frame_host()->child_count()); | 
|  | EXPECT_TRUE(top_frame_host()->GetEmbeddingToken().has_value()); | 
|  | auto top_token = top_frame_host()->GetEmbeddingToken().value(); | 
|  |  | 
|  | // Child 0 (b) should have an embedding token. | 
|  | auto child_0_token = | 
|  | top_frame_host()->child_at(0)->current_frame_host()->GetEmbeddingToken(); | 
|  | ASSERT_TRUE(child_0_token.has_value()); | 
|  | EXPECT_NE(base::UnguessableToken::Null(), child_0_token); | 
|  | EXPECT_NE(top_token, child_0_token); | 
|  |  | 
|  | // Child 0 (a) of Child 0 (b) should have an embedding token. | 
|  | ASSERT_EQ(1U, top_frame_host()->child_at(0)->child_count()); | 
|  | auto child_0_0_token = top_frame_host() | 
|  | ->child_at(0) | 
|  | ->child_at(0) | 
|  | ->current_frame_host() | 
|  | ->GetEmbeddingToken(); | 
|  | ASSERT_TRUE(child_0_0_token.has_value()); | 
|  | EXPECT_NE(base::UnguessableToken::Null(), child_0_0_token); | 
|  | EXPECT_NE(top_token, child_0_0_token); | 
|  | EXPECT_NE(child_0_token, child_0_0_token); | 
|  |  | 
|  | // Child 1 (c) should have an embedding token. | 
|  | auto child_1_token = | 
|  | top_frame_host()->child_at(1)->current_frame_host()->GetEmbeddingToken(); | 
|  | ASSERT_TRUE(child_1_token.has_value()); | 
|  | EXPECT_NE(base::UnguessableToken::Null(), child_1_token); | 
|  | EXPECT_NE(top_token, child_1_token); | 
|  | EXPECT_NE(child_0_token, child_1_token); | 
|  | EXPECT_NE(child_0_0_token, child_1_token); | 
|  |  | 
|  | // Child 2 (a) should have an embedding token. | 
|  | auto child_2_token = | 
|  | top_frame_host()->child_at(2)->current_frame_host()->GetEmbeddingToken(); | 
|  | ASSERT_TRUE(child_2_token.has_value()); | 
|  | EXPECT_NE(base::UnguessableToken::Null(), child_2_token); | 
|  | EXPECT_NE(top_token, child_2_token); | 
|  | EXPECT_NE(child_0_token, child_2_token); | 
|  | EXPECT_NE(child_0_0_token, child_2_token); | 
|  |  | 
|  | // TODO(ckitagawa): Somehow assert that the parent and child have matching | 
|  | // embedding tokens in parent HTMLOwnerElement and child LocalFrame. | 
|  | } | 
|  |  | 
|  | IN_PROC_BROWSER_TEST_F(EmbeddingTokenBrowserTest, | 
|  | EmbeddingTokenSwapsOnCrossDocumentNavigation) { | 
|  | EXPECT_TRUE(NavigateToURL( | 
|  | shell(), embedded_test_server()->GetURL( | 
|  | "a.com", "/cross_site_iframe_factory.html?a(b)"))); | 
|  |  | 
|  | ASSERT_EQ(1U, top_frame_host()->child_count()); | 
|  | EXPECT_TRUE(top_frame_host()->GetEmbeddingToken().has_value()); | 
|  | auto top_token = top_frame_host()->GetEmbeddingToken().value(); | 
|  |  | 
|  | // Child 0 (b) should have an embedding token. | 
|  | RenderFrameHost* target = top_frame_host()->child_at(0)->current_frame_host(); | 
|  | auto child_0_token = target->GetEmbeddingToken(); | 
|  | ASSERT_TRUE(child_0_token.has_value()); | 
|  | EXPECT_NE(base::UnguessableToken::Null(), child_0_token); | 
|  | EXPECT_NE(top_token, child_0_token); | 
|  |  | 
|  | // Navigate child 0 (b) to same-site the token should swap. | 
|  | NavigateIframeToURL(shell()->web_contents(), "child-0", | 
|  | embedded_test_server() | 
|  | ->GetURL("b.com", "/site_isolation/") | 
|  | .Resolve("blank.html")); | 
|  | auto same_site_new_child_0_token = | 
|  | top_frame_host()->child_at(0)->current_frame_host()->GetEmbeddingToken(); | 
|  | ASSERT_TRUE(same_site_new_child_0_token.has_value()); | 
|  | EXPECT_NE(base::UnguessableToken::Null(), same_site_new_child_0_token); | 
|  | EXPECT_NE(top_token, same_site_new_child_0_token); | 
|  | EXPECT_NE(child_0_token, same_site_new_child_0_token); | 
|  |  | 
|  | // Navigate child 0 (b) to another site (cross-process) the token should swap. | 
|  | { | 
|  | if (ShouldCreateNewHostForSameSiteSubframe()) { | 
|  | // The RenderFrameHost was been replaced when the frame navigated. | 
|  | target = top_frame_host()->child_at(0)->current_frame_host(); | 
|  | } | 
|  | RenderFrameDeletedObserver deleted_observer(target); | 
|  | NavigateIframeToURL(shell()->web_contents(), "child-0", | 
|  | embedded_test_server() | 
|  | ->GetURL("c.com", "/site_isolation/") | 
|  | .Resolve("blank.html")); | 
|  | deleted_observer.WaitUntilDeleted(); | 
|  | } | 
|  | auto new_site_child_0_token = | 
|  | top_frame_host()->child_at(0)->current_frame_host()->GetEmbeddingToken(); | 
|  | ASSERT_TRUE(same_site_new_child_0_token.has_value()); | 
|  | EXPECT_NE(base::UnguessableToken::Null(), new_site_child_0_token); | 
|  | EXPECT_NE(top_token, new_site_child_0_token); | 
|  | EXPECT_NE(child_0_token, new_site_child_0_token); | 
|  | EXPECT_NE(same_site_new_child_0_token, new_site_child_0_token); | 
|  |  | 
|  | // TODO(ckitagawa): Somehow assert that the parent and child have matching | 
|  | // embedding tokens in parent HTMLOwnerElement and child LocalFrame. | 
|  | } | 
|  |  | 
|  | IN_PROC_BROWSER_TEST_F( | 
|  | EmbeddingTokenBrowserTest, | 
|  | EmbeddingTokenNotChangedOnSubframeSameDocumentNavigation) { | 
|  | EXPECT_TRUE(NavigateToURL( | 
|  | shell(), embedded_test_server()->GetURL( | 
|  | "a.com", "/cross_site_iframe_factory.html?a(a)"))); | 
|  |  | 
|  | ASSERT_EQ(1U, top_frame_host()->child_count()); | 
|  | EXPECT_TRUE(top_frame_host()->GetEmbeddingToken().has_value()); | 
|  | auto top_token = top_frame_host()->GetEmbeddingToken().value(); | 
|  |  | 
|  | // Child 0 (a) should have an embedding token. | 
|  | RenderFrameHost* target = top_frame_host()->child_at(0)->current_frame_host(); | 
|  | auto child_0_token = target->GetEmbeddingToken(); | 
|  | ASSERT_TRUE(child_0_token.has_value()); | 
|  | EXPECT_NE(base::UnguessableToken::Null(), child_0_token); | 
|  | EXPECT_NE(top_token, child_0_token); | 
|  |  | 
|  | auto b_url = embedded_test_server()->GetURL("b.com", "/site_isolation/"); | 
|  | // Navigate child 0 to another site (cross-process) a token should be created. | 
|  | { | 
|  | RenderFrameDeletedObserver deleted_observer( | 
|  | top_frame_host()->child_at(0)->current_frame_host()); | 
|  | NavigateIframeToURL(web_contents(), "child-0", b_url.Resolve("blank.html")); | 
|  | deleted_observer.WaitUntilDeleted(); | 
|  | } | 
|  |  | 
|  | // Child 0 (b) should have a new embedding token. | 
|  | auto new_child_0_token = | 
|  | top_frame_host()->child_at(0)->current_frame_host()->GetEmbeddingToken(); | 
|  | ASSERT_TRUE(child_0_token.has_value()); | 
|  | EXPECT_NE(base::UnguessableToken::Null(), new_child_0_token); | 
|  | EXPECT_NE(top_token, new_child_0_token); | 
|  | EXPECT_NE(child_0_token, new_child_0_token); | 
|  |  | 
|  | // Navigate child 0 (b) to same document the token should not swap. | 
|  | NavigateIframeToURL(web_contents(), "child-0", | 
|  | b_url.Resolve("blank.html#foo")); | 
|  | auto same_document_new_child_0_token = | 
|  | top_frame_host()->child_at(0)->current_frame_host()->GetEmbeddingToken(); | 
|  | ASSERT_TRUE(same_document_new_child_0_token.has_value()); | 
|  | EXPECT_EQ(new_child_0_token, same_document_new_child_0_token); | 
|  |  | 
|  | // TODO(ckitagawa): Somehow assert that the parent and child have matching | 
|  | // embedding tokens in parent HTMLOwnerElement and child LocalFrame. | 
|  | } | 
|  |  | 
|  | IN_PROC_BROWSER_TEST_F(EmbeddingTokenBrowserTest, | 
|  | EmbeddingTokenChangedOnSubframeNavigationToNewDocument) { | 
|  | auto a_url = embedded_test_server()->GetURL("a.com", "/"); | 
|  | EXPECT_TRUE(NavigateToURL( | 
|  | shell(), a_url.Resolve("cross_site_iframe_factory.html?a(b)"))); | 
|  |  | 
|  | ASSERT_EQ(1U, top_frame_host()->child_count()); | 
|  | EXPECT_TRUE(top_frame_host()->GetEmbeddingToken().has_value()); | 
|  | auto top_token = top_frame_host()->GetEmbeddingToken().value(); | 
|  |  | 
|  | // Child 0 (b) should have an embedding token. | 
|  | RenderFrameHost* target = top_frame_host()->child_at(0)->current_frame_host(); | 
|  | auto child_0_token = target->GetEmbeddingToken(); | 
|  | ASSERT_TRUE(child_0_token.has_value()); | 
|  | EXPECT_NE(base::UnguessableToken::Null(), child_0_token); | 
|  | EXPECT_NE(top_token, child_0_token); | 
|  |  | 
|  | // Navigate child 0 (b) to the same site as the main frame. This should create | 
|  | // an embedding token. | 
|  | { | 
|  | RenderFrameDeletedObserver deleted_observer(target); | 
|  | NavigateIframeToURL(web_contents(), "child-0", | 
|  | a_url.Resolve("site_isolation/").Resolve("blank.html")); | 
|  | deleted_observer.WaitUntilDeleted(); | 
|  | } | 
|  |  | 
|  | auto new_child_0_token = | 
|  | top_frame_host()->child_at(0)->current_frame_host()->GetEmbeddingToken(); | 
|  | ASSERT_TRUE(new_child_0_token.has_value()); | 
|  | EXPECT_NE(base::UnguessableToken::Null(), new_child_0_token); | 
|  | EXPECT_NE(top_token, new_child_0_token); | 
|  | EXPECT_NE(child_0_token, new_child_0_token); | 
|  |  | 
|  | // TODO(ckitagawa): Somehow assert that the parent and child have matching | 
|  | // embedding tokens in parent HTMLOwnerElement and child LocalFrame. | 
|  | } | 
|  |  | 
|  | IN_PROC_BROWSER_TEST_F(EmbeddingTokenBrowserTest, | 
|  | BackForwardCacheCrossDocument) { | 
|  | auto a_url = embedded_test_server()->GetURL("a.com", "/site_isolation/"); | 
|  | auto b_url = embedded_test_server()->GetURL("b.com", "/site_isolation/"); | 
|  | EXPECT_TRUE(NavigateToURL(shell(), a_url.Resolve("blank.html"))); | 
|  |  | 
|  | EXPECT_TRUE(top_frame_host()->GetEmbeddingToken().has_value()); | 
|  | auto top_token_a = top_frame_host()->GetEmbeddingToken().value(); | 
|  |  | 
|  | EXPECT_TRUE(NavigateToURL(shell(), b_url.Resolve("blank.html"))); | 
|  | EXPECT_TRUE(top_frame_host()->GetEmbeddingToken().has_value()); | 
|  | auto top_token_b = top_frame_host()->GetEmbeddingToken().value(); | 
|  | EXPECT_NE(top_token_a, top_token_b); | 
|  |  | 
|  | // Navigate back to the first origin. The back forward cache should keep | 
|  | // the embedding token. | 
|  | web_contents()->GetController().GoBack(); | 
|  | EXPECT_TRUE(content::WaitForLoadStop(web_contents())); | 
|  |  | 
|  | EXPECT_TRUE(top_frame_host()->GetEmbeddingToken().has_value()); | 
|  | auto top_token_a_prime = top_frame_host()->GetEmbeddingToken().value(); | 
|  | EXPECT_EQ(top_token_a, top_token_a_prime); | 
|  | } | 
|  |  | 
|  | IN_PROC_BROWSER_TEST_F(EmbeddingTokenBrowserTest, | 
|  | BackForwardCacheCrossDocumentAfterSameDocument) { | 
|  | auto a_url = embedded_test_server()->GetURL("a.com", "/site_isolation/"); | 
|  | auto b_url = embedded_test_server()->GetURL("b.com", "/site_isolation/"); | 
|  | EXPECT_TRUE(NavigateToURL(shell(), a_url.Resolve("blank.html"))); | 
|  |  | 
|  | EXPECT_TRUE(top_frame_host()->GetEmbeddingToken().has_value()); | 
|  | auto top_token_a = top_frame_host()->GetEmbeddingToken().value(); | 
|  |  | 
|  | EXPECT_TRUE(NavigateToURL(shell(), a_url.Resolve("blank.html#foo"))); | 
|  | EXPECT_TRUE(top_frame_host()->GetEmbeddingToken().has_value()); | 
|  | EXPECT_EQ(top_frame_host()->GetEmbeddingToken().value(), top_token_a); | 
|  |  | 
|  | EXPECT_TRUE(NavigateToURL(shell(), b_url.Resolve("blank.html"))); | 
|  | EXPECT_TRUE(top_frame_host()->GetEmbeddingToken().has_value()); | 
|  | auto top_token_b = top_frame_host()->GetEmbeddingToken().value(); | 
|  | EXPECT_NE(top_token_a, top_token_b); | 
|  |  | 
|  | // Navigate back to the first origin. The back forward cache should keep | 
|  | // the embedding token even when the embedding token is not present in the | 
|  | // most recent navigation. | 
|  | web_contents()->GetController().GoBack(); | 
|  | EXPECT_TRUE(content::WaitForLoadStop(web_contents())); | 
|  |  | 
|  | EXPECT_TRUE(top_frame_host()->GetEmbeddingToken().has_value()); | 
|  | auto top_token_a_prime = top_frame_host()->GetEmbeddingToken().value(); | 
|  | EXPECT_EQ(top_token_a, top_token_a_prime); | 
|  | } | 
|  |  | 
|  | IN_PROC_BROWSER_TEST_F(EmbeddingTokenBrowserTest, | 
|  | SameDocumentHistoryPreservesTokens) { | 
|  | auto a_url = embedded_test_server()->GetURL("a.com", "/site_isolation/"); | 
|  | EXPECT_TRUE(NavigateToURL(shell(), a_url.Resolve("blank.html"))); | 
|  |  | 
|  | EXPECT_TRUE(top_frame_host()->GetEmbeddingToken().has_value()); | 
|  | auto top_token_a = top_frame_host()->GetEmbeddingToken().value(); | 
|  |  | 
|  | EXPECT_TRUE(NavigateToURL(shell(), a_url.Resolve("blank.html#foo"))); | 
|  | EXPECT_TRUE(top_frame_host()->GetEmbeddingToken().has_value()); | 
|  | auto top_token_a_prime = top_frame_host()->GetEmbeddingToken().value(); | 
|  | EXPECT_EQ(top_token_a, top_token_a_prime); | 
|  |  | 
|  | // Navigate back to before the fragment was added. This should preserve the | 
|  | // embedding token. | 
|  | web_contents()->GetController().GoBack(); | 
|  | EXPECT_TRUE(content::WaitForLoadStop(web_contents())); | 
|  |  | 
|  | EXPECT_TRUE(top_frame_host()->GetEmbeddingToken().has_value()); | 
|  | auto top_token_a_prime_prime = top_frame_host()->GetEmbeddingToken().value(); | 
|  | EXPECT_EQ(top_token_a, top_token_a_prime_prime); | 
|  | } | 
|  |  | 
|  | }  // namespace content |