0

[3/N] Sample debugging only reports.

Pass a bool forDebuggingOnlyInCooldownOrLockout to generateBid() and
scoreAd() through their browserSignals, if features
kBiddingAndScoringDebugReportingAPI and kFledgeSampleDebugReports are enabled.

Bug: b/310944302

Change-Id: I0c3e91891a1b570f3aec170abe27d26403285090
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5099563
Reviewed-by: Alexis Menard <alexis.menard@intel.com>
Reviewed-by: Nate Chapin <japhet@chromium.org>
Commit-Queue: Qingxin Wu <qingxinwu@google.com>
Reviewed-by: Russ Hamilton <behamilton@google.com>
Reviewed-by: Ken Buchanan <kenrb@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1238170}
This commit is contained in:
Qingxin Wu
2023-12-15 18:57:47 +00:00
committed by Chromium LUCI CQ
parent 24fa6a01bf
commit 09c36fe3f1
23 changed files with 446 additions and 62 deletions

@ -18,7 +18,6 @@
#include "content/browser/interest_group/auction_metrics_recorder.h"
#include "content/browser/interest_group/auction_nonce_manager.h"
#include "content/browser/interest_group/interest_group_auction_reporter.h"
#include "content/browser/interest_group/interest_group_features.h"
#include "content/browser/interest_group/interest_group_manager_impl.h"
#include "content/browser/renderer_host/frame_tree_node.h"
#include "content/public/browser/browser_context.h"
@ -520,7 +519,10 @@ void AuctionRunner::OnLoadInterestGroupsComplete(bool success) {
return;
}
if (base::FeatureList::IsEnabled(features::kFledgeSampleDebugReports)) {
if (base::FeatureList::IsEnabled(
blink::features::kBiddingAndScoringDebugReportingAPI) &&
base::FeatureList::IsEnabled(
blink::features::kFledgeSampleDebugReports)) {
// All sellers and buyers in the auction.
base::flat_set<url::Origin> origins = auction_.GetSellersAndBuyers();
// Use a weak pointer here so that

@ -45,7 +45,6 @@
#include "content/browser/interest_group/debuggable_auction_worklet.h"
#include "content/browser/interest_group/debuggable_auction_worklet_tracker.h"
#include "content/browser/interest_group/interest_group_auction.h"
#include "content/browser/interest_group/interest_group_features.h"
#include "content/browser/interest_group/interest_group_k_anonymity_manager.h"
#include "content/browser/interest_group/interest_group_manager_impl.h"
#include "content/browser/interest_group/interest_group_storage.h"
@ -1033,6 +1032,70 @@ std::string MakeAuctionScriptSupportsTie() {
kSellerDebugWinReportBaseUrl, kPostAuctionSignalsPlaceholder);
}
// Report generateBid()'s browserSignals.forDebuggingOnlyInCooldownOrLockout.
std::string MakeBidScriptWithForDebuggingOnlyInCooldownOrLockout() {
constexpr char kBidScript[] = R"(
function generateBid(
interestGroup, auctionSignals, perBuyerSignals, trustedBiddingSignals,
browserSignals) {
let bid = 1;
if (browserSignals.hasOwnProperty(
'forDebuggingOnlyInCooldownOrLockout')) {
bid = browserSignals.forDebuggingOnlyInCooldownOrLockout === true ? 10 :
11;
}
return {bid, render: interestGroup.ads[0].renderURL,
allowComponentAuction: true};
}
function reportWin(
auctionSignals, perBuyerSignals, sellerSignals, browserSignals) {
let forDebuggingOnlyInCooldownOrLockout = 'null';
if (browserSignals.bid == 10) {
forDebuggingOnlyInCooldownOrLockout = 'true';
} else if (browserSignals.bid == 11) {
forDebuggingOnlyInCooldownOrLockout = 'false';
}
sendReportTo(
'https://buyer-reporting.com/?forDebuggingOnlyInCooldownOrLockout=' +
forDebuggingOnlyInCooldownOrLockout);
}
)";
return base::StringPrintf(kBidScript);
}
// Report scoreAd()'s browserSignals.forDebuggingOnlyInCooldownOrLockout.
std::string MakeAuctionScriptWithForDebuggingOnlyInCooldownOrLockout() {
constexpr char kAuctionScript[] = R"(
function scoreAd(
adMetadata, bid, auctionConfig, trustedScoringSignals, browserSignals) {
let score = 1;
if (browserSignals.hasOwnProperty(
'forDebuggingOnlyInCooldownOrLockout')) {
score = browserSignals.forDebuggingOnlyInCooldownOrLockout === true ?
10 : 11;
}
return {desirability: score, allowComponentAuction: true};
}
function reportResult(auctionConfig, browserSignals) {
let reportUrl = browserSignals.topLevelSeller !== undefined ?
'https://component-seller-reporting.com/' :
'https://seller-reporting.com/';
let forDebuggingOnlyInCooldownOrLockout = 'null';
if (browserSignals.desirability == 10) {
forDebuggingOnlyInCooldownOrLockout = 'true';
} else if (browserSignals.desirability == 11) {
forDebuggingOnlyInCooldownOrLockout = 'false';
}
sendReportTo(
reportUrl + '?forDebuggingOnlyInCooldownOrLockout=' +
forDebuggingOnlyInCooldownOrLockout);
}
)";
return base::StringPrintf(kAuctionScript);
}
// Represents an entry in trusted bidding signal's `perInterestGroupData` field.
struct BiddingSignalsPerInterestGroupData {
std::string interest_group_name;
@ -1763,9 +1826,37 @@ class AuctionRunnerTest : public RenderViewHostTestHarness,
ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>();
base::Time now = base::Time::Now();
interest_group_manager_->RecordDebugReportLockout(now);
// Add previous wins and bids to the interest group manager.
bool sample_debug_reports =
base::FeatureList::IsEnabled(
blink::features::kBiddingAndScoringDebugReportingAPI) &&
base::FeatureList::IsEnabled(
blink::features::kFledgeSampleDebugReports);
if (sample_debug_reports) {
if (seller_decision_logic_url == kSellerUrlDebugReportLockout) {
interest_group_manager_->RecordDebugReportLockout(now);
} else if (seller_decision_logic_url == kSellerUrlDebugReportCooldown) {
interest_group_manager_->RecordDebugReportCooldown(
url::Origin::Create(seller_decision_logic_url), now,
DebugReportCooldownType::kShortCooldown);
}
}
// Add previous wins, bids, and debug report cooldowns to the interest group
// manager.
for (auto& bidder : bidders) {
if (sample_debug_reports) {
if (bidder.interest_group.name == kBidderNameDebugReportShortCooldown ||
bidder.interest_group.name ==
kBidderNameDebugReportRestrictedCooldown) {
DebugReportCooldownType type =
bidder.interest_group.name == kBidderNameDebugReportShortCooldown
? DebugReportCooldownType::kShortCooldown
: DebugReportCooldownType::kRestrictedCooldown;
interest_group_manager_->RecordDebugReportCooldown(
bidder.interest_group.owner, now, type);
}
}
for (int i = 0; i < bidder.bidding_browser_signals->join_count; i++) {
interest_group_manager_->JoinInterestGroup(
bidder.interest_group, bidder.joining_origin.GetURL());
@ -1785,9 +1876,6 @@ class AuctionRunnerTest : public RenderViewHostTestHarness,
// match those in `prev_wins`.
task_environment()->FastForwardBy(base::Seconds(1));
}
interest_group_manager_->RecordDebugReportCooldown(
bidder.interest_group.owner, now,
DebugReportCooldownType::kShortCooldown);
for (const auto& kanon_data : bidder.bidding_ads_kanon) {
interest_group_manager_->UpdateKAnonymity(kanon_data);
@ -2030,7 +2118,7 @@ class AuctionRunnerTest : public RenderViewHostTestHarness,
storage_group.interest_group = std::move(interest_group);
storage_group.bidding_browser_signals =
auction_worklet::mojom::BiddingBrowserSignals::New(
3, 5, std::move(previous_wins));
3, 5, std::move(previous_wins), false);
storage_group.joining_origin = storage_group.interest_group.owner;
return storage_group;
}
@ -3011,6 +3099,10 @@ class AuctionRunnerTest : public RenderViewHostTestHarness,
url::Origin::Create(GURL("https://frame.origin.test"));
const GURL kSellerUrl{"https://adstuff.publisher1.com/auction.js"};
const url::Origin kSeller = url::Origin::Create(kSellerUrl);
const GURL kSellerUrlDebugReportLockout{
"https://lockout.publisher.com/auction.js"};
const GURL kSellerUrlDebugReportCooldown{
"https://cooldown.publisher.com/auction.js"};
absl::optional<GURL> trusted_scoring_signals_url_;
const GURL kComponentSeller1Url{"https://component.seller1.test/foo.js"};
@ -3032,6 +3124,10 @@ class AuctionRunnerTest : public RenderViewHostTestHarness,
const std::string kBidder2Name{"Another Ad Thing"};
const InterestGroupKey kBidder2Key{kBidder2, kBidder2Name};
const GURL kBidder2TrustedSignalsUrl{"https://anotheradthing.com/signals2"};
const std::string kBidderNameDebugReportShortCooldown{
"Short Debug Report Cooldown"};
const std::string kBidderNameDebugReportRestrictedCooldown{
"Restricted Debug Report Cooldown"};
const base::TimeDelta kAllBuyersCumulativeTimeout = base::Milliseconds(23456);
@ -18880,7 +18976,7 @@ class AuctionRunnerSampleDebugReportsEnabledTest : public AuctionRunnerTest {
feature_list_.InitWithFeatures(
/*enabled_features=*/{blink::features::
kBiddingAndScoringDebugReportingAPI,
features::kFledgeSampleDebugReports},
blink::features::kFledgeSampleDebugReports},
/*disabled_features=*/{});
}
@ -18934,6 +19030,97 @@ TEST_F(AuctionRunnerSampleDebugReportsEnabledTest, ForDebuggingOnlyReporting) {
"https://seller-debug-win-reporting.com/2"));
}
TEST_F(AuctionRunnerSampleDebugReportsEnabledTest,
BrowserSignalForDebuggingOnlyInCooldownOrLockout) {
const struct {
const std::string bidder_name;
const GURL seller_url;
const std::string bidder_in_cooldown_or_lockout;
const std::string seller_in_cooldown_or_lockout;
} kTestCases[] = {
// Not under lockout. Bidder and seller origins are not under cooldown.
{kBidder1Name, kSellerUrl, "false", "false"},
// Under lockout. Bidder and seller origins are not under cooldown.
{kBidder1Name, kSellerUrlDebugReportLockout, "true", "true"},
// Under lockout. Bidder origin is under cooldown.
{kBidderNameDebugReportShortCooldown, kSellerUrlDebugReportLockout,
"true", "true"},
// Not under lockout. Bidder and seller origins are under cooldown.
{kBidderNameDebugReportShortCooldown, kSellerUrlDebugReportCooldown,
"true", "true"},
{kBidderNameDebugReportRestrictedCooldown, kSellerUrlDebugReportCooldown,
"true", "true"},
// Not under lockout. Bidder origin is under cooldown, seller origin not.
{kBidderNameDebugReportShortCooldown, kSellerUrl, "true", "false"},
// Not under lockout. Seller origin is under cooldown, bidder origin not.
{kBidder1Name, kSellerUrlDebugReportCooldown, "false", "true"},
};
for (const auto& test_case : kTestCases) {
SCOPED_TRACE(test_case.bidder_name);
auction_worklet::AddJavascriptResponse(
&url_loader_factory_, kBidder1Url,
MakeBidScriptWithForDebuggingOnlyInCooldownOrLockout());
auction_worklet::AddJavascriptResponse(
&url_loader_factory_, test_case.seller_url,
MakeAuctionScriptWithForDebuggingOnlyInCooldownOrLockout());
std::vector<StorageInterestGroup> bidders;
bidders.emplace_back(MakeInterestGroup(
kBidder1, test_case.bidder_name, kBidder1Url,
/*trusted_bidding_signals_url=*/absl::nullopt,
/*trusted_bidding_signals_keys=*/{}, GURL("https://ad1.com")));
RunAuctionAndWait(test_case.seller_url, std::move(bidders));
EXPECT_THAT(result_.errors, testing::ElementsAre());
EXPECT_THAT(result_.report_urls,
testing::UnorderedElementsAre(
"https://buyer-reporting.com/"
"?forDebuggingOnlyInCooldownOrLockout=" +
test_case.bidder_in_cooldown_or_lockout,
"https://seller-reporting.com/"
"?forDebuggingOnlyInCooldownOrLockout=" +
test_case.seller_in_cooldown_or_lockout));
}
}
// Sellers are under cooldown, and the bidder is not.
TEST_F(AuctionRunnerSampleDebugReportsEnabledTest,
ComponentAuctionBrowserSignalForDebuggingOnlyInCooldownOrLockout) {
interest_group_buyers_.emplace();
component_auctions_.emplace_back(
CreateAuctionConfig(kSellerUrlDebugReportCooldown, {{kBidder1}}));
auction_worklet::AddJavascriptResponse(
&url_loader_factory_, kSellerUrlDebugReportCooldown,
MakeAuctionScriptWithForDebuggingOnlyInCooldownOrLockout());
auction_worklet::AddJavascriptResponse(
&url_loader_factory_, kBidder1Url,
MakeBidScriptWithForDebuggingOnlyInCooldownOrLockout());
auction_worklet::AddJavascriptResponse(
&url_loader_factory_, kSellerUrlDebugReportCooldown,
MakeAuctionScriptWithForDebuggingOnlyInCooldownOrLockout());
std::vector<StorageInterestGroup> bidders;
bidders.emplace_back(MakeInterestGroup(
kBidder1, kBidder1Name, kBidder1Url,
/*trusted_bidding_signals_url=*/absl::nullopt,
/*trusted_bidding_signals_keys=*/{}, GURL("https://ad1.com")));
RunAuctionAndWait(kSellerUrlDebugReportCooldown, std::move(bidders));
EXPECT_THAT(result_.errors, testing::UnorderedElementsAre());
EXPECT_EQ(GURL("https://ad1.com/"), result_.ad_descriptor->url);
EXPECT_THAT(result_.report_urls,
testing::UnorderedElementsAre(
"https://buyer-reporting.com/"
"?forDebuggingOnlyInCooldownOrLockout=false",
"https://seller-reporting.com/"
"?forDebuggingOnlyInCooldownOrLockout=true",
"https://component-seller-reporting.com/"
"?forDebuggingOnlyInCooldownOrLockout=true"));
}
// Disable private aggregation API.
class AuctionRunnerPrivateAggregationAPIDisabledTest
: public AuctionRunnerTest {

@ -354,6 +354,7 @@ class MockSellerWorklet : public auction_worklet::mojom::SellerWorklet {
const GURL& browser_signal_render_url,
const std::vector<GURL>& browser_signal_ad_components,
uint32_t browser_signal_bidding_duration_msecs,
bool browser_signal_for_debugging_only_in_cooldown_or_lockout,
const absl::optional<base::TimeDelta> seller_timeout,
uint64_t trace_id,
mojo::PendingRemote<auction_worklet::mojom::ScoreAdClient>

@ -74,6 +74,7 @@
#include "services/network/public/mojom/client_security_state.mojom.h"
#include "services/network/public/mojom/url_loader_factory.mojom-forward.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/features_generated.h"
#include "third_party/blink/public/common/interest_group/ad_auction_constants.h"
#include "third_party/blink/public/common/interest_group/ad_auction_currencies.h"
@ -971,7 +972,6 @@ class InterestGroupAuction::BuyerHelper
state->pa_timings(PrivateAggregationPhase::kBidder).script_run_time =
bidding_latency;
auction_->ReportBiddingLatency(interest_group, bidding_latency);
// This is intentionally recorded here as opposed to in
// OnGenerateBidCompleteInternal in order to exclude bids that were
// filtered during reprioritization. It also excludes those bids that
@ -990,6 +990,15 @@ class InterestGroupAuction::BuyerHelper
errors);
}
void SetForDebuggingOnlyInCooldownOrLockout(
bool for_debugging_only_in_cooldown_or_lockout) {
for (auto& bid_state : bid_states_) {
bid_state->bidder->bidding_browser_signals
->for_debugging_only_in_cooldown_or_lockout =
for_debugging_only_in_cooldown_or_lockout;
}
}
// Closes all Mojo pipes, releases all weak pointers, and stops the timeout
// timer.
void ClosePipes() {
@ -2193,8 +2202,7 @@ void InterestGroupAuction::StartBiddingAndScoringPhase(
*trace_id_);
if (debug_report_lockout_and_cooldowns.has_value()) {
debug_report_lockout_and_cooldowns_ =
std::move(debug_report_lockout_and_cooldowns);
debug_report_lockout_and_cooldowns_ = *debug_report_lockout_and_cooldowns;
}
on_seller_receiver_callback_ = std::move(on_seller_receiver_callback);
@ -2256,10 +2264,19 @@ void InterestGroupAuction::StartBiddingAndScoringPhase(
: base::BindOnce(
&InterestGroupAuction::OnComponentSellerWorkletReceived,
base::Unretained(this));
component_auction->StartBiddingAndScoringPhase(
absl::nullopt, std::move(component_on_seller_receiver_callback),
base::BindOnce(&InterestGroupAuction::OnComponentAuctionComplete,
base::Unretained(this), component_auction));
if (debug_report_lockout_and_cooldowns.has_value()) {
component_auction->StartBiddingAndScoringPhase(
*debug_report_lockout_and_cooldowns,
std::move(component_on_seller_receiver_callback),
base::BindOnce(&InterestGroupAuction::OnComponentAuctionComplete,
base::Unretained(this), component_auction));
} else {
component_auction->StartBiddingAndScoringPhase(
/*debug_report_lockout_and_cooldowns=*/absl::nullopt,
std::move(component_on_seller_receiver_callback),
base::BindOnce(&InterestGroupAuction::OnComponentAuctionComplete,
base::Unretained(this), component_auction));
}
}
// If there are no local auctions then we need to request the top-level
// seller worklet. In the case where there are any local auctions this will
@ -2270,6 +2287,8 @@ void InterestGroupAuction::StartBiddingAndScoringPhase(
}
for (const auto& buyer_helper : buyer_helpers_) {
buyer_helper->SetForDebuggingOnlyInCooldownOrLockout(
IsForDebuggingOnlyInLockoutOrCooldown(buyer_helper->owner()));
buyer_helper->StartGeneratingBids();
}
@ -4033,7 +4052,8 @@ void InterestGroupAuction::ScoreBidIfReady(std::unique_ptr<Bid> bid) {
: absl::nullopt,
bid_raw->interest_group->owner, bid_raw->ad_descriptor.url,
bid_raw->GetAdComponentUrls(), bid_raw->bid_duration.InMilliseconds(),
SellerTimeout(), bid_trace_id, std::move(score_ad_remote));
IsForDebuggingOnlyInLockoutOrCooldown(config_->seller), SellerTimeout(),
bid_trace_id, std::move(score_ad_remote));
}
bool InterestGroupAuction::ValidateScoreBidCompleteResult(
@ -4759,6 +4779,34 @@ void InterestGroupAuction::OnDirectFromSellerSignalHeaderAdSlotResolved(
}
}
bool InterestGroupAuction::IsForDebuggingOnlyInLockoutOrCooldown(
const url::Origin& origin) {
if (!debug_report_lockout_and_cooldowns_.has_value()) {
return false;
}
base::Time now = base::Time::Now();
if (debug_report_lockout_and_cooldowns_->last_report_sent_time.has_value() &&
*debug_report_lockout_and_cooldowns_->last_report_sent_time +
blink::features::kFledgeDebugReportLockout.Get() >=
now) {
return true;
}
const auto cooldown_it =
debug_report_lockout_and_cooldowns_->debug_report_cooldown_map.find(
origin);
if (cooldown_it !=
debug_report_lockout_and_cooldowns_->debug_report_cooldown_map.end()) {
absl::optional<base::TimeDelta> duration =
ConvertDebugReportCooldownTypeToDuration(cooldown_it->second.type);
if (duration.has_value() &&
cooldown_it->second.starting_time + *duration >= now) {
return true;
}
}
return false;
}
// static
data_decoder::DataDecoder* InterestGroupAuction::GetDataDecoder(
base::WeakPtr<InterestGroupAuction> instance) {

@ -1184,6 +1184,8 @@ class CONTENT_EXPORT InterestGroupAuction
std::string ad_slot,
scoped_refptr<HeaderDirectFromSellerSignals::Result> signals);
bool IsForDebuggingOnlyInLockoutOrCooldown(const url::Origin& origin);
static data_decoder::DataDecoder* GetDataDecoder(
base::WeakPtr<InterestGroupAuction> instance);

@ -22,22 +22,6 @@ BASE_FEATURE(kEnableUpdatingExecutionModeToFrozenContext,
"EnableUpdatingExecutionModeToFrozenContext",
base::FEATURE_DISABLED_BY_DEFAULT);
// Enable sampling forDebuggingOnly reports.
BASE_FEATURE(kFledgeSampleDebugReports,
"FledgeSampleDebugReports",
base::FEATURE_DISABLED_BY_DEFAULT);
// Prevent ad techs who accidentally call the API repeatedly for all users,
// from locking themselves out of sending any more debug reports for years.
// This is accomplished by most of the time putting that ad tech in a shorter
// cooldown period, and only some time (e.g., 10% of the time) putting it in a
// restricted cooldown period.
const base::FeatureParam<base::TimeDelta> kFledgeDebugReportShortCooldown{
&kFledgeSampleDebugReports, "fledge_debug_report_short_cooldown",
base::Days(14)};
const base::FeatureParam<base::TimeDelta> kFledgeDebugReportRestrictedCooldown{
&kFledgeSampleDebugReports, "fledge_debug_report_restricted_cooldown",
base::Days(365)};
// Enable updating userBiddingSignals when updating a user's interests groups.
BASE_FEATURE(kEnableUpdatingUserBiddingSignals,
"EnableUpdatingUserBiddingSignals",

@ -16,12 +16,6 @@ namespace features {
CONTENT_EXPORT BASE_DECLARE_FEATURE(kEnableIFrameAdAuctionHeaders);
CONTENT_EXPORT BASE_DECLARE_FEATURE(
kEnableUpdatingExecutionModeToFrozenContext);
CONTENT_EXPORT BASE_DECLARE_FEATURE(kFledgeSampleDebugReports);
CONTENT_EXPORT extern const base::FeatureParam<base::TimeDelta>
kFledgeDebugReportShortCooldown;
CONTENT_EXPORT extern const base::FeatureParam<base::TimeDelta>
kFledgeDebugReportRestrictedCooldown;
CONTENT_EXPORT BASE_DECLARE_FEATURE(kEnableUpdatingUserBiddingSignals);
} // namespace features

@ -27,7 +27,6 @@
#include "content/browser/devtools/devtools_instrumentation.h"
#include "content/browser/devtools/network_service_devtools_observer.h"
#include "content/browser/interest_group/interest_group_caching_storage.h"
#include "content/browser/interest_group/interest_group_features.h"
#include "content/browser/interest_group/interest_group_storage.h"
#include "content/browser/interest_group/interest_group_update.h"
#include "content/browser/renderer_host/frame_tree_node.h"

@ -32,7 +32,6 @@
#include "base/strings/stringprintf.h"
#include "base/time/time.h"
#include "content/browser/interest_group/interest_group_ad.pb.h"
#include "content/browser/interest_group/interest_group_features.h"
#include "content/browser/interest_group/interest_group_k_anonymity_manager.h"
#include "content/browser/interest_group/interest_group_update.h"
#include "content/browser/interest_group/storage_interest_group.h"
@ -163,16 +162,6 @@ absl::optional<GURL> DeserializeURL(const std::string& serialized_url) {
return result;
}
absl::optional<base::TimeDelta> ConvertCooldownTypeToDuration(int type) {
switch (type) {
case DebugReportCooldownType::kShortCooldown:
return features::kFledgeDebugReportShortCooldown.Get();
case DebugReportCooldownType::kRestrictedCooldown:
return features::kFledgeDebugReportRestrictedCooldown.Get();
}
return absl::nullopt;
}
blink::InterestGroup::Ad FromInterestGroupAdValue(const base::Value::Dict& dict,
bool for_components) {
blink::InterestGroup::Ad result;
@ -943,14 +932,14 @@ bool UpgradeV21SchemaToV22(sql::Database& db, sql::MetaTable& meta_table) {
if (!db.Execute(kDropLockoutTableTableSql)) {
return false;
}
static const char kLockoutDebuggingOnlyReportTableSql[] =
static const char kLockoutDebugReportTableSql[] =
// clang-format off
"CREATE TABLE lockout_debugging_only_report("
"id INTEGER NOT NULL,"
"last_report_sent_time INTEGER NOT NULL,"
"PRIMARY KEY(id))";
// clang-format on
if (!db.Execute(kLockoutDebuggingOnlyReportTableSql)) {
if (!db.Execute(kLockoutDebugReportTableSql)) {
return false;
}
@ -959,7 +948,7 @@ bool UpgradeV21SchemaToV22(sql::Database& db, sql::MetaTable& meta_table) {
if (!db.Execute(kDropCooldownTableSql)) {
return false;
}
static const char kCooldownDebuggingOnlyReportTableSql[] =
static const char kCooldownDebugReportTableSql[] =
// clang-format off
"CREATE TABLE cooldown_debugging_only_report("
"origin TEXT NOT NULL,"
@ -967,7 +956,7 @@ bool UpgradeV21SchemaToV22(sql::Database& db, sql::MetaTable& meta_table) {
"type INTEGER NOT NULL,"
"PRIMARY KEY(origin))";
// clang-format on
if (!db.Execute(kCooldownDebuggingOnlyReportTableSql)) {
if (!db.Execute(kCooldownDebugReportTableSql)) {
return false;
}
@ -3872,10 +3861,10 @@ bool DeleteExpiredDebugReportCooldown(sql::Database& db, base::Time now) {
delete_cooldown.Reset(true);
absl::optional<base::TimeDelta> short_duration =
ConvertCooldownTypeToDuration(
ConvertDebugReportCooldownTypeToDuration(
static_cast<int>(DebugReportCooldownType::kShortCooldown));
absl::optional<base::TimeDelta> restricted_duration =
ConvertCooldownTypeToDuration(
ConvertDebugReportCooldownTypeToDuration(
static_cast<int>(DebugReportCooldownType::kRestrictedCooldown));
CHECK(short_duration.has_value());
CHECK(restricted_duration.has_value());

@ -23,7 +23,6 @@
#include "base/test/simple_test_clock.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "content/browser/interest_group/interest_group_features.h"
#include "content/browser/interest_group/interest_group_update.h"
#include "content/browser/interest_group/storage_interest_group.h"
#include "content/services/auction_worklet/public/mojom/bidder_worklet.mojom.h"
@ -824,7 +823,8 @@ TEST_F(InterestGroupStorageTest, DeleteExpiredDebugReportCooldown) {
// expire. Fast-forward extra time to make sure the cooldown expires,
// because the starting_time is ceiled to its nearest hour.
task_environment().FastForwardBy(
features::kFledgeDebugReportShortCooldown.Get() + expected_time - time);
blink::features::kFledgeDebugReportShortCooldown.Get() + expected_time -
time);
// If maintenance has not been triggered yet, the cooldown table will not be
// updated.
cooldowns = storage->GetDebugReportLockoutAndCooldowns(origins);

@ -362,6 +362,7 @@ void MockSellerWorklet::ScoreAd(
const GURL& browser_signal_render_url,
const std::vector<GURL>& browser_signal_ad_components,
uint32_t browser_signal_bidding_duration_msecs,
bool browser_signal_for_debugging_only_in_cooldown_or_lockout,
const absl::optional<base::TimeDelta> seller_timeout,
uint64_t trace_id,
mojo::PendingRemote<auction_worklet::mojom::ScoreAdClient>

@ -263,6 +263,7 @@ class MockSellerWorklet : public auction_worklet::mojom::SellerWorklet {
const GURL& browser_signal_render_url,
const std::vector<GURL>& browser_signal_ad_components,
uint32_t browser_signal_bidding_duration_msecs,
bool browser_signal_for_debugging_only_in_cooldown_or_lockout,
const absl::optional<base::TimeDelta> seller_timeout,
uint64_t trace_id,
mojo::PendingRemote<auction_worklet::mojom::ScoreAdClient>

@ -5,7 +5,10 @@
#include "content/browser/interest_group/storage_interest_group.h"
#include "base/ranges/algorithm.h"
#include "base/time/time.h"
#include "content/services/auction_worklet/public/mojom/bidder_worklet.mojom.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/interest_group/interest_group.h"
namespace content {
@ -33,4 +36,15 @@ DebugReportLockoutAndCooldowns::DebugReportLockoutAndCooldowns(
DebugReportLockoutAndCooldowns&&) = default;
DebugReportLockoutAndCooldowns::~DebugReportLockoutAndCooldowns() = default;
absl::optional<base::TimeDelta> ConvertDebugReportCooldownTypeToDuration(
int type) {
switch (type) {
case DebugReportCooldownType::kShortCooldown:
return blink::features::kFledgeDebugReportShortCooldown.Get();
case DebugReportCooldownType::kRestrictedCooldown:
return blink::features::kFledgeDebugReportRestrictedCooldown.Get();
}
return absl::nullopt;
}
} // namespace content

@ -12,6 +12,7 @@
#include "base/time/time.h"
#include "content/services/auction_worklet/public/mojom/bidder_worklet.mojom.h"
#include "mojo/public/cpp/bindings/struct_ptr.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/blink/public/common/interest_group/interest_group.h"
#include "url/gurl.h"
#include "url/origin.h"
@ -101,6 +102,11 @@ struct CONTENT_EXPORT DebugReportLockoutAndCooldowns {
std::map<url::Origin, DebugReportCooldown> debug_report_cooldown_map = {};
};
// Converts forDebuggingOnly API's cooldown type to its actual cooldown
// duration.
CONTENT_EXPORT absl::optional<base::TimeDelta>
ConvertDebugReportCooldownTypeToDuration(int type);
} // namespace content
#endif // CONTENT_BROWSER_INTEREST_GROUP_STORAGE_INTEREST_GROUP_H_

@ -1338,6 +1338,14 @@ BidderWorklet::V8State::GenerateSingleBid(
bidding_browser_signals->join_count) ||
!browser_signals_dict.Set("bidCount",
bidding_browser_signals->bid_count) ||
(base::FeatureList::IsEnabled(
blink::features::kBiddingAndScoringDebugReportingAPI) &&
base::FeatureList::IsEnabled(
blink::features::kFledgeSampleDebugReports) &&
!browser_signals_dict.Set(
"forDebuggingOnlyInCooldownOrLockout",
bidding_browser_signals
->for_debugging_only_in_cooldown_or_lockout)) ||
(base::FeatureList::IsEnabled(
blink::features::kFledgePassRecencyToGenerateBid) &&
!browser_signals_dict.Set("recency",

@ -330,6 +330,7 @@ class BidderWorkletTest : public testing::Test {
browser_signal_join_count_ = 2;
browser_signal_bid_count_ = 3;
browser_signal_for_debugging_only_in_cooldown_or_lockout_ = false;
browser_signal_prev_wins_.clear();
auction_signals_ = "[\"auction_signals\"]";
@ -631,7 +632,8 @@ class BidderWorkletTest : public testing::Test {
mojom::BiddingBrowserSignalsPtr CreateBiddingBrowserSignals() {
return mojom::BiddingBrowserSignals::New(
browser_signal_join_count_, browser_signal_bid_count_,
CloneWinList(browser_signal_prev_wins_));
CloneWinList(browser_signal_prev_wins_),
browser_signal_for_debugging_only_in_cooldown_or_lockout_);
}
// Create a BidderWorklet, returning the remote. If `out_bidder_worklet_impl`
@ -897,6 +899,7 @@ class BidderWorkletTest : public testing::Test {
interest_group_trusted_bidding_signals_keys_;
int browser_signal_join_count_;
int browser_signal_bid_count_;
bool browser_signal_for_debugging_only_in_cooldown_or_lockout_;
base::TimeDelta browser_signal_recency_generate_bid_;
std::vector<mojo::StructPtr<mojom::PreviousWin>> browser_signal_prev_wins_;
@ -3581,6 +3584,13 @@ TEST_F(BidderWorkletTest, GenerateBidBrowserSignalJoinCountBidCount) {
}
}
TEST_F(BidderWorkletTest,
GenerateBidBrowserSignalForDebuggingOnlyInCooldownOrLockout) {
RunGenerateBidExpectingExpressionIsTrue(R"(
!browserSignals.hasOwnProperty('forDebuggingOnlyInCooldownOrLockout');
)");
}
TEST_F(BidderWorkletTest, GenerateBidAds) {
// A bid URL that's not in the InterestGroup's ads list should fail.
RunGenerateBidWithReturnValueExpectingResult(
@ -6874,6 +6884,16 @@ TEST_F(BidderWorkletBiddingAndScoringDebugReportingAPIEnabledTest,
GURL("https://loss.url1"));
}
// forDebuggingOnlyInCooldownOrLockout is not passed to generateBid's browser
// signals if feature kBiddingAndScoringDebugReportingAPI is disabled, even if
// kFledgeSampleDebugReports is enabled.
TEST_F(BidderWorkletBiddingAndScoringDebugReportingAPIEnabledTest,
GenerateBidBrowserSignalForDebuggingOnlyInCooldownOrLockout) {
RunGenerateBidExpectingExpressionIsTrue(R"(
!browserSignals.hasOwnProperty('forDebuggingOnlyInCooldownOrLockout');
)");
}
TEST_F(BidderWorkletTest, ReportWinRegisterAdBeacon) {
base::flat_map<std::string, GURL> expected_ad_beacon_map = {
{"click", GURL("https://click.example.com/")},
@ -9258,5 +9278,31 @@ TEST_F(BidderWorkletAdMacroReportingEnabledTest,
}
}
class BidderWorkletSampleDebugReportsEnabledTest : public BidderWorkletTest {
public:
BidderWorkletSampleDebugReportsEnabledTest() {
scoped_feature_list_.InitWithFeatures(
/*enabled_features=*/{blink::features::
kBiddingAndScoringDebugReportingAPI,
blink::features::kFledgeSampleDebugReports},
/*disabled_features=*/{});
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
TEST_F(BidderWorkletSampleDebugReportsEnabledTest,
GenerateBidBrowserSignalForDebuggingOnlyInCooldownOrLockout) {
RunGenerateBidExpectingExpressionIsTrue(R"(
browserSignals.forDebuggingOnlyInCooldownOrLockout === false;
)");
browser_signal_for_debugging_only_in_cooldown_or_lockout_ = true;
RunGenerateBidExpectingExpressionIsTrue(R"(
browserSignals.forDebuggingOnlyInCooldownOrLockout === true;
)");
}
} // namespace
} // namespace auction_worklet

@ -95,6 +95,10 @@ struct BiddingBrowserSignals {
// Previous times the group won auctions.
array<PreviousWin> prev_wins;
// Whether the browser is under lockout or the buyer's origin is under
// cooldown for sending forDebuggingOnly reports.
bool for_debugging_only_in_cooldown_or_lockout;
};
// The results of running a FLEDGE generateBid() script.

@ -213,6 +213,10 @@ interface SellerWorklet {
// took to generate the bid. Taken as milliseconds to reduce granularity of
// timing information passed to an untrusted process.
//
// `browser_signal_for_debugging_only_in_cooldown_or_lockout` Whether the
// browser is under lockout or the seller's origin is under cooldown for
// sending forDebuggingOnly reports.
//
// `seller_timeout` Restrict the runtime of the seller's scoring script. Any
// timeout higher than 500 ms will be clamped to 500 ms before passing in as
// `seller_timeout`. Null if not provided by the publisher page. Null will be
@ -238,6 +242,7 @@ interface SellerWorklet {
url.mojom.Url browser_signal_render_url,
array<url.mojom.Url> browser_signal_ad_component_render_urls,
uint32 browser_signal_bidding_duration_msecs,
bool browser_signal_for_debugging_only_in_cooldown_or_lockout,
mojo_base.mojom.TimeDelta? seller_timeout,
uint64 trace_id,
pending_remote<ScoreAdClient> score_ad_client);

@ -675,6 +675,7 @@ void SellerWorklet::ScoreAd(
const GURL& browser_signal_render_url,
const std::vector<GURL>& browser_signal_ad_components,
uint32_t browser_signal_bidding_duration_msecs,
bool browser_signal_for_debugging_only_in_cooldown_or_lockout,
const absl::optional<base::TimeDelta> seller_timeout,
uint64_t trace_id,
mojo::PendingRemote<auction_worklet::mojom::ScoreAdClient>
@ -703,6 +704,8 @@ void SellerWorklet::ScoreAd(
}
score_ad_task->browser_signal_bidding_duration_msecs =
browser_signal_bidding_duration_msecs;
score_ad_task->browser_signal_for_debugging_only_in_cooldown_or_lockout =
browser_signal_for_debugging_only_in_cooldown_or_lockout;
score_ad_task->seller_timeout = seller_timeout;
score_ad_task->trace_id = trace_id;
score_ad_task->score_ad_client.Bind(std::move(score_ad_client));
@ -953,6 +956,7 @@ void SellerWorklet::V8State::ScoreAd(
const GURL& browser_signal_render_url,
const std::vector<std::string>& browser_signal_ad_components,
uint32_t browser_signal_bidding_duration_msecs,
bool browser_signal_for_debugging_only_in_cooldown_or_lockout,
const absl::optional<base::TimeDelta> seller_timeout,
uint64_t trace_id,
base::ScopedClosureRunner cleanup_score_ad_task,
@ -1027,7 +1031,14 @@ void SellerWorklet::V8State::ScoreAd(
blink::PrintableAdCurrency(bid_currency)) ||
(scoring_signals_data_version.has_value() &&
!browser_signals_dict.Set("dataVersion",
scoring_signals_data_version.value()))) {
scoring_signals_data_version.value())) ||
(base::FeatureList::IsEnabled(
blink::features::kBiddingAndScoringDebugReportingAPI) &&
base::FeatureList::IsEnabled(
blink::features::kFledgeSampleDebugReports) &&
!browser_signals_dict.Set(
"forDebuggingOnlyInCooldownOrLockout",
browser_signal_for_debugging_only_in_cooldown_or_lockout))) {
PostScoreAdCallbackToUserThreadOnError(
std::move(callback),
/*scoring_latency=*/elapsed_timer.Elapsed(),
@ -1903,6 +1914,7 @@ void SellerWorklet::ScoreAdIfReady(ScoreAdTaskList::iterator task) {
std::move(task->browser_signal_render_url),
std::move(task->browser_signal_ad_components),
task->browser_signal_bidding_duration_msecs,
task->browser_signal_for_debugging_only_in_cooldown_or_lockout,
std::move(task->seller_timeout), task->trace_id,
base::ScopedClosureRunner(std::move(cleanup_score_ad_task)),
base::BindOnce(&SellerWorklet::DeliverScoreAdCallbackOnUserThread,

@ -113,6 +113,7 @@ class CONTENT_EXPORT SellerWorklet : public mojom::SellerWorklet {
const GURL& browser_signal_render_url,
const std::vector<GURL>& browser_signal_ad_components,
uint32_t browser_signal_bidding_duration_msecs,
bool browser_signal_for_debugging_only_in_cooldown_or_lockout,
const absl::optional<base::TimeDelta> seller_timeout,
uint64_t trace_id,
mojo::PendingRemote<auction_worklet::mojom::ScoreAdClient>
@ -174,6 +175,7 @@ class CONTENT_EXPORT SellerWorklet : public mojom::SellerWorklet {
// ScoringSignals code with BidderWorklets.
std::vector<std::string> browser_signal_ad_components;
uint32_t browser_signal_bidding_duration_msecs;
bool browser_signal_for_debugging_only_in_cooldown_or_lockout;
absl::optional<base::TimeDelta> seller_timeout;
uint64_t trace_id;
@ -334,6 +336,7 @@ class CONTENT_EXPORT SellerWorklet : public mojom::SellerWorklet {
const GURL& browser_signal_render_url,
const std::vector<std::string>& browser_signal_ad_components,
uint32_t browser_signal_bidding_duration_msecs,
bool browser_signal_for_debugging_only_in_cooldown_or_lockout,
const absl::optional<base::TimeDelta> seller_timeout,
uint64_t trace_id,
base::ScopedClosureRunner cleanup_score_ad_task,

@ -244,8 +244,10 @@ class SellerWorkletTest : public testing::Test {
url::Origin::Create(GURL("https://interest.group.owner.test/"));
browser_signal_buyer_and_seller_reporting_id_ = absl::nullopt;
browser_signal_render_url_ = GURL("https://render.url.test/");
browser_signal_for_debugging_only_in_cooldown_or_lockout_ = false;
browser_signal_ad_components_.clear();
browser_signal_bidding_duration_msecs_ = 0;
browser_signal_for_debugging_only_in_cooldown_or_lockout_ = false;
browser_signal_desireability_ = 1;
seller_timeout_ = absl::nullopt;
browser_signal_highest_scoring_other_bid_ = 0;
@ -376,6 +378,7 @@ class SellerWorkletTest : public testing::Test {
browser_signals_other_seller_.Clone(), component_expect_bid_currency_,
browser_signal_interest_group_owner_, browser_signal_render_url_,
browser_signal_ad_components_, browser_signal_bidding_duration_msecs_,
browser_signal_for_debugging_only_in_cooldown_or_lockout_,
seller_timeout_,
/*trace_id=*/1,
TestScoreAdClient::Create(base::BindOnce(
@ -471,6 +474,7 @@ class SellerWorkletTest : public testing::Test {
browser_signals_other_seller_.Clone(), component_expect_bid_currency_,
browser_signal_interest_group_owner_, browser_signal_render_url_,
browser_signal_ad_components_, browser_signal_bidding_duration_msecs_,
browser_signal_for_debugging_only_in_cooldown_or_lockout_,
seller_timeout_,
/*trace_id=*/1,
TestScoreAdClient::Create(
@ -798,6 +802,7 @@ class SellerWorkletTest : public testing::Test {
GURL browser_signal_render_url_;
std::vector<GURL> browser_signal_ad_components_;
uint32_t browser_signal_bidding_duration_msecs_;
bool browser_signal_for_debugging_only_in_cooldown_or_lockout_;
double browser_signal_desireability_;
double browser_signal_highest_scoring_other_bid_;
absl::optional<blink::AdCurrency>
@ -3325,6 +3330,7 @@ TEST_F(SellerWorkletTest, ScriptIsolation) {
browser_signals_other_seller_.Clone(), component_expect_bid_currency_,
browser_signal_interest_group_owner_, browser_signal_render_url_,
browser_signal_ad_components_, browser_signal_bidding_duration_msecs_,
browser_signal_for_debugging_only_in_cooldown_or_lockout_,
seller_timeout_,
/*trace_id=*/1,
TestScoreAdClient::Create(base::BindLambdaForTesting(
@ -3400,6 +3406,7 @@ TEST_F(SellerWorkletTest, DeleteBeforeScoreAdCallback) {
browser_signals_other_seller_.Clone(), component_expect_bid_currency_,
browser_signal_interest_group_owner_, browser_signal_render_url_,
browser_signal_ad_components_, browser_signal_bidding_duration_msecs_,
browser_signal_for_debugging_only_in_cooldown_or_lockout_,
seller_timeout_,
/*trace_id=*/1,
TestScoreAdClient::Create(
@ -4076,6 +4083,7 @@ TEST_F(SellerWorkletTest, Cancelation) {
browser_signals_other_seller_.Clone(), component_expect_bid_currency_,
browser_signal_interest_group_owner_, browser_signal_render_url_,
browser_signal_ad_components_, browser_signal_bidding_duration_msecs_,
browser_signal_for_debugging_only_in_cooldown_or_lockout_,
seller_timeout_,
/*trace_id=*/1, client_receiver.BindNewPipeAndPassRemote());
@ -4137,6 +4145,7 @@ TEST_F(SellerWorkletTest, CancelBeforeFetch) {
browser_signals_other_seller_.Clone(), component_expect_bid_currency_,
browser_signal_interest_group_owner_, browser_signal_render_url_,
browser_signal_ad_components_, browser_signal_bidding_duration_msecs_,
browser_signal_for_debugging_only_in_cooldown_or_lockout_,
seller_timeout_,
/*trace_id=*/1, client_receiver.BindNewPipeAndPassRemote());
task_environment_.RunUntilIdle();
@ -4243,6 +4252,14 @@ TEST_F(SellerWorkletTest,
/*expected_report_url=*/absl::nullopt);
}
TEST_F(SellerWorkletTest,
ScoreAdBrowserSignalForDebuggingOnlyInCooldownOrLockout) {
RunScoreAdWithReturnValueExpectingResult(
R"(browserSignals.hasOwnProperty('forDebuggingOnlyInCooldownOrLockout') ?
3 : 0)",
0);
}
class SellerWorkletSharedStorageAPIDisabledTest : public SellerWorkletTest {
public:
SellerWorkletSharedStorageAPIDisabledTest() {
@ -4811,6 +4828,7 @@ TEST_F(SellerWorkletBiddingAndScoringDebugReportingAPIEnabledTest,
browser_signals_other_seller_.Clone(), component_expect_bid_currency_,
browser_signal_interest_group_owner_, browser_signal_render_url_,
browser_signal_ad_components_, browser_signal_bidding_duration_msecs_,
browser_signal_for_debugging_only_in_cooldown_or_lockout_,
seller_timeout_,
/*trace_id=*/1,
TestScoreAdClient::Create(base::BindLambdaForTesting(
@ -4843,6 +4861,40 @@ TEST_F(SellerWorkletBiddingAndScoringDebugReportingAPIEnabledTest,
}
}
TEST_F(SellerWorkletBiddingAndScoringDebugReportingAPIEnabledTest,
ScoreAdBrowserSignalForDebuggingOnlyInCooldownOrLockout) {
RunScoreAdWithReturnValueExpectingResult(
R"(browserSignals.hasOwnProperty('forDebuggingOnlyInCooldownOrLockout') ?
3 : 0)",
0);
}
class SellerWorkletSampleDebugReportsEnabledTest : public SellerWorkletTest {
public:
SellerWorkletSampleDebugReportsEnabledTest() {
scoped_feature_list_.InitWithFeatures(
/*enabled_features=*/{blink::features::
kBiddingAndScoringDebugReportingAPI,
blink::features::kFledgeSampleDebugReports},
/*disabled_features=*/{});
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
TEST_F(SellerWorkletSampleDebugReportsEnabledTest,
ScoreAdBrowserSignalForDebuggingOnlyInCooldownOrLockout) {
RunScoreAdWithReturnValueExpectingResult(
R"(browserSignals.forDebuggingOnlyInCooldownOrLockout === false ? 3 : 0)",
3);
browser_signal_for_debugging_only_in_cooldown_or_lockout_ = true;
RunScoreAdWithReturnValueExpectingResult(
R"(browserSignals.forDebuggingOnlyInCooldownOrLockout === true ? 3 : 0)",
3);
}
class SellerWorkletPrivateAggregationEnabledTest : public SellerWorkletTest {
public:
SellerWorkletPrivateAggregationEnabledTest() {

@ -873,6 +873,19 @@ BASE_FEATURE(kFledgePassRecencyToGenerateBid,
"FledgePassRecencyToGenerateBid",
base::FEATURE_ENABLED_BY_DEFAULT);
BASE_FEATURE(kFledgeSampleDebugReports,
"FledgeSampleDebugReports",
base::FEATURE_DISABLED_BY_DEFAULT);
const base::FeatureParam<base::TimeDelta> kFledgeDebugReportLockout{
&kFledgeSampleDebugReports, "fledge_debug_report_lockout",
base::Days(365 * 3)};
const base::FeatureParam<base::TimeDelta> kFledgeDebugReportRestrictedCooldown{
&kFledgeSampleDebugReports, "fledge_debug_report_restricted_cooldown",
base::Days(365)};
const base::FeatureParam<base::TimeDelta> kFledgeDebugReportShortCooldown{
&kFledgeSampleDebugReports, "fledge_debug_report_short_cooldown",
base::Days(14)};
BASE_FEATURE(kForceDeferScriptIntervention,
"ForceDeferScriptIntervention",
base::FEATURE_DISABLED_BY_DEFAULT);

@ -466,6 +466,19 @@ BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kFledgeEnforceKAnonymity);
BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kFledgePassKAnonStatusToReportWin);
BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kFledgePassRecencyToGenerateBid);
BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kFledgeSampleDebugReports);
BLINK_COMMON_EXPORT extern const base::FeatureParam<base::TimeDelta>
kFledgeDebugReportLockout;
// Prevent ad techs who accidentally call the API repeatedly for all users,
// from locking themselves out of sending any more debug reports for years.
// This is accomplished by most of the time putting that ad tech in a shorter
// cooldown period, and only some time (e.g., 10% of the time) putting it in a
// restricted cooldown period.
BLINK_COMMON_EXPORT extern const base::FeatureParam<base::TimeDelta>
kFledgeDebugReportRestrictedCooldown;
BLINK_COMMON_EXPORT extern const base::FeatureParam<base::TimeDelta>
kFledgeDebugReportShortCooldown;
BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kForceWebContentsDarkMode);
BLINK_COMMON_EXPORT extern const base::FeatureParam<ForceDarkInversionMethod>
kForceDarkInversionMethodParam;