diff --git a/content/browser/btm/btm_bounce_detector.cc b/content/browser/btm/btm_bounce_detector.cc index af8edf0f4a80e..36a8f951c357a 100644 --- a/content/browser/btm/btm_bounce_detector.cc +++ b/content/browser/btm/btm_bounce_detector.cc @@ -295,51 +295,6 @@ std::set<std::string> BtmRedirectContext::AllSitesWithUserActivationOrAuthn() return sites; } -std::map<std::string, std::pair<GURL, bool>> -BtmRedirectContext::GetRedirectHeuristicURLs( - const GURL& first_party_url, - base::optional_ref<std::set<std::string>> allowed_sites, - bool require_current_interaction) const { - std::map<std::string, std::pair<GURL, bool>> - sites_to_url_and_current_interaction; - - std::set<std::string> sites_with_current_interaction = - AllSitesWithUserActivationOrAuthn(); - - const std::string& first_party_site = GetSiteForBtm(first_party_url); - for (const auto& redirect : redirects_) { - const GURL& url = redirect->redirecting_url.url; - const std::string& site = redirect->site; - - // The redirect heuristic does not apply for first-party cookie access. - if (site == first_party_site) { - continue; - } - - // Check the list of allowed sites, if provided. - if (allowed_sites.has_value() && !allowed_sites->contains(site)) { - continue; - } - - // Check for a current interaction, if the flag requires it. - if (require_current_interaction && - !sites_with_current_interaction.contains(site)) { - continue; - } - - // Add the url to the map, but do not override a previous current - // interaction. - auto& [prev_url, had_current_interaction] = - sites_to_url_and_current_interaction[site]; - if (prev_url.is_empty() || !had_current_interaction) { - prev_url = url; - had_current_interaction = sites_with_current_interaction.contains(site); - } - } - - return sites_to_url_and_current_interaction; -} - base::span<const BtmRedirectInfoPtr> BtmRedirectContext::GetServerRedirectsSinceLastPrimaryPageChange() const { size_t index = size(); diff --git a/content/browser/btm/btm_bounce_detector.h b/content/browser/btm/btm_bounce_detector.h index 22b46bf024f0a..a6c2b057ef15c 100644 --- a/content/browser/btm/btm_bounce_detector.h +++ b/content/browser/btm/btm_bounce_detector.h @@ -161,16 +161,6 @@ class CONTENT_EXPORT BtmRedirectContext { // Return all sites that had an interaction in the current redirect context. std::set<std::string> AllSitesWithUserActivationOrAuthn() const; - // Returns a map of (site, (url, has_current_interaction)) for all URLs in the - // current redirect chain that satisfy the redirect heuristic. This performs - // all checks except for the presence of a past interaction, which should be - // checked by the caller using the DIPS db. If `allowed_sites` is present, - // only sites in `allowed_sites` should be included. - std::map<std::string, std::pair<GURL, bool>> GetRedirectHeuristicURLs( - const GURL& first_party_url, - base::optional_ref<std::set<std::string>> allowed_sites, - bool require_current_interaction) const; - // Returns the server redirects from the last navigation. Note that due to // limitations in C++ the BtmRedirectInfo objects are unavoidably mutable. // Clients must not modify them. diff --git a/content/browser/btm/btm_bounce_detector_unittest.cc b/content/browser/btm/btm_bounce_detector_unittest.cc index 441375c14ee63..2fef476c5b41b 100644 --- a/content/browser/btm/btm_bounce_detector_unittest.cc +++ b/content/browser/btm/btm_bounce_detector_unittest.cc @@ -1506,118 +1506,6 @@ TEST(BtmRedirectContextTest, AddLateCookieAccess) { HasBtmDataAccessType(BtmDataAccessType::kRead)))); } -TEST(BtmRedirectContextTest, GetRedirectHeuristicURLs_NoRequirements) { - base::test::ScopedFeatureList features; - features.InitAndEnableFeatureWithParameters( - content_settings::features::kTpcdHeuristicsGrants, - {{"TpcdRedirectHeuristicRequireABAFlow", "false"}}); - - UrlAndSourceId first_party_url = MakeUrlAndId("http://a.test/"); - UrlAndSourceId current_interaction_url = MakeUrlAndId("http://b.test/"); - GURL no_current_interaction_url("http://c.test/"); - - std::vector<ChainPair> chains; - BtmRedirectContext context( - base::BindRepeating(AppendChainPair, std::ref(chains)), base::DoNothing(), - UrlAndSourceId(), - /*redirect_prefix_count=*/0); - - context.AppendCommitted(first_party_url, - {MakeServerRedirects({"http://c.test"})}, - current_interaction_url, false); - context.AppendCommitted( - MakeClientRedirect("http://b.test/", BtmDataAccessType::kNone, - /*has_sticky_activation=*/true), - {}, first_party_url, false); - - ASSERT_EQ(context.size(), 2u); - - std::map<std::string, std::pair<GURL, bool>> - sites_to_url_and_current_interaction = context.GetRedirectHeuristicURLs( - first_party_url.url, std::nullopt, - /*require_current_interaction=*/false); - EXPECT_THAT( - sites_to_url_and_current_interaction, - testing::UnorderedElementsAre( - std::pair<std::string, std::pair<GURL, bool>>( - "b.test", std::make_pair(current_interaction_url.url, true)), - std::pair<std::string, std::pair<GURL, bool>>( - "c.test", std::make_pair(no_current_interaction_url, false)))); -} - -TEST(BtmRedirectContextTest, GetRedirectHeuristicURLs_RequireABAFlow) { - base::test::ScopedFeatureList features; - features.InitAndEnableFeatureWithParameters( - content_settings::features::kTpcdHeuristicsGrants, - {{"TpcdRedirectHeuristicRequireABAFlow", "true"}}); - - UrlAndSourceId first_party_url = MakeUrlAndId("http://a.test/"); - GURL aba_url("http://b.test/"); - GURL no_aba_url("http://c.test/"); - - std::vector<ChainPair> chains; - BtmRedirectContext context( - base::BindRepeating(AppendChainPair, std::ref(chains)), base::DoNothing(), - UrlAndSourceId(), - /*redirect_prefix_count=*/0); - - context.AppendCommitted( - first_party_url, - {MakeServerRedirects({"http://b.test", "http://c.test"})}, - first_party_url, false); - - ASSERT_EQ(context.size(), 2u); - - std::set<std::string> allowed_sites = {GetSiteForBtm(aba_url)}; - - std::map<std::string, std::pair<GURL, bool>> - sites_to_url_and_current_interaction = context.GetRedirectHeuristicURLs( - first_party_url.url, allowed_sites, - /*require_current_interaction=*/false); - EXPECT_THAT(sites_to_url_and_current_interaction, - testing::UnorderedElementsAre( - std::pair<std::string, std::pair<GURL, bool>>( - "b.test", std::make_pair(aba_url, false)))); -} - -TEST(BtmRedirectContextTest, - GetRedirectHeuristicURLs_RequireCurrentInteraction) { - base::test::ScopedFeatureList features; - features.InitAndEnableFeatureWithParameters( - content_settings::features::kTpcdHeuristicsGrants, - {{"TpcdRedirectHeuristicRequireABAFlow", "false"}}); - - UrlAndSourceId first_party_url = MakeUrlAndId("http://a.test/"); - UrlAndSourceId current_interaction_url = MakeUrlAndId("http://b.test/"); - GURL no_current_interaction_url("http://c.test/"); - - std::vector<ChainPair> chains; - BtmRedirectContext context( - base::BindRepeating(AppendChainPair, std::ref(chains)), base::DoNothing(), - UrlAndSourceId(), - /*redirect_prefix_count=*/0); - - context.AppendCommitted(first_party_url, - {MakeServerRedirects({"http://c.test"})}, - current_interaction_url, false); - context.AppendCommitted( - MakeClientRedirect("http://b.test/", BtmDataAccessType::kNone, - /*has_sticky_activation=*/false, true), - {}, first_party_url, false); - - ASSERT_EQ(context.size(), 2u); - - std::map<std::string, std::pair<GURL, bool>> - sites_to_url_and_current_interaction = context.GetRedirectHeuristicURLs( - first_party_url.url, std::nullopt, - /*require_current_interaction=*/true); - EXPECT_THAT( - sites_to_url_and_current_interaction, - testing::UnorderedElementsAre( - std::pair<std::string, std::pair<GURL, bool>>( - "b.test", std::make_pair(current_interaction_url.url, true)))); -} - TEST(BtmRedirectContextTest, GetServerRedirectsSinceLastPrimaryPageChangeNoRedirects) { std::vector<ChainPair> chains; diff --git a/content/browser/tpcd_heuristics/opener_heuristic_utils.cc b/content/browser/tpcd_heuristics/opener_heuristic_utils.cc index 741d7d2a8a065..c50a7a8f8d7c2 100644 --- a/content/browser/tpcd_heuristics/opener_heuristic_utils.cc +++ b/content/browser/tpcd_heuristics/opener_heuristic_utils.cc @@ -4,6 +4,8 @@ #include "content/browser/tpcd_heuristics/opener_heuristic_utils.h" +#include "content/browser/btm/btm_bounce_detector.h" +#include "content/public/browser/cookie_access_details.h" #include "services/network/public/cpp/features.h" #include "url/gurl.h" @@ -26,4 +28,52 @@ OptionalBool IsAdTaggedCookieForHeuristics(const CookieAccessDetails& details) { net::CookieSettingOverride::kSkipTPCDHeuristicsGrant)); } +std::map<std::string, std::pair<GURL, bool>> GetRedirectHeuristicURLs( + const BtmRedirectContext& committed_redirect_context, + const GURL& first_party_url, + base::optional_ref<std::set<std::string>> allowed_sites, + bool require_current_interaction) { + std::map<std::string, std::pair<GURL, bool>> + sites_to_url_and_current_interaction; + + std::set<std::string> sites_with_current_interaction = + committed_redirect_context.AllSitesWithUserActivationOrAuthn(); + + const std::string& first_party_site = GetSiteForBtm(first_party_url); + for (size_t redirect_index = 0; + redirect_index < committed_redirect_context.size(); redirect_index++) { + const BtmRedirectInfo& redirect = + committed_redirect_context[redirect_index]; + const GURL& url = redirect.redirecting_url.url; + const std::string& site = redirect.site; + + // The redirect heuristic does not apply for first-party cookie access. + if (site == first_party_site) { + continue; + } + + // Check the list of allowed sites, if provided. + if (allowed_sites.has_value() && !allowed_sites->contains(site)) { + continue; + } + + // Check for a current interaction, if the flag requires it. + if (require_current_interaction && + !sites_with_current_interaction.contains(site)) { + continue; + } + + // Add the url to the map, but do not override a previous current + // interaction. + auto& [prev_url, had_current_interaction] = + sites_to_url_and_current_interaction[site]; + if (prev_url.is_empty() || !had_current_interaction) { + prev_url = url; + had_current_interaction = sites_with_current_interaction.contains(site); + } + } + + return sites_to_url_and_current_interaction; +} + } // namespace content diff --git a/content/browser/tpcd_heuristics/opener_heuristic_utils.h b/content/browser/tpcd_heuristics/opener_heuristic_utils.h index b319bfc842310..f4c344420f489 100644 --- a/content/browser/tpcd_heuristics/opener_heuristic_utils.h +++ b/content/browser/tpcd_heuristics/opener_heuristic_utils.h @@ -5,13 +5,18 @@ #ifndef CONTENT_BROWSER_TPCD_HEURISTICS_OPENER_HEURISTIC_UTILS_H_ #define CONTENT_BROWSER_TPCD_HEURISTICS_OPENER_HEURISTIC_UTILS_H_ +#include <map> + +#include "base/types/optional_ref.h" #include "content/common/content_export.h" -#include "content/public/browser/cookie_access_details.h" class GURL; namespace content { +class BtmRedirectContext; +struct CookieAccessDetails; + // Common identity providers that open pop-ups, to help estimate the impact of // third-party cookie blocking and prioritize mitigations. These values are // emitted in metrics and should not be renumbered. @@ -40,6 +45,18 @@ inline OptionalBool ToOptionalBool(bool b) { CONTENT_EXPORT OptionalBool IsAdTaggedCookieForHeuristics(const CookieAccessDetails& details); +// Returns a map of (site, (url, has_current_interaction)) for all URLs in the +// current redirect chain that satisfy the redirect heuristic. This performs +// all checks except for the presence of a past interaction, which should be +// checked by the caller using the BTM database. If `allowed_sites` is present, +// only sites in `allowed_sites` should be included. +CONTENT_EXPORT std::map<std::string, std::pair<GURL, bool>> +GetRedirectHeuristicURLs( + const BtmRedirectContext& committed_redirect_context, + const GURL& first_party_url, + base::optional_ref<std::set<std::string>> allowed_sites, + bool require_current_interaction); + } // namespace content #endif // CONTENT_BROWSER_TPCD_HEURISTICS_OPENER_HEURISTIC_UTILS_H_ diff --git a/content/browser/tpcd_heuristics/opener_heuristic_utils_unittest.cc b/content/browser/tpcd_heuristics/opener_heuristic_utils_unittest.cc index 9d17268329e9f..2bc1eea2dcb9a 100644 --- a/content/browser/tpcd_heuristics/opener_heuristic_utils_unittest.cc +++ b/content/browser/tpcd_heuristics/opener_heuristic_utils_unittest.cc @@ -5,11 +5,68 @@ #include "content/browser/tpcd_heuristics/opener_heuristic_utils.h" #include "base/test/scoped_feature_list.h" +#include "components/content_settings/core/common/features.h" +#include "content/browser/btm/btm_bounce_detector.h" +#include "content/public/browser/btm_redirect_info.h" #include "content/public/browser/cookie_access_details.h" +#include "net/http/http_status_code.h" #include "services/network/public/cpp/features.h" +#include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "url/gurl.h" +namespace { + +using content::BtmDataAccessType; +using content::BtmRedirectChainInfoPtr; +using content::BtmRedirectInfo; +using content::BtmRedirectInfoPtr; +using content::UrlAndSourceId; + +UrlAndSourceId MakeUrlAndId(std::string_view url) { + return UrlAndSourceId(GURL(url), ukm::AssignNewSourceId()); +} + +using ChainPair = + std::pair<BtmRedirectChainInfoPtr, std::vector<BtmRedirectInfoPtr>>; + +void AppendChainPair(std::vector<ChainPair>& vec, + std::vector<BtmRedirectInfoPtr> redirects, + BtmRedirectChainInfoPtr chain) { + vec.emplace_back(std::move(chain), std::move(redirects)); +} + +std::vector<BtmRedirectInfoPtr> MakeServerRedirects( + std::vector<std::string> urls, + BtmDataAccessType access_type = BtmDataAccessType::kReadWrite) { + std::vector<BtmRedirectInfoPtr> redirects; + for (const auto& url : urls) { + redirects.push_back(BtmRedirectInfo::CreateForServer( + /*url=*/MakeUrlAndId(url), + /*access_type=*/access_type, + /*time=*/base::Time::Now(), + /*was_response_cached=*/false, + /*response_code=*/net::HTTP_FOUND, + /*server_bounce_delay=*/base::TimeDelta())); + } + return redirects; +} + +BtmRedirectInfoPtr MakeClientRedirect( + std::string url, + BtmDataAccessType access_type = BtmDataAccessType::kReadWrite, + bool has_sticky_activation = false, + bool has_web_authn_assertion = false) { + return BtmRedirectInfo::CreateForClient( + /*url=*/MakeUrlAndId(url), + /*access_type=*/access_type, + /*time=*/base::Time::Now(), + /*client_bounce_delay=*/base::Seconds(1), + /*has_sticky_activation=*/has_sticky_activation, + /*web_authn_assertion_request_succeeded*/ has_web_authn_assertion); +} +} // namespace + namespace content { TEST(OpenerHeuristicUtilsTest, GetPopupProvider) { @@ -67,4 +124,116 @@ TEST(IsAdTaggedCookieForHeuristics, ReturnsCorrectlyWithoutExperimentParam) { EXPECT_EQ(IsAdTaggedCookieForHeuristics(details), OptionalBool::kUnknown); } +TEST(BtmRedirectContextTest, GetRedirectHeuristicURLs_NoRequirements) { + base::test::ScopedFeatureList features; + features.InitAndEnableFeatureWithParameters( + content_settings::features::kTpcdHeuristicsGrants, + {{"TpcdRedirectHeuristicRequireABAFlow", "false"}}); + + UrlAndSourceId first_party_url = MakeUrlAndId("http://a.test/"); + UrlAndSourceId current_interaction_url = MakeUrlAndId("http://b.test/"); + GURL no_current_interaction_url("http://c.test/"); + + std::vector<ChainPair> chains; + BtmRedirectContext context( + base::BindRepeating(AppendChainPair, std::ref(chains)), base::DoNothing(), + UrlAndSourceId(), + /*redirect_prefix_count=*/0); + + context.AppendCommitted(first_party_url, + {MakeServerRedirects({"http://c.test"})}, + current_interaction_url, false); + context.AppendCommitted( + MakeClientRedirect("http://b.test/", BtmDataAccessType::kNone, + /*has_sticky_activation=*/true), + {}, first_party_url, false); + + ASSERT_EQ(context.size(), 2u); + + std::map<std::string, std::pair<GURL, bool>> + sites_to_url_and_current_interaction = + GetRedirectHeuristicURLs(context, first_party_url.url, std::nullopt, + /*require_current_interaction=*/false); + EXPECT_THAT( + sites_to_url_and_current_interaction, + testing::UnorderedElementsAre( + std::pair<std::string, std::pair<GURL, bool>>( + "b.test", std::make_pair(current_interaction_url.url, true)), + std::pair<std::string, std::pair<GURL, bool>>( + "c.test", std::make_pair(no_current_interaction_url, false)))); +} + +TEST(BtmRedirectContextTest, GetRedirectHeuristicURLs_RequireABAFlow) { + base::test::ScopedFeatureList features; + features.InitAndEnableFeatureWithParameters( + content_settings::features::kTpcdHeuristicsGrants, + {{"TpcdRedirectHeuristicRequireABAFlow", "true"}}); + + UrlAndSourceId first_party_url = MakeUrlAndId("http://a.test/"); + GURL aba_url("http://b.test/"); + GURL no_aba_url("http://c.test/"); + + std::vector<ChainPair> chains; + BtmRedirectContext context( + base::BindRepeating(AppendChainPair, std::ref(chains)), base::DoNothing(), + UrlAndSourceId(), + /*redirect_prefix_count=*/0); + + context.AppendCommitted( + first_party_url, + {MakeServerRedirects({"http://b.test", "http://c.test"})}, + first_party_url, false); + + ASSERT_EQ(context.size(), 2u); + + std::set<std::string> allowed_sites = {GetSiteForBtm(aba_url)}; + + std::map<std::string, std::pair<GURL, bool>> + sites_to_url_and_current_interaction = + GetRedirectHeuristicURLs(context, first_party_url.url, allowed_sites, + /*require_current_interaction=*/false); + EXPECT_THAT(sites_to_url_and_current_interaction, + testing::UnorderedElementsAre( + std::pair<std::string, std::pair<GURL, bool>>( + "b.test", std::make_pair(aba_url, false)))); +} + +TEST(BtmRedirectContextTest, + GetRedirectHeuristicURLs_RequireCurrentInteraction) { + base::test::ScopedFeatureList features; + features.InitAndEnableFeatureWithParameters( + content_settings::features::kTpcdHeuristicsGrants, + {{"TpcdRedirectHeuristicRequireABAFlow", "false"}}); + + UrlAndSourceId first_party_url = MakeUrlAndId("http://a.test/"); + UrlAndSourceId current_interaction_url = MakeUrlAndId("http://b.test/"); + GURL no_current_interaction_url("http://c.test/"); + + std::vector<ChainPair> chains; + BtmRedirectContext context( + base::BindRepeating(AppendChainPair, std::ref(chains)), base::DoNothing(), + UrlAndSourceId(), + /*redirect_prefix_count=*/0); + + context.AppendCommitted(first_party_url, + {MakeServerRedirects({"http://c.test"})}, + current_interaction_url, false); + context.AppendCommitted( + MakeClientRedirect("http://b.test/", BtmDataAccessType::kNone, + /*has_sticky_activation=*/false, true), + {}, first_party_url, false); + + ASSERT_EQ(context.size(), 2u); + + std::map<std::string, std::pair<GURL, bool>> + sites_to_url_and_current_interaction = + GetRedirectHeuristicURLs(context, first_party_url.url, std::nullopt, + /*require_current_interaction=*/true); + EXPECT_THAT( + sites_to_url_and_current_interaction, + testing::UnorderedElementsAre( + std::pair<std::string, std::pair<GURL, bool>>( + "b.test", std::make_pair(current_interaction_url.url, true)))); +} + } // namespace content diff --git a/content/browser/tpcd_heuristics/redirect_heuristic_tab_helper.cc b/content/browser/tpcd_heuristics/redirect_heuristic_tab_helper.cc index 3d72d9a72fa96..160b27b8df5b8 100644 --- a/content/browser/tpcd_heuristics/redirect_heuristic_tab_helper.cc +++ b/content/browser/tpcd_heuristics/redirect_heuristic_tab_helper.cc @@ -207,11 +207,11 @@ void RedirectHeuristicTabHelper::CreateAllRedirectHeuristicGrants( } std::map<std::string, std::pair<GURL, bool>> - sites_to_url_and_current_interaction = - detector_->CommittedRedirectContext().GetRedirectHeuristicURLs( - first_party_url, sites_with_aba_flow, - content_settings::features:: - kTpcdRedirectHeuristicRequireCurrentInteraction.Get()); + sites_to_url_and_current_interaction = GetRedirectHeuristicURLs( + detector_->CommittedRedirectContext(), first_party_url, + sites_with_aba_flow, + content_settings::features:: + kTpcdRedirectHeuristicRequireCurrentInteraction.Get()); for (const auto& kv : sites_to_url_and_current_interaction) { auto [url, is_current_interaction] = kv.second;