0

Move GetRedirectHeuristicURLs from btm/ to tpcd_heuristics/

This function is only used by the heuristics, not by BTM, so this
location is more appropriate.

Bug: 40286530
Change-Id: Ice2f8d1b21b71c478b865bfbd20dbe09f4bb385e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6470824
Reviewed-by: Anton Maliev <amaliev@chromium.org>
Commit-Queue: Anton Maliev <amaliev@chromium.org>
Auto-Submit: Svend Larsen <svend@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1449637}
This commit is contained in:
Svend Larsen
2025-04-21 15:15:32 -07:00
committed by Chromium LUCI CQ
parent 8c5acb173f
commit cb60a6ad7b
7 changed files with 242 additions and 173 deletions

@ -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();

@ -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.

@ -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;

@ -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

@ -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_

@ -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

@ -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;