blob: abaf49cc17a22cb041c6dd29f0eb7e64a2bf17f8 [file] [log] [blame]
// 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 <memory>
#include "base/containers/contains.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/memory/raw_ptr.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 "base/test/test_future.h"
#include "components/fingerprinting_protection_filter/interventions/common/interventions_features.h"
#include "components/ukm/test_ukm_recorder.h"
#include "content/browser/back_forward_cache_test_util.h"
#include "content/browser/browsing_data/shared_storage_clear_site_data_tester.h"
#include "content/browser/fingerprinting_protection/canvas_noise_token_data.h"
#include "content/browser/preloading/prefetch/prefetch_document_manager.h"
#include "content/browser/preloading/prefetch/prefetch_features.h"
#include "content/browser/preloading/prefetch/prefetch_status.h"
#include "content/browser/preloading/prerender/prerender_final_status.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/public/browser/back_forward_cache.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browsing_data_filter_builder.h"
#include "content/public/browser/browsing_data_remover.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_features.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/browsing_data_remover_test_util.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/navigation_simulator.h"
#include "content/public/test/prefetch_test_util.h"
#include "content/public/test/prerender_test_util.h"
#include "content/public/test/simple_url_loader_test_helper.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/shell/browser/shell.h"
#include "content/shell/browser/shell_content_browser_client.h"
#include "net/base/features.h"
#include "net/base/net_errors.h"
#include "net/cookies/cookie_base.h"
#include "net/cookies/cookie_partition_key.h"
#include "net/cookies/cookie_partition_key_collection.h"
#include "net/cookies/cookie_util.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 "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "services/network/public/mojom/network_context.mojom.h"
#include "services/network/public/mojom/network_service.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features.h"
#include "url/gurl.h"
namespace {
const char kHstsPath[] = "/hsts";
const char kHttpAuthPath[] = "/http_auth";
const char kHstsResponseBody[] = "HSTS set";
// Use a.test because 127.0.0.1/localhost cannot use HSTS.
const char kHstsHostname[] = "a.test";
std::unique_ptr<net::test_server::HttpResponse> HandleHstsRequest(
const net::test_server::HttpRequest& request) {
if (request.relative_url == kHstsPath) {
std::unique_ptr<net::test_server::BasicHttpResponse> hsts_response =
std::make_unique<net::test_server::BasicHttpResponse>();
hsts_response->AddCustomHeader("Strict-Transport-Security",
"max-age=1000000");
hsts_response->set_content(kHstsResponseBody);
return hsts_response;
}
return nullptr;
}
// Handles |request| to "/http_auth". If "Authorization" header is present,
// responds with a non-empty HTTP 200 page (regardless of auth credentials).
// Otherwise serves a Basic Auth challenge.
std::unique_ptr<net::test_server::HttpResponse> HandleHttpAuthRequest(
const net::test_server::HttpRequest& request) {
if (request.relative_url != kHttpAuthPath) {
return nullptr;
}
auto http_response = std::make_unique<net::test_server::BasicHttpResponse>();
if (base::Contains(request.headers, "Authorization")) {
http_response->set_code(net::HTTP_OK);
http_response->set_content("Success!");
} else {
http_response->set_code(net::HTTP_UNAUTHORIZED);
http_response->AddCustomHeader("WWW-Authenticate",
"Basic realm=\"test realm\"");
}
return http_response;
}
} // namespace
namespace content {
class BrowsingDataRemoverImplBrowserTest
: public ContentBrowserTest,
public BackForwardCacheMetricsTestMatcher {
public:
BrowsingDataRemoverImplBrowserTest()
: ssl_server_(net::test_server::EmbeddedTestServer::TYPE_HTTPS) {
// HSTS tests must run on non-localhost, while other tests use localhost.
ssl_server_.SetCertHostnames({kHstsHostname, "localhost"});
ssl_server_.AddDefaultHandlers(GetTestDataFilePath());
ssl_server_.RegisterRequestHandler(base::BindRepeating(&HandleHstsRequest));
ssl_server_.RegisterRequestHandler(
base::BindRepeating(&HandleHttpAuthRequest));
EXPECT_TRUE(ssl_server_.Start());
}
void SetUpOnMainThread() override {
ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>();
histogram_tester_ = std::make_unique<base::HistogramTester>();
host_resolver()->AddRule(kHstsHostname, "127.0.0.1");
}
void RemoveAndWait(uint64_t remove_mask) {
content::BrowsingDataRemover* remover =
shell()->web_contents()->GetBrowserContext()->GetBrowsingDataRemover();
content::BrowsingDataRemoverCompletionObserver completion_observer(remover);
remover->RemoveAndReply(
base::Time(), base::Time::Max(), remove_mask,
content::BrowsingDataRemover::ORIGIN_TYPE_UNPROTECTED_WEB,
&completion_observer);
completion_observer.BlockUntilCompletion();
}
void RemoveWithFilterAndWait(
uint64_t remove_mask,
std::unique_ptr<BrowsingDataFilterBuilder> filter) {
content::BrowsingDataRemover* remover =
shell()->web_contents()->GetBrowserContext()->GetBrowsingDataRemover();
content::BrowsingDataRemoverCompletionObserver completion_observer(remover);
remover->RemoveWithFilterAndReply(
base::Time(), base::Time::Max(), remove_mask,
content::BrowsingDataRemover::ORIGIN_TYPE_UNPROTECTED_WEB,
std::move(filter), &completion_observer);
completion_observer.BlockUntilCompletion();
}
// Issues a request for kHstsPath on localhost, and expects it to enable HSTS
// for the domain.
void IssueRequestThatSetsHsts() {
std::unique_ptr<network::ResourceRequest> request =
std::make_unique<network::ResourceRequest>();
request->url = ssl_server_.GetURL(kHstsHostname, kHstsPath);
SimpleURLLoaderTestHelper loader_helper;
std::unique_ptr<network::SimpleURLLoader> loader =
network::SimpleURLLoader::Create(std::move(request),
TRAFFIC_ANNOTATION_FOR_TESTS);
loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
url_loader_factory(), loader_helper.GetCallbackDeprecated());
loader_helper.WaitForCallback();
ASSERT_TRUE(loader_helper.response_body());
EXPECT_EQ(kHstsResponseBody, *loader_helper.response_body());
EXPECT_TRUE(IsHstsSet());
}
// Returns true if HSTS is set on localhost. Does this by issuing a main
// frame HTTP request to the embedded test server, and expecting it to be
// redirected from HTTP to HTTPS if HSTS is enabled. If the request succeeds,
// it was sent over HTTPS, so HSTS is enabled. If it fails, the request was
// send using HTTP instead, so HSTS is not enabled for the domain.
//
// That the request be main frame is necessary when
// kHstsTopLevelNavigationsOnly is enabled.
bool IsHstsSet() {
GURL url = ssl_server_.GetURL(kHstsHostname, "/echo");
GURL::Replacements replacements;
replacements.SetSchemeStr("http");
url = url.ReplaceComponents(replacements);
url::Origin origin = url::Origin::Create(url);
std::unique_ptr<network::ResourceRequest> request =
std::make_unique<network::ResourceRequest>();
request->url = url;
request->site_for_cookies = net::SiteForCookies::FromOrigin(origin);
request->update_first_party_url_on_redirect = true;
request->trusted_params.emplace();
request->trusted_params->isolation_info = net::IsolationInfo::Create(
net::IsolationInfo::RequestType::kMainFrame, origin, origin,
net::SiteForCookies::FromOrigin(origin));
std::unique_ptr<network::SimpleURLLoader> loader =
network::SimpleURLLoader::Create(std::move(request),
TRAFFIC_ANNOTATION_FOR_TESTS);
SimpleURLLoaderTestHelper loader_helper;
loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
url_loader_factory(), loader_helper.GetCallbackDeprecated());
loader_helper.WaitForCallback();
// On success, HSTS was enabled for the domain.
if (loader_helper.response_body()) {
EXPECT_EQ("Echo", *loader_helper.response_body());
return true;
}
// On failure, the server just hangs up, since it didn't receive an SSL
// handshake.
EXPECT_EQ(net::ERR_EMPTY_RESPONSE, loader->NetError());
return false;
}
// Sets HTTP auth cache by making a request with credentials specified in the
// URL to a page with an auth challenge.
void IssueRequestThatSetsHttpAuthCache() {
GURL url = ssl_server_.GetURL(kHttpAuthPath);
GURL::Replacements replacements;
replacements.SetUsernameStr("user");
replacements.SetPasswordStr("password");
GURL url_with_creds = url.ReplaceComponents(replacements);
ASSERT_TRUE(NavigateToURL(shell(), url_with_creds));
ASSERT_TRUE(IsHttpAuthCacheSet());
}
// Determines if auth cache is populated by seeing if a request to a page with
// an auth challenge succeeds.
bool IsHttpAuthCacheSet() {
// Set a login request callback to be used instead of a login dialog since
// such a dialog is difficult to control programmatically and doesn't work
// on all platforms.
bool login_requested = false;
ShellContentBrowserClient::Get()->set_login_request_callback(
base::BindLambdaForTesting(
[&](bool is_primary_main_frame_navigation /* unused */,
bool is_navigation /* unused */) { login_requested = true; }));
GURL url = ssl_server_.GetURL(kHttpAuthPath);
bool navigation_suceeded = NavigateToURL(shell(), url);
// Because our login request callback does nothing, navigation should
// succeed iff login is not needed unless some other unexpected error
// occurs.
EXPECT_NE(navigation_suceeded, login_requested);
return !login_requested && navigation_suceeded;
}
network::mojom::URLLoaderFactory* url_loader_factory() {
return shell()
->web_contents()
->GetBrowserContext()
->GetDefaultStoragePartition()
->GetURLLoaderFactoryForBrowserProcess()
.get();
}
network::mojom::NetworkContext* network_context() {
return shell()
->web_contents()
->GetBrowserContext()
->GetDefaultStoragePartition()
->GetNetworkContext();
}
const ukm::TestAutoSetUkmRecorder& ukm_recorder() override {
return *ukm_recorder_;
}
const base::HistogramTester& histogram_tester() override {
return *histogram_tester_;
}
const net::test_server::EmbeddedTestServer& ssl_server() {
return ssl_server_;
}
private:
net::test_server::EmbeddedTestServer ssl_server_;
std::unique_ptr<ukm::TestAutoSetUkmRecorder> ukm_recorder_;
std::unique_ptr<base::HistogramTester> histogram_tester_;
};
// Verify that TransportSecurityState data is cleared for REMOVE_CACHE.
IN_PROC_BROWSER_TEST_F(BrowsingDataRemoverImplBrowserTest,
ClearTransportSecurityState) {
IssueRequestThatSetsHsts();
RemoveAndWait(BrowsingDataRemover::DATA_TYPE_CACHE);
EXPECT_FALSE(IsHstsSet());
}
// Verify that TransportSecurityState data is not cleared if REMOVE_CACHE is not
// set or there is a deletelist filter.
// TODO(crbug.com/40667157): Add support for filtered deletions and update test.
IN_PROC_BROWSER_TEST_F(BrowsingDataRemoverImplBrowserTest,
PreserveTransportSecurityState) {
IssueRequestThatSetsHsts();
RemoveAndWait(BrowsingDataRemover::DATA_TYPE_DOWNLOADS);
EXPECT_TRUE(IsHstsSet());
auto filter = BrowsingDataFilterBuilder::Create(
BrowsingDataFilterBuilder::Mode::kDelete);
filter->AddRegisterableDomain("foobar.com");
RemoveWithFilterAndWait(BrowsingDataRemover::DATA_TYPE_CACHE,
std::move(filter));
EXPECT_TRUE(IsHstsSet());
}
IN_PROC_BROWSER_TEST_F(BrowsingDataRemoverImplBrowserTest, ClearHttpAuthCache) {
ASSERT_FALSE(IsHttpAuthCacheSet());
IssueRequestThatSetsHttpAuthCache();
RemoveAndWait(BrowsingDataRemover::DATA_TYPE_COOKIES);
EXPECT_FALSE(IsHttpAuthCacheSet());
}
IN_PROC_BROWSER_TEST_F(BrowsingDataRemoverImplBrowserTest,
PreserveHttpAuthCache) {
ASSERT_FALSE(IsHttpAuthCacheSet());
IssueRequestThatSetsHttpAuthCache();
RemoveAndWait(BrowsingDataRemover::DATA_TYPE_DOWNLOADS);
EXPECT_TRUE(IsHttpAuthCacheSet());
}
IN_PROC_BROWSER_TEST_F(BrowsingDataRemoverImplBrowserTest,
ClearHttpAuthCacheWhenEmpty) {
ASSERT_FALSE(IsHttpAuthCacheSet());
RemoveAndWait(BrowsingDataRemover::DATA_TYPE_COOKIES);
EXPECT_FALSE(IsHttpAuthCacheSet());
}
IN_PROC_BROWSER_TEST_F(BrowsingDataRemoverImplBrowserTest,
ClearBackForwardCacheEntries) {
if (!content::BackForwardCache::IsBackForwardCacheFeatureEnabled()) {
return;
}
GURL url_1 = ssl_server().GetURL("/title1.html");
GURL url_2 = ssl_server().GetURL("/title2.html");
// 1) Navigate to url_1, then to url_2.
ASSERT_TRUE(NavigateToURL(shell(), url_1));
ASSERT_TRUE(NavigateToURL(shell(), url_2));
// 2) Go back, the page should be restored from BFCache.
ASSERT_TRUE(HistoryGoBack(shell()->web_contents()));
ExpectRestored(FROM_HERE);
// 3) Navigate to url_2 again.
ASSERT_TRUE(NavigateToURL(shell(), url_2));
// 4) Remove the browsing data with DATA_TYPE_CACHE and go back, the page
// should not be restored from BFCache since the BFCache entry should be
// flushed.
RemoveAndWait(BrowsingDataRemover::DATA_TYPE_CACHE);
ASSERT_TRUE(HistoryGoBack(shell()->web_contents()));
ExpectNotRestored({BackForwardCacheMetrics::NotRestoredReason::kCacheFlushed},
{}, {}, {}, {}, FROM_HERE);
}
IN_PROC_BROWSER_TEST_F(BrowsingDataRemoverImplBrowserTest,
ClearBackForwardCacheEntriesWithOrigin_DataTypeCache) {
if (!content::BackForwardCache::IsBackForwardCacheFeatureEnabled()) {
return;
}
GURL url_1 = ssl_server().GetURL("/title1.html");
GURL url_2 = ssl_server().GetURL("/title2.html");
// 1) Navigate to url_1, then to url_2.
ASSERT_TRUE(NavigateToURL(shell(), url_1));
ASSERT_TRUE(NavigateToURL(shell(), url_2));
// 2) Go back, the page should be restored from BFCache.
ASSERT_TRUE(HistoryGoBack(shell()->web_contents()));
ExpectRestored(FROM_HERE);
// 3) Navigate to url_2 again.
ASSERT_TRUE(NavigateToURL(shell(), url_2));
// 4) Remove the browsing data with DATA_TYPE_CACHE and some random domain
// that doesn't match the BFCached document's origin.
auto filter = BrowsingDataFilterBuilder::Create(
BrowsingDataFilterBuilder::Mode::kDelete);
filter->AddRegisterableDomain("foobar.com");
RemoveWithFilterAndWait(BrowsingDataRemover::DATA_TYPE_CACHE,
std::move(filter));
// 5) Go back, the page should be restored from BFCache.
ASSERT_TRUE(HistoryGoBack(shell()->web_contents()));
ExpectRestored(FROM_HERE);
// 6) Navigate to url_2 again.
ASSERT_TRUE(NavigateToURL(shell(), url_2));
// 7) Remove the browsing data with DATA_TYPE_CACHE and the domain that
// matches the BFCached document's origin.
filter = BrowsingDataFilterBuilder::Create(
BrowsingDataFilterBuilder::Mode::kDelete);
filter->AddRegisterableDomain(ssl_server().base_url().host());
RemoveWithFilterAndWait(BrowsingDataRemover::DATA_TYPE_CACHE,
std::move(filter));
// 8) Go back, and the page should not be restored from BFCache since the
// BFCache entry should be flushed.
ASSERT_TRUE(HistoryGoBack(shell()->web_contents()));
ExpectNotRestored({BackForwardCacheMetrics::NotRestoredReason::kCacheFlushed},
{}, {}, {}, {}, FROM_HERE);
}
class BrowsingDataRemoverImplForCacheControlNoStorePageBrowserTest
: public BrowsingDataRemoverImplBrowserTest {
protected:
void SetUpCommandLine(base::CommandLine* command_line) override {
feature_list_.InitAndEnableFeatureWithParameters(
features::kCacheControlNoStoreEnterBackForwardCache,
{{"level", "store-and-evict"}});
BrowsingDataRemoverImplBrowserTest::SetUpCommandLine(command_line);
}
private:
base::test::ScopedFeatureList feature_list_;
};
IN_PROC_BROWSER_TEST_F(
BrowsingDataRemoverImplForCacheControlNoStorePageBrowserTest,
DoesClearNonCacheControlNoStoreBackForwardCacheEntries_DataTypeCookie) {
if (!content::BackForwardCache::IsBackForwardCacheFeatureEnabled()) {
return;
}
GURL url_1 = ssl_server().GetURL("/title1.html");
GURL url_2 = ssl_server().GetURL("/title2.html");
// 1) Navigate to url_1, then to url_2.
ASSERT_TRUE(NavigateToURL(shell(), url_1));
ASSERT_TRUE(NavigateToURL(shell(), url_2));
// 2) Go back, the page should be restored from BFCache.
ASSERT_TRUE(HistoryGoBack(shell()->web_contents()));
ExpectRestored(FROM_HERE);
// 3) Navigate to url_2 again.
ASSERT_TRUE(NavigateToURL(shell(), url_2));
// 4) Remove the browsing data with DATA_TYPE_COOKIES and the domain that
// matches the BFCached document's origin.
auto filter = BrowsingDataFilterBuilder::Create(
BrowsingDataFilterBuilder::Mode::kDelete);
filter->AddRegisterableDomain(ssl_server().base_url().host());
RemoveWithFilterAndWait(BrowsingDataRemover::DATA_TYPE_COOKIES,
std::move(filter));
// 5) Go back, and the page should be restored from BFCache.
ASSERT_TRUE(HistoryGoBack(shell()->web_contents()));
ExpectRestored(FROM_HERE);
}
IN_PROC_BROWSER_TEST_F(
BrowsingDataRemoverImplForCacheControlNoStorePageBrowserTest,
ClearCacheControlNoStoreBackForwardCacheEntries_DataTypeCookie) {
if (!content::BackForwardCache::IsBackForwardCacheFeatureEnabled()) {
return;
}
GURL url_1 = ssl_server().GetURL("/echoall/nocache");
GURL url_2 = ssl_server().GetURL("/title1.html");
// 1) Navigate to url_1 with CCNS response, then to url_2.
ASSERT_TRUE(NavigateToURL(shell(), url_1));
ASSERT_TRUE(NavigateToURL(shell(), url_2));
// 2) Remove the browsing data with DATA_TYPE_COOKIES and the domain that
// matches the BFCached document's origin.
auto filter = BrowsingDataFilterBuilder::Create(
BrowsingDataFilterBuilder::Mode::kDelete);
filter->AddRegisterableDomain(ssl_server().base_url().host());
RemoveWithFilterAndWait(BrowsingDataRemover::DATA_TYPE_COOKIES,
std::move(filter));
// 3) Go back, and the page should not be restored from BFCache since the
// BFCache entry should be flushed.
ASSERT_TRUE(HistoryGoBack(shell()->web_contents()));
ExpectNotRestored(
{BackForwardCacheMetrics::NotRestoredReason::kCookieFlushed,
BackForwardCacheMetrics::NotRestoredReason::kCacheControlNoStore},
{}, {}, {}, {}, FROM_HERE);
}
class CookiesBrowsingDataRemoverImplBrowserTest
: public BrowsingDataRemoverImplBrowserTest {
public:
void SetUpOnMainThread() override {
network_context()->GetCookieManager(
cookie_manager_.BindNewPipeAndPassReceiver());
}
bool SetCookie(
const GURL& url,
const std::string& cookie_line,
const std::optional<net::CookiePartitionKey>& cookie_partition_key) {
auto cookie_obj = net::CanonicalCookie::CreateForTesting(
url, cookie_line, base::Time::Now(), /*server_time=*/std::nullopt,
cookie_partition_key);
base::test::TestFuture<net::CookieAccessResult> future;
cookie_manager_->SetCanonicalCookie(*cookie_obj, url,
net::CookieOptions::MakeAllInclusive(),
future.GetCallback());
return future.Take().status.IsInclude();
}
net::CookieList GetAllCookies() {
base::test::TestFuture<const net::CookieList&> future;
cookie_manager_->GetAllCookies(future.GetCallback());
return future.Take();
}
private:
mojo::Remote<network::mojom::CookieManager> cookie_manager_;
};
IN_PROC_BROWSER_TEST_F(CookiesBrowsingDataRemoverImplBrowserTest,
ClearsAllCookiesByDefault) {
// Set unpartitioned cookies.
ASSERT_TRUE(SetCookie(GURL("http://a.com"), "A=0", std::nullopt));
ASSERT_TRUE(SetCookie(GURL("https://a.com"), "B=1; secure; samesite=none",
std::nullopt));
ASSERT_TRUE(SetCookie(GURL("https://b.com"),
"C=2; secure; samesite=none; max-age=10000",
std::nullopt));
ASSERT_EQ(3u, GetAllCookies().size());
// Set partitioned cookies.
ASSERT_TRUE(SetCookie(
GURL("https://c.com"), "A=0; secure; partitioned",
net::CookiePartitionKey::FromURLForTesting(GURL("https://d.com"))));
ASSERT_TRUE(SetCookie(
GURL("https://c.com"), "A=0; secure; samesite=none; partitioned",
net::CookiePartitionKey::FromURLForTesting(GURL("http://e.com"))));
ASSERT_TRUE(SetCookie(
GURL("https://f.com"),
"B=1; secure; samesite=none; max-age=10000; partitioned",
net::CookiePartitionKey::FromURLForTesting(GURL("https://g.com"))));
ASSERT_TRUE(
SetCookie(GURL("https://f.com"), "C=2; secure; samesite=none",
net::CookiePartitionKey::FromURLForTesting(
GURL("https://g.com"),
net::CookiePartitionKey::AncestorChainBit::kCrossSite,
base::UnguessableToken::Create())));
ASSERT_EQ(7u, GetAllCookies().size());
RemoveAndWait(BrowsingDataRemover::DATA_TYPE_COOKIES);
EXPECT_EQ(0u, GetAllCookies().size());
}
IN_PROC_BROWSER_TEST_F(CookiesBrowsingDataRemoverImplBrowserTest,
ClearingCookiesByHostKey) {
// Cookies set by a.com, should be removed.
// partition_key: null, host_key: a.com
ASSERT_TRUE(SetCookie(GURL("https://a.com"), "A=0; secure; partitioned",
/*cookie_partition_key=*/std::nullopt));
// partition_key: a.com, host_key: a.com
ASSERT_TRUE(SetCookie(
GURL("https://a.com"), "B=1; secure; partitioned",
net::CookiePartitionKey::FromURLForTesting(GURL("https://a.com"))));
// partition_key: b.com, host_key: a.com
ASSERT_TRUE(SetCookie(
GURL("https://a.com"), "C=2; secure; partitioned",
net::CookiePartitionKey::FromURLForTesting(GURL("https://b.com"))));
// Cookies set by b.com, should not be removed.
// partition_key: null, host_key: b.com
ASSERT_TRUE(SetCookie(GURL("https://b.com"), "D=3; secure; partitioned",
/*cookie_partition_key=*/std::nullopt));
// partition_key: a.com, host_key: b.com
ASSERT_TRUE(SetCookie(
GURL("https://b.com"), "E=4; secure; partitioned",
net::CookiePartitionKey::FromURLForTesting(GURL("https://a.com"))));
// partition_key: b.com, host_key: b.com
ASSERT_TRUE(SetCookie(
GURL("https://b.com"), "F=5; secure; partitioned",
net::CookiePartitionKey::FromURLForTesting(GURL("https://b.com"))));
ASSERT_EQ(6u, GetAllCookies().size());
std::unique_ptr<BrowsingDataFilterBuilder> builder(
BrowsingDataFilterBuilder::Create(
BrowsingDataFilterBuilder::Mode::kDelete));
builder->AddRegisterableDomain("a.com");
RemoveWithFilterAndWait(BrowsingDataRemover::DATA_TYPE_COOKIES,
std::move(builder));
auto cookies = GetAllCookies();
EXPECT_EQ(3u, cookies.size());
EXPECT_EQ("D", cookies[0].Name());
EXPECT_EQ("E", cookies[1].Name());
EXPECT_EQ("F", cookies[2].Name());
}
// Regression test for https://crbug.com/1457600.
IN_PROC_BROWSER_TEST_F(CookiesBrowsingDataRemoverImplBrowserTest,
ClearCookiesWithEmptyFilter) {
ASSERT_TRUE(SetCookie(GURL("https://a.com"), "A=0; secure",
/*cookie_partition_key=*/std::nullopt));
ASSERT_EQ(1u, GetAllCookies().size());
std::unique_ptr<BrowsingDataFilterBuilder> builder(
BrowsingDataFilterBuilder::Create(
BrowsingDataFilterBuilder::Mode::kDelete));
EXPECT_TRUE(builder->MatchesNothing());
// `builder` produces a malformed filter, so it fails a CHECK in
// `BrowsingDataRemoverImpl`.
EXPECT_DEATH_IF_SUPPORTED(
RemoveWithFilterAndWait(BrowsingDataRemover::DATA_TYPE_COOKIES,
std::move(builder)),
"");
EXPECT_EQ(1u, GetAllCookies().size());
}
IN_PROC_BROWSER_TEST_F(CookiesBrowsingDataRemoverImplBrowserTest,
ClearSiteData_PartitionedCookiesOnly) {
// Unpartitioned cookie should not be removed when third-party cookie blocking
// applies to the request that sent Clear-Site-Data.
ASSERT_TRUE(SetCookie(GURL("https://a.com"), "A=0; secure;",
/*cookie_partition_key=*/std::nullopt));
// Partitioned cookies should still be removed.
ASSERT_TRUE(SetCookie(
GURL("https://a.com"), "B=1; secure; partitioned",
net::CookiePartitionKey::FromURLForTesting(GURL("https://b.com"))));
// Nonced partitioned cookies should still be removed.
ASSERT_TRUE(
SetCookie(GURL("https://a.com"), "C=2; secure;",
net::CookiePartitionKey::FromURLForTesting(
GURL("https://b.com"),
net::CookiePartitionKey::AncestorChainBit::kCrossSite,
base::UnguessableToken::Create())));
std::unique_ptr<BrowsingDataFilterBuilder> builder(
BrowsingDataFilterBuilder::Create(
BrowsingDataFilterBuilder::Mode::kDelete));
builder->AddRegisterableDomain("a.com");
builder->SetPartitionedCookiesOnly(true);
RemoveWithFilterAndWait(BrowsingDataRemover::DATA_TYPE_COOKIES,
std::move(builder));
auto cookies = GetAllCookies();
EXPECT_EQ(1u, cookies.size());
EXPECT_EQ("A", cookies[0].Name());
}
IN_PROC_BROWSER_TEST_F(CookiesBrowsingDataRemoverImplBrowserTest,
ClearSiteData_AllDomainsPartitionedCookiesOnly) {
// Unpartitioned cookies should not be removed when
// SetPartitionedCookiesOnly(true)
ASSERT_TRUE(SetCookie(GURL("https://a.com"), "A=0; secure;",
/*cookie_partition_key=*/std::nullopt));
// All partitioned cookies should be removed.
ASSERT_TRUE(SetCookie(
GURL("https://a.com"), "B=1; secure; partitioned",
net::CookiePartitionKey::FromURLForTesting(GURL("https://b.com"))));
std::unique_ptr<BrowsingDataFilterBuilder> builder(
BrowsingDataFilterBuilder::Create(
BrowsingDataFilterBuilder::Mode::kPreserve));
// Mode::kPreserve + no origins/domains = delete everything.
builder->SetPartitionedCookiesOnly(true);
RemoveWithFilterAndWait(BrowsingDataRemover::DATA_TYPE_COOKIES,
std::move(builder));
EXPECT_THAT(GetAllCookies(), testing::ElementsAre(testing::Property(
&net::CookieBase::Name, "A")));
}
IN_PROC_BROWSER_TEST_F(CookiesBrowsingDataRemoverImplBrowserTest,
ClearSiteData_AllDomainsCookiePartitionKeyCollection) {
// All unpartitioned cookies should be removed.
ASSERT_TRUE(SetCookie(GURL("https://a.com"), "A=0; secure;",
/*cookie_partition_key=*/std::nullopt));
// Cookies partitioned under b.com should also be removed.
ASSERT_TRUE(SetCookie(
GURL("https://a.com"), "B=1; secure; partitioned",
net::CookiePartitionKey::FromURLForTesting(GURL("https://b.com"))));
// Cookies partitioned under other sites should NOT be removed.
ASSERT_TRUE(SetCookie(
GURL("https://a.com"), "C=2; secure; partitioned",
net::CookiePartitionKey::FromURLForTesting(GURL("https://c.com"))));
std::unique_ptr<BrowsingDataFilterBuilder> builder(
BrowsingDataFilterBuilder::Create(
BrowsingDataFilterBuilder::Mode::kPreserve));
// Mode::kPreserve + no origins/domains = delete everything.
builder->SetCookiePartitionKeyCollection(net::CookiePartitionKeyCollection(
net::CookiePartitionKey::FromURLForTesting(GURL("https://b.com"))));
RemoveWithFilterAndWait(BrowsingDataRemover::DATA_TYPE_COOKIES,
std::move(builder));
EXPECT_THAT(GetAllCookies(), testing::ElementsAre(testing::Property(
&net::CookieBase::Name, "C")));
}
namespace {
// Tests Trust Tokens clearing by calling
// TrustTokenQueryAnswerer::HasTrustTokens with a TrustTokenQueryAnswerer
// obtained from the provided NetworkContext.
//
// The Trust Tokens functionality places a cap of 2 distinct arguments to the
// |issuer| argument of
// TrustTokenQueryAnswerer(origin)::HasTrustTokens(issuer)
// for each top-frame origin |origin|. (This limit is recorded in persistent
// storage scoped to the origin |origin| and is not related to the lifetime of
// the specific TrustTokenQueryAnswerer object.)
//
// To add an origin, the tester creates a TrustTokenQueryAnswerer parameterized
// by |origin| and calls HasTrustTokens with two distinct "priming" issuer
// arguments. This will make the Trust Tokens persistent storage record that
// |origin| is associated with each of these issuers, with the effect that
// (barring a data clear) subsequent HasTrustTokens calls with different issuer
// arguments will fail. To check if an origin is present, the tester calls
// TrustTokenQueryAnswerer(origin)::HasTrustTokens(issuer)
// with an |issuer| argument distinct from the two earlier "priming" issuers.
// This third HasTrustTokens call will error out exactly if |origin| was
// previously added by AddOrigin.
//
// Usage:
// >= 0 AddOrigin() - origins must be HTTPS
// (clear data)
// >= 0 HasOrigin()
class TrustTokensTester {
public:
explicit TrustTokensTester(network::mojom::NetworkContext* network_context)
: network_context_(network_context) {}
void AddOrigin(const url::Origin& origin) {
mojo::Remote<network::mojom::TrustTokenQueryAnswerer> answerer;
network_context_->GetTrustTokenQueryAnswerer(
answerer.BindNewPipeAndPassReceiver(), origin);
// Calling HasTrustTokens will associate the issuer argument with the
// origin |origin|.
//
// Do this until the |origin| is associated with
// network::kTrustTokenPerToplevelMaxNumberOfAssociatedIssuers many issuers
// (namely 2; this value is not expected to change frequently).
//
// After the limit is reached, subsequent HasTrustToken(origin, issuer)
// queries will fail for any issuers not in {https://prime0.example,
// https://prime1.example} --- unless data for |origin| is cleared.
for (int i = 0; i < 2; ++i) {
base::RunLoop run_loop;
answerer->HasTrustTokens(
url::Origin::Create(
GURL(base::StringPrintf("https://prime%d.example", i))),
base::BindLambdaForTesting(
[&](network::mojom::HasTrustTokensResultPtr) {
run_loop.Quit();
}));
run_loop.Run();
}
}
bool HasOrigin(const url::Origin& origin) {
mojo::Remote<network::mojom::TrustTokenQueryAnswerer> answerer;
network_context_->GetTrustTokenQueryAnswerer(
answerer.BindNewPipeAndPassReceiver(), origin);
base::RunLoop run_loop;
bool has_origin = false;
// Since https://probe.example is not among the issuer origins previously
// provided to HasTrustTokens(origin, _) calls in AddOrigin:
// - If data has not been cleared,
// HasTrustToken(origin, https://probe.example)
// is expected to fail with kSiteIssuerLimit because |origin| is at
// its number-of-associated-issuers limit, so the answerer will refuse
// to answer a query for an origin it has not yet seen.
// - If data has been cleared, the answerer should be able to fulfill the
// query.
answerer->HasTrustTokens(
url::Origin::Create(GURL("https://probe.example")),
base::BindLambdaForTesting(
[&](network::mojom::HasTrustTokensResultPtr result) {
// HasTrustTokens will error out with kSiteIssuerLimit exactly
// when the top-frame origin |origin| was previously added by
// AddOrigin.
if (result->status ==
network::mojom::TrustTokenOperationStatus::kSiteIssuerLimit) {
has_origin = true;
}
run_loop.Quit();
}));
run_loop.Run();
return has_origin;
}
private:
raw_ptr<network::mojom::NetworkContext> network_context_ = nullptr;
};
} // namespace
using BrowsingDataRemoverImplTrustTokenTest =
BrowsingDataRemoverImplBrowserTest;
IN_PROC_BROWSER_TEST_F(BrowsingDataRemoverImplTrustTokenTest, Remove) {
TrustTokensTester tester(network_context());
auto origin = url::Origin::Create(GURL("https://topframe.example"));
tester.AddOrigin(origin);
ASSERT_TRUE(tester.HasOrigin(origin));
RemoveAndWait(BrowsingDataRemover::DATA_TYPE_TRUST_TOKENS);
EXPECT_FALSE(tester.HasOrigin(origin));
}
IN_PROC_BROWSER_TEST_F(BrowsingDataRemoverImplTrustTokenTest, RemoveByDomain) {
TrustTokensTester tester(network_context());
auto origin = url::Origin::Create(GURL("https://topframe.example"));
auto sub_origin = url::Origin::Create(GURL("https://sub.topframe.example"));
auto another_origin =
url::Origin::Create(GURL("https://another-topframe.example"));
tester.AddOrigin(origin);
tester.AddOrigin(sub_origin);
tester.AddOrigin(another_origin);
ASSERT_TRUE(tester.HasOrigin(origin));
ASSERT_TRUE(tester.HasOrigin(sub_origin));
ASSERT_TRUE(tester.HasOrigin(another_origin));
std::unique_ptr<BrowsingDataFilterBuilder> builder(
BrowsingDataFilterBuilder::Create(
BrowsingDataFilterBuilder::Mode::kDelete));
builder->AddRegisterableDomain("topframe.example");
RemoveWithFilterAndWait(BrowsingDataRemover::DATA_TYPE_TRUST_TOKENS,
std::move(builder));
EXPECT_FALSE(tester.HasOrigin(origin));
EXPECT_FALSE(tester.HasOrigin(sub_origin));
EXPECT_TRUE(tester.HasOrigin(another_origin));
}
IN_PROC_BROWSER_TEST_F(BrowsingDataRemoverImplTrustTokenTest,
PreserveByDomain) {
TrustTokensTester tester(network_context());
auto origin = url::Origin::Create(GURL("https://topframe.example"));
auto sub_origin = url::Origin::Create(GURL("https://sub.topframe.example"));
auto another_origin =
url::Origin::Create(GURL("https://another-topframe.example"));
tester.AddOrigin(origin);
tester.AddOrigin(sub_origin);
tester.AddOrigin(another_origin);
ASSERT_TRUE(tester.HasOrigin(origin));
ASSERT_TRUE(tester.HasOrigin(sub_origin));
ASSERT_TRUE(tester.HasOrigin(another_origin));
// Delete all data *except* that specified by the filter.
std::unique_ptr<BrowsingDataFilterBuilder> builder(
BrowsingDataFilterBuilder::Create(
BrowsingDataFilterBuilder::Mode::kPreserve));
builder->AddRegisterableDomain("topframe.example");
RemoveWithFilterAndWait(BrowsingDataRemover::DATA_TYPE_TRUST_TOKENS,
std::move(builder));
EXPECT_TRUE(tester.HasOrigin(origin));
EXPECT_TRUE(tester.HasOrigin(sub_origin));
EXPECT_FALSE(tester.HasOrigin(another_origin));
}
class BrowsingDataRemoverImplSharedStorageBrowserTest
: public BrowsingDataRemoverImplBrowserTest {
public:
BrowsingDataRemoverImplSharedStorageBrowserTest() {
feature_list_.InitAndEnableFeature(network::features::kSharedStorageAPI);
}
StoragePartition* storage_partition() {
return shell()
->web_contents()
->GetBrowserContext()
->GetDefaultStoragePartition();
}
private:
base::test::ScopedFeatureList feature_list_;
};
IN_PROC_BROWSER_TEST_F(BrowsingDataRemoverImplSharedStorageBrowserTest,
Remove) {
SharedStorageClearSiteDataTester tester(storage_partition());
auto origin = url::Origin::Create(GURL("https://topframe.example"));
tester.AddConsecutiveSharedStorageEntries(origin, u"key", u"value", 10);
EXPECT_THAT(tester.GetSharedStorageOrigins(),
testing::UnorderedElementsAre(origin));
// Note that u"key" concatenated with a single digit has 4 char16_t's and
// hence 8 bytes. Similarly, u"value" concatenated with one digit has
// 6 char16_t's and hence 12 bytes. A pair of these together thus has
// 20 bytes.
const int kNumBytesPerEntry = 20;
EXPECT_EQ(10 * kNumBytesPerEntry, tester.GetSharedStorageTotalBytes());
RemoveAndWait(BrowsingDataRemover::DATA_TYPE_SHARED_STORAGE);
EXPECT_TRUE(tester.GetSharedStorageOrigins().empty());
EXPECT_EQ(0, tester.GetSharedStorageTotalBytes());
}
IN_PROC_BROWSER_TEST_F(BrowsingDataRemoverImplSharedStorageBrowserTest,
RemoveByDomain) {
SharedStorageClearSiteDataTester tester(storage_partition());
auto origin = url::Origin::Create(GURL("https://topframe.example"));
auto sub_origin = url::Origin::Create(GURL("https://sub.topframe.example"));
auto another_origin =
url::Origin::Create(GURL("https://another-topframe.example"));
tester.AddConsecutiveSharedStorageEntries(origin, u"key", u"value", 5);
tester.AddConsecutiveSharedStorageEntries(sub_origin, u"key", u"value", 10);
tester.AddConsecutiveSharedStorageEntries(another_origin, u"key", u"value",
1);
EXPECT_THAT(
tester.GetSharedStorageOrigins(),
testing::UnorderedElementsAre(origin, sub_origin, another_origin));
// Note that u"key" concatenated with a single digit has 4 char16_t's and
// hence 8 bytes. Similarly, u"value" concatenated with one digit has
// 6 char16_t's and hence 12 bytes. A pair of these together thus has
// 20 bytes.
const int kNumBytesPerEntry = 20;
EXPECT_EQ(16 * kNumBytesPerEntry, tester.GetSharedStorageTotalBytes());
std::unique_ptr<BrowsingDataFilterBuilder> builder(
BrowsingDataFilterBuilder::Create(
BrowsingDataFilterBuilder::Mode::kDelete));
builder->AddRegisterableDomain("topframe.example");
RemoveWithFilterAndWait(BrowsingDataRemover::DATA_TYPE_SHARED_STORAGE,
std::move(builder));
EXPECT_THAT(tester.GetSharedStorageOrigins(),
testing::UnorderedElementsAre(another_origin));
EXPECT_EQ(1 * kNumBytesPerEntry, tester.GetSharedStorageTotalBytes());
}
IN_PROC_BROWSER_TEST_F(BrowsingDataRemoverImplSharedStorageBrowserTest,
PreserveByDomain) {
SharedStorageClearSiteDataTester tester(storage_partition());
auto origin = url::Origin::Create(GURL("https://topframe.example"));
auto sub_origin = url::Origin::Create(GURL("https://sub.topframe.example"));
auto another_origin =
url::Origin::Create(GURL("https://another-topframe.example"));
tester.AddConsecutiveSharedStorageEntries(origin, u"key", u"value", 5);
tester.AddConsecutiveSharedStorageEntries(sub_origin, u"key", u"value", 10);
tester.AddConsecutiveSharedStorageEntries(another_origin, u"key", u"value",
1);
EXPECT_THAT(
tester.GetSharedStorageOrigins(),
testing::UnorderedElementsAre(origin, sub_origin, another_origin));
// Note that u"key" concatenated with a single digit has 4 char16_t's and
// hence 8 bytes. Similarly, u"value" concatenated with one digit has
// 6 char16_t's and hence 12 bytes. A pair of these together thus has
// 20 bytes.
const int kNumBytesPerEntry = 20;
EXPECT_EQ(16 * kNumBytesPerEntry, tester.GetSharedStorageTotalBytes());
// Delete all data *except* that specified by the filter.
std::unique_ptr<BrowsingDataFilterBuilder> builder(
BrowsingDataFilterBuilder::Create(
BrowsingDataFilterBuilder::Mode::kPreserve));
builder->AddRegisterableDomain("topframe.example");
RemoveWithFilterAndWait(BrowsingDataRemover::DATA_TYPE_SHARED_STORAGE,
std::move(builder));
EXPECT_THAT(tester.GetSharedStorageOrigins(),
testing::UnorderedElementsAre(origin, sub_origin));
EXPECT_EQ(15 * kNumBytesPerEntry, tester.GetSharedStorageTotalBytes());
}
class BrowsingDataRemoverImplPrerenderingBrowserTest
: public BrowsingDataRemoverImplBrowserTest {
public:
BrowsingDataRemoverImplPrerenderingBrowserTest() {
prerender_helper_ =
std::make_unique<test::PrerenderTestHelper>(base::BindRepeating(
&BrowsingDataRemoverImplPrerenderingBrowserTest::web_contents,
base::Unretained(this)));
}
~BrowsingDataRemoverImplPrerenderingBrowserTest() override = default;
protected:
test::PrerenderTestHelper& prerender_helper() { return *prerender_helper_; }
WebContents* web_contents() { return shell()->web_contents(); }
std::unique_ptr<test::PrerenderTestHelper> prerender_helper_;
};
IN_PROC_BROWSER_TEST_F(BrowsingDataRemoverImplPrerenderingBrowserTest,
ClearCacheCancelsPrerendering) {
GURL initial_url = ssl_server().GetURL("/title1.html");
GURL prerendering_url = ssl_server().GetURL("/empty.html");
// 1) Navigate to the initial url.
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
// 2) Add and wait for prerendering of the prerendering url to complete.
FrameTreeNodeId host_id = prerender_helper().AddPrerender(prerendering_url);
content::test::PrerenderHostObserver host_observer(*web_contents(), host_id);
// 3) Remove the browsing data with DATA_TYPE_CACHE.
auto filter = BrowsingDataFilterBuilder::Create(
BrowsingDataFilterBuilder::Mode::kDelete);
filter->AddRegisterableDomain(ssl_server().base_url().host());
RemoveWithFilterAndWait(BrowsingDataRemover::DATA_TYPE_CACHE,
std::move(filter));
host_observer.WaitForDestroyed();
// 4) Verify that prerendering was canceled due to removing browsing data.
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
PrerenderFinalStatus::kBrowsingDataRemoved, 1);
}
class BrowsingDataRemoverImplPrefetchBrowserTest
: public BrowsingDataRemoverImplBrowserTest {
public:
BrowsingDataRemoverImplPrefetchBrowserTest() {
feature_list_.InitAndEnableFeature(features::kPrefetchBrowsingDataRemoval);
}
void StartPrefetch(const GURL& url, Shell* shell) {
auto* prefetch_document_manager =
PrefetchDocumentManager::GetOrCreateForCurrentDocument(
shell->web_contents()->GetPrimaryMainFrame());
auto candidate = blink::mojom::SpeculationCandidate::New();
candidate->url = url;
candidate->action = blink::mojom::SpeculationAction::kPrefetch;
candidate->eagerness = blink::mojom::SpeculationEagerness::kImmediate;
candidate->referrer = Referrer::SanitizeForRequest(
url, blink::mojom::Referrer(
shell->web_contents()->GetURL(),
network::mojom::ReferrerPolicy::kStrictOriginWhenCrossOrigin));
std::vector<blink::mojom::SpeculationCandidatePtr> candidates;
candidates.push_back(std::move(candidate));
prefetch_document_manager->ProcessCandidates(candidates);
}
~BrowsingDataRemoverImplPrefetchBrowserTest() override = default;
private:
base::test::ScopedFeatureList feature_list_;
};
IN_PROC_BROWSER_TEST_F(BrowsingDataRemoverImplPrefetchBrowserTest,
ClearCacheCancelsPrefetch) {
GURL initial_url = ssl_server().GetURL("/empty.html");
GURL prefetch_url = ssl_server().GetURL("/title1.html");
// 1) Navigate to the initial url.
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
test::TestPrefetchWatcher test_prefetch_watcher;
// 2) Start prefetching the prefetch_url.
StartPrefetch(prefetch_url, shell());
// 3) Wait for the prefetch_url to finish prefetching.
test_prefetch_watcher.WaitUntilPrefetchResponseCompleted(
static_cast<RenderFrameHostImpl*>(
shell()->web_contents()->GetPrimaryMainFrame())
->GetDocumentToken(),
prefetch_url);
// 4) Remove the browsing data with DATA_TYPE_CACHE.
auto filter = BrowsingDataFilterBuilder::Create(
BrowsingDataFilterBuilder::Mode::kDelete);
filter->AddRegisterableDomain(ssl_server().base_url().host());
RemoveWithFilterAndWait(BrowsingDataRemover::DATA_TYPE_CACHE,
std::move(filter));
// 5) Verify that the prefetch failed due to removing browsing data.
histogram_tester().ExpectUniqueSample(
"Preloading.Prefetch.PrefetchStatus",
PrefetchStatus::kPrefetchEvictedAfterBrowsingDataRemoved, 1);
}
IN_PROC_BROWSER_TEST_F(BrowsingDataRemoverImplPrefetchBrowserTest,
ClearCacheCancelsPrefetchMultipleOrigins) {
// Test prefetch cancellation for two different origins.
// As part of the Clear Browsing Data trigger, prefetches that have
// referral origins in the BrowsingDataFilterBuilder should be canceled.
GURL referral_url1 = ssl_server().GetURL("/empty.html");
GURL prefetch_url1 = ssl_server().GetURL("/title1.html");
GURL referral_url2 = ssl_server().GetURL("a.test", "/title1.html");
GURL prefetch_url2 = ssl_server().GetURL("a.test", "/empty.html");
// 1) Navigate to the referral url for origin 1.
ASSERT_TRUE(NavigateToURL(shell(), referral_url1));
// 2) Open a new tab and navigate to the referral url for origin 2.
Shell* new_shell =
Shell::CreateNewWindow(shell()->web_contents()->GetBrowserContext(),
GURL(url::kAboutBlankURL), nullptr, gfx::Size());
ASSERT_TRUE(NavigateToURL(new_shell, referral_url2));
test::TestPrefetchWatcher test_prefetch_watcher;
// 3) Start prefetching the first url and wait for it to finish prefetching.
StartPrefetch(prefetch_url1, shell());
test_prefetch_watcher.WaitUntilPrefetchResponseCompleted(
static_cast<RenderFrameHostImpl*>(
shell()->web_contents()->GetPrimaryMainFrame())
->GetDocumentToken(),
prefetch_url1);
// 4) Start prefetching the second url and wait for it to finish prefetching.
StartPrefetch(prefetch_url2, new_shell);
test_prefetch_watcher.WaitUntilPrefetchResponseCompleted(
static_cast<RenderFrameHostImpl*>(
new_shell->web_contents()->GetPrimaryMainFrame())
->GetDocumentToken(),
prefetch_url2);
// 5) Remove the browsing data with DATA_TYPE_CACHE.
// Mode::kPreserve + no origins/domains = delete everything.
auto filter = BrowsingDataFilterBuilder::Create(
BrowsingDataFilterBuilder::Mode::kPreserve);
RemoveWithFilterAndWait(BrowsingDataRemover::DATA_TYPE_CACHE,
std::move(filter));
// 6) Verify that both prefetches failed due to removing browsing data.
histogram_tester().ExpectUniqueSample(
"Preloading.Prefetch.PrefetchStatus",
PrefetchStatus::kPrefetchEvictedAfterBrowsingDataRemoved, 2);
}
class BrowsingDataRemoverImplPrefetchHoldbackBrowserTest
: public BrowsingDataRemoverImplPrefetchBrowserTest {
public:
BrowsingDataRemoverImplPrefetchHoldbackBrowserTest() {
feature_list_.InitAndEnableFeatureWithParameters(
features::kPreloadingConfig, {{"preloading_config", R"(
[{
"preloading_type": "Prefetch",
"preloading_predictor": "SpeculationRules",
"holdback": true,
"sampling_likelihood": 1
}])"}});
}
~BrowsingDataRemoverImplPrefetchHoldbackBrowserTest() override = default;
private:
base::test::ScopedFeatureList feature_list_;
};
IN_PROC_BROWSER_TEST_F(BrowsingDataRemoverImplPrefetchHoldbackBrowserTest,
ClearCacheCancelsHeldbackPrefetch) {
GURL initial_url = ssl_server().GetURL("/empty.html");
GURL prefetch_url = ssl_server().GetURL("/title1.html");
// 1) Navigate to the initial url.
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
// 2) Start prefetching the prefetch_url.
StartPrefetch(prefetch_url, shell());
// 3) Remove the browsing data with DATA_TYPE_CACHE.
auto filter = BrowsingDataFilterBuilder::Create(
BrowsingDataFilterBuilder::Mode::kDelete);
filter->AddRegisterableDomain(ssl_server().base_url().host());
RemoveWithFilterAndWait(BrowsingDataRemover::DATA_TYPE_CACHE,
std::move(filter));
// 4) Verify that the prefetch failed due to removing browsing data.
histogram_tester().ExpectUniqueSample(
"Preloading.Prefetch.PrefetchStatus",
PrefetchStatus::kPrefetchEvictedAfterBrowsingDataRemoved, 1);
}
class BrowsingDataRemoverCanvasNoiseTokenBrowserTest
: public CookiesBrowsingDataRemoverImplBrowserTest {
base::test::ScopedFeatureList features_{
fingerprinting_protection_interventions::features::kCanvasNoise};
};
IN_PROC_BROWSER_TEST_F(BrowsingDataRemoverCanvasNoiseTokenBrowserTest,
CanvasNoiseTokenRegeneratesOnCookieRemoval) {
// Set a cookie.
GURL url = ssl_server().GetURL("/browsing_data/site_data.html");
content::BrowserContext* browser_context =
shell()->web_contents()->GetBrowserContext();
ASSERT_TRUE(NavigateToURL(shell(), url));
url::Origin origin = url::Origin::Create(url);
uint64_t original_token =
content::CanvasNoiseTokenData::GetToken(browser_context, origin);
constexpr uint64_t kRemoveMask =
content::BrowsingDataRemover::DATA_TYPE_COOKIES;
content::BrowsingDataRemover* remover =
browser_context->GetBrowsingDataRemover();
content::BrowsingDataRemoverCompletionObserver completion_observer(remover);
remover->RemoveAndReply(
base::Time(), // delete_begin
base::Time::Max(), // delete_end
kRemoveMask, content::BrowsingDataRemover::ORIGIN_TYPE_UNPROTECTED_WEB,
&completion_observer);
completion_observer.BlockUntilCompletion();
// Next navigation should update the token.
ASSERT_TRUE(NavigateToURL(shell(), url));
EXPECT_NE(content::CanvasNoiseTokenData::GetToken(browser_context, origin),
original_token);
}
} // namespace content