blob: 2a67df36c707633b1031ac790a0d92c5f39a06b6 [file] [log] [blame]
// 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());
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.
{
// The RenderFrameHost might have 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