| // Copyright 2019 The Chromium Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include "base/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/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( | 
 |         {{features::kBackForwardCache, | 
 |           // Set a very long TTL before expiration (longer than the test | 
 |           // timeout) so tests that are expecting deletion don't pass when | 
 |           // they shouldn't. | 
 |           {{"TimeToLiveInBackForwardCacheInSeconds", "3600"}}}}, | 
 |         // Allow BackForwardCache for all devices regardless of their memory. | 
 |         {features::kBackForwardCacheMemoryControls}); | 
 |  | 
 |     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 |