#include "content/services/auction_worklet/seller_worklet.h"
#include <algorithm>
#include <array>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "base/containers/flat_map.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_view_util.h"
#include "base/strings/stringprintf.h"
#include "base/synchronization/waitable_event.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/test/values_test_util.h"
#include "base/test/with_feature_override.h"
#include "base/time/time.h"
#include "components/cbor/writer.h"
#include "content/public/test/shared_storage_test_utils.h"
#include "content/services/auction_worklet/auction_v8_helper.h"
#include "content/services/auction_worklet/public/cpp/auction_downloader.h"
#include "content/services/auction_worklet/public/cpp/auction_worklet_features.h"
#include "content/services/auction_worklet/public/cpp/cbor_test_util.h"
#include "content/services/auction_worklet/public/cpp/real_time_reporting.h"
#include "content/services/auction_worklet/public/mojom/auction_network_events_handler.mojom.h"
#include "content/services/auction_worklet/public/mojom/auction_worklet_service.mojom.h"
#include "content/services/auction_worklet/public/mojom/in_progress_auction_download.mojom.h"
#include "content/services/auction_worklet/public/mojom/private_aggregation_request.mojom.h"
#include "content/services/auction_worklet/public/mojom/real_time_reporting.mojom.h"
#include "content/services/auction_worklet/public/mojom/seller_worklet.mojom.h"
#include "content/services/auction_worklet/public/mojom/trusted_signals_cache.mojom.h"
#include "content/services/auction_worklet/trusted_signals_kvv2_manager.h"
#include "content/services/auction_worklet/worklet_devtools_debug_test_util.h"
#include "content/services/auction_worklet/worklet_test_util.h"
#include "content/services/auction_worklet/worklet_util.h"
#include "content/services/auction_worklet/worklet_v8_debug_test_util.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "mojo/public/cpp/bindings/unique_receiver_set.h"
#include "net/http/http_status_code.h"
#include "net/third_party/quiche/src/quiche/oblivious_http/oblivious_http_gateway.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/mojom/shared_storage.mojom.h"
#include "services/network/test/test_url_loader_factory.h"
#include "testing/gmock/include/gmock/gmock-matchers.h"
#include "testing/gtest/include/gtest/gtest.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_currencies.h"
#include "third_party/blink/public/common/interest_group/ad_display_size.h"
#include "third_party/blink/public/common/interest_group/auction_config.h"
#include "third_party/blink/public/mojom/interest_group/interest_group_types.mojom.h"
#include "url/gurl.h"
#include "url/origin.h"
using testing::HasSubstr;
using testing::StartsWith;
namespace auction_worklet {
namespace {
using PrivateAggregationRequests = SellerWorklet::PrivateAggregationRequests;
using RealTimeReportingContributions =
SellerWorklet::RealTimeReportingContributions;
using content::MojomAppendMethod;
using content::MojomClearMethod;
using content::MojomDeleteMethod;
using content::MojomSetMethod;
constexpr base::TimeDelta kTinyTime = base::Microseconds(1);
const char kTrustedScoringSignalsResponse[] = R"(
{
"renderUrls": {"https://render.url.test/": 4},
"adComponentRenderURLs": {
"https://component1.test/": 1,
"https://component2.test/": 2
}
}
)";
constexpr auto kTestPrivateKey = std::to_array<uint8_t>({
0xff, 0x1f, 0x47, 0xb1, 0x68, 0xb6, 0xb9, 0xea, 0x65, 0xf7, 0x97,
0x4f, 0xf2, 0x2e, 0xf2, 0x36, 0x94, 0xe2, 0xf6, 0xb6, 0x8d, 0x66,
0xf3, 0xa7, 0x64, 0x14, 0x28, 0xd4, 0x45, 0x35, 0x01, 0x8f,
});
constexpr auto kTestPublicKey = std::to_array<uint8_t>({
0xa1, 0x5f, 0x40, 0x65, 0x86, 0xfa, 0xc4, 0x7b, 0x99, 0x59, 0x70,
0xf1, 0x85, 0xd9, 0xd8, 0x91, 0xc7, 0x4d, 0xcf, 0x1e, 0xb9, 0x1a,
0x7d, 0x50, 0xa5, 0x8b, 0x01, 0x68, 0x3e, 0x60, 0x05, 0x2d,
});
const uint8_t kKeyId = 0xFF;
std::string CreateScoreAdScript(const std::string& raw_return_value,
const std::string& extra_code = std::string()) {
constexpr char kSellAdScript[] = R"(
function scoreAd(adMetadata, bid, auctionConfig, trustedScoringSignals,
browserSignals, directFromSellerSignals, crossOriginTrustedSignals) {
%s;
return %s;
}
)";
return base::StringPrintf(kSellAdScript, extra_code.c_str(),
raw_return_value.c_str());
}
std::string CreateBasicSellAdScript() {
return CreateScoreAdScript("1");
}
std::string CreateReportToScript(
const std::string& raw_return_value,
const std::string& extra_code = std::string()) {
constexpr char kBasicSellerScript[] = R"(
function reportResult(auctionConfig, browserSignals,
directFromSellerSignals) {
%s;
return %s;
}
)";
return CreateBasicSellAdScript() +
base::StringPrintf(kBasicSellerScript, extra_code.c_str(),
raw_return_value.c_str());
}
class TestScoreAdClient : public mojom::ScoreAdClient {
public:
using ScoreAdCompleteCallback = base::OnceCallback<void(
double score,
mojom::RejectReason reject_reason,
mojom::ComponentAuctionModifiedBidParamsPtr
component_auction_modified_bid_params,
std::optional<double> bid_in_seller_currency,
std::optional<uint32_t> scoring_signals_data_version,
const std::optional<GURL>& debug_loss_report_url,
const std::optional<GURL>& debug_win_report_url,
PrivateAggregationRequests pa_requests,
RealTimeReportingContributions real_time_contributions,
mojom::SellerTimingMetricsPtr score_ad_timing_metrics,
mojom::ScoreAdDependencyLatenciesPtr score_ad_dependency_latencies,
const std::vector<std::string>& errors)>;
explicit TestScoreAdClient(ScoreAdCompleteCallback score_ad_complete_callback)
: score_ad_complete_callback_(std::move(score_ad_complete_callback)) {}
~TestScoreAdClient() override = default;
static mojo::PendingRemote<mojom::ScoreAdClient> Create(
ScoreAdCompleteCallback score_ad_complete_callback) {
mojo::PendingRemote<mojom::ScoreAdClient> client_remote;
mojo::MakeSelfOwnedReceiver(std::make_unique<TestScoreAdClient>(
std::move(score_ad_complete_callback)),
client_remote.InitWithNewPipeAndPassReceiver());
return client_remote;
}
void OnScoreAdComplete(
double score,
mojom::RejectReason reject_reason,
mojom::ComponentAuctionModifiedBidParamsPtr
component_auction_modified_bid_params,
std::optional<double> bid_in_seller_currency,
std::optional<uint32_t> scoring_signals_data_version,
const std::optional<GURL>& debug_loss_report_url,
const std::optional<GURL>& debug_win_report_url,
PrivateAggregationRequests pa_requests,
RealTimeReportingContributions real_time_contributions,
mojom::SellerTimingMetricsPtr score_ad_timing_metrics,
mojom::ScoreAdDependencyLatenciesPtr score_ad_dependency_latencies,
const std::vector<std::string>& errors) override {
std::move(score_ad_complete_callback_)
.Run(score, reject_reason,
std::move(component_auction_modified_bid_params),
std::move(bid_in_seller_currency),
std::move(scoring_signals_data_version), debug_loss_report_url,
debug_win_report_url, std::move(pa_requests),
std::move(real_time_contributions),
std::move(score_ad_timing_metrics),
std::move(score_ad_dependency_latencies), errors);
}
static ScoreAdCompleteCallback ScoreAdNeverInvokedCallback() {
return base::BindOnce(
[](double score, mojom::RejectReason reject_reason,
mojom::ComponentAuctionModifiedBidParamsPtr
component_auction_modified_bid_params,
std::optional<double> bid_in_seller_currency,
std::optional<uint32_t> scoring_signals_data_version,
const std::optional<GURL>& debug_loss_report_url,
const std::optional<GURL>& debug_win_report_url,
PrivateAggregationRequests pa_requests,
RealTimeReportingContributions real_time_contributions,
mojom::SellerTimingMetricsPtr score_ad_timing_metrics,
mojom::ScoreAdDependencyLatenciesPtr score_ad_dependency_latencies,
const std::vector<std::string>& errors) {
ADD_FAILURE() << "Callback should not be invoked";
});
}
private:
ScoreAdCompleteCallback score_ad_complete_callback_;
};
class SellerWorkletTest : public testing::Test,
public mojom::LoadSellerWorkletClient {
public:
explicit SellerWorkletTest(
base::test::TaskEnvironment::TimeSource time_mode =
base::test::TaskEnvironment::TimeSource::MOCK_TIME)
: task_environment_(time_mode) {
feature_list_.InitWithFeatures(
{blink::features::
kFledgeTrustedSignalsKVv1CreativeScanning,
features::kFledgeTextConversionHelpers},
{});
SetDefaultParameters();
}
~SellerWorkletTest() override = default;
void SetUp() override {
while (v8_helpers_.size() < NumThreads()) {
v8_helpers_.push_back(
AuctionV8Helper::Create(AuctionV8Helper::CreateTaskRunner()));
}
shared_storage_hosts_.resize(NumThreads());
}
void TearDown() override {
v8_helpers_.clear();
task_environment_.RunUntilIdle();
EXPECT_FALSE(disconnect_reason_);
}
virtual size_t NumThreads() { return 1u; }
void SetDefaultParameters() {
ad_metadata_ = "[1]";
bid_ = 1;
bid_currency_ = std::nullopt;
decision_logic_url_ = GURL("https://url.test/");
trusted_scoring_signals_url_.reset();
auction_ad_config_non_shared_params_ =
blink::AuctionConfig::NonSharedParams();
top_window_origin_ = url::Origin::Create(GURL("https://window.test/"));
permissions_policy_state_ =
mojom::AuctionWorkletPermissionsPolicyState::New(
true,
false);
experiment_group_id_ = std::nullopt;
send_creative_scanning_metadata_ = std::nullopt;
public_key_ = nullptr;
browser_signals_other_seller_.reset();
component_expect_bid_currency_ = std::nullopt;
browser_signal_interest_group_owner_ =
url::Origin::Create(GURL("https://interest.group.owner.test/"));
browser_signal_buyer_and_seller_reporting_id_ = std::nullopt;
browser_signal_selected_buyer_and_seller_reporting_id_ = std::nullopt;
browser_signal_render_url_ = GURL("https://render.url.test/");
creative_scanning_metadata_ = std::nullopt;
component_ads_.clear();
browser_signal_bidding_duration_msecs_ = 0;
browser_signal_render_size_ = std::nullopt;
browser_signal_for_debugging_only_in_cooldown_or_lockout_ = false;
browser_signal_for_debugging_only_sampling_ = false;
browser_signal_desireability_ = 1;
seller_timeout_ = std::nullopt;
bidder_joining_origin_ = url::Origin::Create(GURL("https://joining.test/"));
browser_signal_highest_scoring_other_bid_ = 0;
browser_signal_highest_scoring_other_bid_currency_ = std::nullopt;
}
auction_worklet::mojom::CreativeInfoWithoutOwnerPtr MainAd() const {
return auction_worklet::mojom::CreativeInfoWithoutOwner::New(
blink::AdDescriptor(browser_signal_render_url_,
browser_signal_render_size_),
creative_scanning_metadata_);
}
std::vector<auction_worklet::mojom::CreativeInfoWithoutOwnerPtr>
ComponentAds() const {
std::vector<auction_worklet::mojom::CreativeInfoWithoutOwnerPtr> out;
for (const auto& entry : component_ads_) {
out.push_back(entry->Clone());
}
return out;
}
void RunScoreAdWithReturnValueExpectingResult(
const std::string& raw_return_value,
double expected_score,
const std::vector<std::string>& expected_errors =
std::vector<std::string>(),
mojom::ComponentAuctionModifiedBidParamsPtr
expected_component_auction_modified_bid_params =
mojom::ComponentAuctionModifiedBidParamsPtr(),
std::optional<uint32_t> expected_data_version = std::nullopt,
const std::optional<GURL>& expected_debug_loss_report_url = std::nullopt,
const std::optional<GURL>& expected_debug_win_report_url = std::nullopt,
mojom::RejectReason expected_reject_reason =
mojom::RejectReason::kNotAvailable,
PrivateAggregationRequests expected_pa_requests = {},
RealTimeReportingContributions expected_real_time_contributions = {},
std::optional<double> expected_bid_in_seller_currency = std::nullopt) {
RunScoreAdWithJavascriptExpectingResult(
CreateScoreAdScript(raw_return_value), expected_score, expected_errors,
std::move(expected_component_auction_modified_bid_params),
expected_data_version, expected_debug_loss_report_url,
expected_debug_win_report_url, expected_reject_reason,
std::move(expected_pa_requests),
std::move(expected_real_time_contributions),
expected_bid_in_seller_currency);
}
void RunScoreAdWithReturnValueExpectingResultInExactTime(
const std::string& raw_return_value,
double expected_score,
mojom::ComponentAuctionModifiedBidParamsPtr
expected_component_auction_modified_bid_params,
base::TimeDelta expected_duration,
const std::vector<std::string>& expected_errors = {},
std::optional<uint32_t> expected_data_version = std::nullopt,
const std::optional<GURL>& expected_debug_loss_report_url = std::nullopt,
const std::optional<GURL>& expected_debug_win_report_url = std::nullopt,
mojom::RejectReason expected_reject_reason =
mojom::RejectReason::kNotAvailable,
PrivateAggregationRequests expected_pa_requests = {},
RealTimeReportingContributions expected_real_time_contributions = {},
std::optional<double> expected_bid_in_seller_currency = std::nullopt) {
AddJavascriptResponse(&url_loader_factory_, decision_logic_url_,
CreateScoreAdScript(raw_return_value),
extra_js_headers_);
auto seller_worklet = CreateWorklet();
base::RunLoop run_loop;
RunScoreAdOnWorkletAsync(
seller_worklet.get(), expected_score, expected_errors,
std::move(expected_component_auction_modified_bid_params),
expected_data_version, expected_debug_loss_report_url,
expected_debug_win_report_url, expected_reject_reason,
std::move(expected_pa_requests),
std::move(expected_real_time_contributions),
expected_bid_in_seller_currency,
false,
std::nullopt,
std::nullopt, run_loop.QuitClosure());
task_environment_.FastForwardBy(expected_duration - kTinyTime);
EXPECT_FALSE(run_loop.AnyQuitCalled());
task_environment_.FastForwardBy(kTinyTime);
EXPECT_TRUE(run_loop.AnyQuitCalled());
}
void RunScoreAdWithJavascriptExpectingResult(
const std::string& javascript,
double expected_score,
const std::vector<std::string>& expected_errors =
std::vector<std::string>(),
mojom::ComponentAuctionModifiedBidParamsPtr
expected_component_auction_modified_bid_params =
mojom::ComponentAuctionModifiedBidParamsPtr(),
std::optional<uint32_t> expected_data_version = std::nullopt,
const std::optional<GURL>& expected_debug_loss_report_url = std::nullopt,
const std::optional<GURL>& expected_debug_win_report_url = std::nullopt,
mojom::RejectReason expected_reject_reason =
mojom::RejectReason::kNotAvailable,
PrivateAggregationRequests expected_pa_requests = {},
RealTimeReportingContributions expected_real_time_contributions = {},
std::optional<double> expected_bid_in_seller_currency = std::nullopt) {
SCOPED_TRACE(javascript);
AddJavascriptResponse(&url_loader_factory_, decision_logic_url_, javascript,
extra_js_headers_);
RunScoreAdExpectingResult(
expected_score, expected_errors,
std::move(expected_component_auction_modified_bid_params),
expected_data_version, expected_debug_loss_report_url,
expected_debug_win_report_url, expected_reject_reason,
std::move(expected_pa_requests),
std::move(expected_real_time_contributions),
expected_bid_in_seller_currency);
}
void RunScoreAdOnWorkletAsync(
mojom::SellerWorklet* seller_worklet,
double expected_score,
const std::vector<std::string>& expected_errors,
mojom::ComponentAuctionModifiedBidParamsPtr
expected_component_auction_modified_bid_params,
std::optional<uint32_t> expected_data_version,
const std::optional<GURL>& expected_debug_loss_report_url,
const std::optional<GURL>& expected_debug_win_report_url,
mojom::RejectReason expected_reject_reason,
PrivateAggregationRequests expected_pa_requests,
RealTimeReportingContributions expected_real_time_contributions,
std::optional<double> expected_bid_in_seller_currency,
bool expected_score_ad_timeout,
std::optional<base::TimeDelta> expected_signals_fetch_latency,
std::optional<base::TimeDelta> expected_code_ready_latency,
base::OnceClosure done_closure) {
seller_worklet->ScoreAd(
ad_metadata_, bid_, bid_currency_, auction_ad_config_non_shared_params_,
trusted_signals_cache_key_.Clone(), MainAd(), ComponentAds(),
direct_from_seller_seller_signals_,
direct_from_seller_seller_signals_header_ad_slot_,
direct_from_seller_auction_signals_,
direct_from_seller_auction_signals_header_ad_slot_,
browser_signals_other_seller_.Clone(), component_expect_bid_currency_,
browser_signal_interest_group_owner_,
browser_signal_selected_buyer_and_seller_reporting_id_,
browser_signal_buyer_and_seller_reporting_id_,
browser_signal_bidding_duration_msecs_,
browser_signal_for_debugging_only_in_cooldown_or_lockout_,
browser_signal_for_debugging_only_sampling_, seller_timeout_,
group_by_origin_id_, allow_group_by_origin_mode_,
1, bidder_joining_origin_,
TestScoreAdClient::Create(base::BindOnce(
[](double expected_score,
mojom::RejectReason expected_reject_reason,
mojom::ComponentAuctionModifiedBidParamsPtr
expected_component_auction_modified_bid_params,
std::optional<uint32_t> expected_data_version,
const std::optional<GURL>& expected_debug_loss_report_url,
const std::optional<GURL>& expected_debug_win_report_url,
PrivateAggregationRequests expected_pa_requests,
RealTimeReportingContributions expected_real_time_contributions,
std::optional<double> expected_bid_in_seller_currency,
std::optional<base::TimeDelta> expected_score_ad_timeout,
std::optional<base::TimeDelta> expected_signals_fetch_latency,
std::optional<base::TimeDelta> expected_code_ready_latency,
std::vector<std::string> expected_errors,
base::OnceClosure done_closure, double score,
mojom::RejectReason reject_reason,
mojom::ComponentAuctionModifiedBidParamsPtr
component_auction_modified_bid_params,
std::optional<double> bid_in_seller_currency,
std::optional<uint32_t> scoring_signals_data_version,
const std::optional<GURL>& debug_loss_report_url,
const std::optional<GURL>& debug_win_report_url,
PrivateAggregationRequests pa_requests,
RealTimeReportingContributions real_time_contributions,
mojom::SellerTimingMetricsPtr score_ad_timing_metrics,
mojom::ScoreAdDependencyLatenciesPtr
score_ad_dependency_latencies,
const std::vector<std::string>& errors) {
EXPECT_EQ(expected_score, score);
EXPECT_EQ(static_cast<int>(expected_reject_reason),
static_cast<int>(reject_reason));
EXPECT_EQ(
expected_component_auction_modified_bid_params.is_null(),
component_auction_modified_bid_params.is_null());
if (!expected_component_auction_modified_bid_params.is_null() &&
!component_auction_modified_bid_params.is_null()) {
EXPECT_EQ(expected_component_auction_modified_bid_params->ad,
component_auction_modified_bid_params->ad);
EXPECT_EQ(expected_component_auction_modified_bid_params->bid,
component_auction_modified_bid_params->bid);
}
EXPECT_EQ(expected_debug_loss_report_url, debug_loss_report_url);
EXPECT_EQ(expected_debug_win_report_url, debug_win_report_url);
EXPECT_EQ(expected_data_version, scoring_signals_data_version);
EXPECT_EQ(expected_pa_requests, pa_requests);
EXPECT_EQ(expected_real_time_contributions,
real_time_contributions);
EXPECT_EQ(expected_bid_in_seller_currency,
bid_in_seller_currency);
if (expected_score_ad_timeout) {
EXPECT_GE(score_ad_timing_metrics->script_latency,
expected_score_ad_timeout.value() * 0.9);
}
EXPECT_EQ(expected_score_ad_timeout.has_value(),
score_ad_timing_metrics->script_timed_out);
if (expected_signals_fetch_latency) {
EXPECT_EQ(score_ad_dependency_latencies
->trusted_scoring_signals_latency,
expected_signals_fetch_latency);
}
if (expected_code_ready_latency) {
EXPECT_EQ(score_ad_dependency_latencies->code_ready_latency,
expected_code_ready_latency);
}
EXPECT_EQ(expected_errors, errors);
std::move(done_closure).Run();
},
expected_score, expected_reject_reason,
std::move(expected_component_auction_modified_bid_params),
expected_data_version, expected_debug_loss_report_url,
expected_debug_win_report_url, std::move(expected_pa_requests),
std::move(expected_real_time_contributions),
expected_bid_in_seller_currency,
expected_score_ad_timeout
? std::make_optional(
seller_timeout_.value_or(AuctionV8Helper::kScriptTimeout))
: std::nullopt,
expected_signals_fetch_latency, expected_code_ready_latency,
expected_errors, std::move(done_closure))));
}
void RunScoreAdOnWorkletExpectingCallbackNeverInvoked(
mojom::SellerWorklet* seller_worklet) {
seller_worklet->ScoreAd(
ad_metadata_, bid_, bid_currency_, auction_ad_config_non_shared_params_,
trusted_signals_cache_key_.Clone(), MainAd(), ComponentAds(),
direct_from_seller_seller_signals_,
direct_from_seller_seller_signals_header_ad_slot_,
direct_from_seller_auction_signals_,
direct_from_seller_auction_signals_header_ad_slot_,
browser_signals_other_seller_.Clone(), component_expect_bid_currency_,
browser_signal_interest_group_owner_,
browser_signal_selected_buyer_and_seller_reporting_id_,
browser_signal_buyer_and_seller_reporting_id_,
browser_signal_bidding_duration_msecs_,
browser_signal_for_debugging_only_in_cooldown_or_lockout_,
browser_signal_for_debugging_only_sampling_, seller_timeout_,
group_by_origin_id_, allow_group_by_origin_mode_,
1, bidder_joining_origin_,
TestScoreAdClient::Create(
TestScoreAdClient::ScoreAdNeverInvokedCallback()));
}
void RunScoreAdExpectingResultOnWorklet(
mojom::SellerWorklet* seller_worklet,
double expected_score,
const std::vector<std::string>& expected_errors =
std::vector<std::string>(),
mojom::ComponentAuctionModifiedBidParamsPtr
expected_component_auction_modified_bid_params =
mojom::ComponentAuctionModifiedBidParamsPtr(),
std::optional<uint32_t> expected_data_version = std::nullopt,
const std::optional<GURL>& expected_debug_loss_report_url = std::nullopt,
const std::optional<GURL>& expected_debug_win_report_url = std::nullopt,
mojom::RejectReason expected_reject_reason =
mojom::RejectReason::kNotAvailable,
PrivateAggregationRequests expected_pa_requests = {},
RealTimeReportingContributions expected_real_time_contributions = {},
std::optional<double> expected_bid_in_seller_currency = std::nullopt) {
base::RunLoop run_loop;
RunScoreAdOnWorkletAsync(
seller_worklet, expected_score, expected_errors,
std::move(expected_component_auction_modified_bid_params),
expected_data_version, expected_debug_loss_report_url,
expected_debug_win_report_url, expected_reject_reason,
std::move(expected_pa_requests),
std::move(expected_real_time_contributions),
expected_bid_in_seller_currency,
false,
std::nullopt,
std::nullopt, run_loop.QuitClosure());
run_loop.Run();
}
void RunScoreAdExpectingResult(
double expected_score,
const std::vector<std::string>& expected_errors =
std::vector<std::string>(),
mojom::ComponentAuctionModifiedBidParamsPtr
expected_component_auction_modified_bid_params =
mojom::ComponentAuctionModifiedBidParamsPtr(),
std::optional<uint32_t> expected_data_version = std::nullopt,
const std::optional<GURL>& expected_debug_loss_report_url = std::nullopt,
const std::optional<GURL>& expected_debug_win_report_url = std::nullopt,
mojom::RejectReason expected_reject_reason =
mojom::RejectReason::kNotAvailable,
PrivateAggregationRequests expected_pa_requests = {},
RealTimeReportingContributions expected_real_time_contributions = {},
std::optional<double> expected_bid_in_seller_currency = std::nullopt) {
auto seller_worklet = CreateWorklet();
ASSERT_TRUE(seller_worklet);
RunScoreAdExpectingResultOnWorklet(
seller_worklet.get(), expected_score, expected_errors,
std::move(expected_component_auction_modified_bid_params),
expected_data_version, expected_debug_loss_report_url,
expected_debug_win_report_url, expected_reject_reason,
std::move(expected_pa_requests),
std::move(expected_real_time_contributions),
expected_bid_in_seller_currency);
}
void RunReportResultCreatedScriptExpectingResult(
const std::string& raw_return_value,
const std::string& extra_code,
const std::optional<std::string>& expected_signals_for_winner,
const std::optional<GURL>& expected_report_url,
const base::flat_map<std::string, GURL>& expected_ad_beacon_map =
base::flat_map<std::string, GURL>(),
PrivateAggregationRequests expected_pa_requests = {},
const std::vector<std::string>& expected_errors =
std::vector<std::string>()) {
RunReportResultWithJavascriptExpectingResult(
CreateReportToScript(raw_return_value, extra_code),
expected_signals_for_winner, expected_report_url,
expected_ad_beacon_map, std::move(expected_pa_requests),
expected_errors);
}
void RunReportResultWithJavascriptExpectingResult(
const std::string& javascript,
const std::optional<std::string>& expected_signals_for_winner,
const std::optional<GURL>& expected_report_url,
const base::flat_map<std::string, GURL>& expected_ad_beacon_map =
base::flat_map<std::string, GURL>(),
PrivateAggregationRequests expected_pa_requests = {},
const std::vector<std::string>& expected_errors =
std::vector<std::string>()) {
SCOPED_TRACE(javascript);
AddJavascriptResponse(&url_loader_factory_, decision_logic_url_,
javascript);
RunReportResultExpectingResult(
expected_signals_for_winner, expected_report_url,
expected_ad_beacon_map, std::move(expected_pa_requests),
false, expected_errors);
}
void RunReportResultExpectingResultAsync(
mojom::SellerWorklet* seller_worklet,
const std::optional<std::string>& expected_signals_for_winner,
const std::optional<GURL>& expected_report_url,
const base::flat_map<std::string, GURL>& expected_ad_beacon_map,
PrivateAggregationRequests expected_pa_requests,
bool expected_reporting_latency_timeout,
const std::vector<std::string>& expected_errors,
base::OnceClosure done_closure) {
seller_worklet->ReportResult(
auction_ad_config_non_shared_params_,
direct_from_seller_seller_signals_,
direct_from_seller_seller_signals_header_ad_slot_,
direct_from_seller_auction_signals_,
direct_from_seller_auction_signals_header_ad_slot_,
browser_signals_other_seller_.Clone(),
browser_signal_interest_group_owner_,
browser_signal_buyer_and_seller_reporting_id_,
browser_signal_selected_buyer_and_seller_reporting_id_,
browser_signal_render_url_, bid_, bid_currency_,
browser_signal_desireability_,
browser_signal_highest_scoring_other_bid_,
browser_signal_highest_scoring_other_bid_currency_,
browser_signals_component_auction_report_result_params_.Clone(),
browser_signal_data_version_,
1,
base::BindOnce(
[](const std::optional<std::string>& expected_signals_for_winner,
const std::optional<GURL>& expected_report_url,
const base::flat_map<std::string, GURL>& expected_ad_beacon_map,
PrivateAggregationRequests expected_pa_requests,
bool expected_reporting_latency_timeout,
std::optional<base::TimeDelta> reporting_timeout,
const std::vector<std::string>& expected_errors,
base::OnceClosure done_closure,
const std::optional<std::string>& signals_for_winner,
const std::optional<GURL>& report_url,
const base::flat_map<std::string, GURL>& ad_beacon_map,
PrivateAggregationRequests pa_requests,
auction_worklet::mojom::SellerTimingMetricsPtr timing_metrics,
const std::vector<std::string>& errors) {
if (signals_for_winner && expected_signals_for_winner) {
EXPECT_THAT(base::test::ParseJson(*signals_for_winner),
base::test::IsJson(*expected_signals_for_winner));
} else {
EXPECT_EQ(expected_signals_for_winner, signals_for_winner);
}
EXPECT_EQ(expected_report_url, report_url);
EXPECT_EQ(expected_ad_beacon_map, ad_beacon_map);
EXPECT_EQ(expected_pa_requests, pa_requests);
if (expected_reporting_latency_timeout) {
EXPECT_GE(timing_metrics->script_latency,
(reporting_timeout.has_value()
? reporting_timeout.value()
: AuctionV8Helper::kScriptTimeout) *
0.9);
}
EXPECT_EQ(expected_reporting_latency_timeout,
timing_metrics->script_timed_out);
EXPECT_EQ(expected_errors, errors);
std::move(done_closure).Run();
},
expected_signals_for_winner, expected_report_url,
expected_ad_beacon_map, std::move(expected_pa_requests),
expected_reporting_latency_timeout,
auction_ad_config_non_shared_params_.reporting_timeout,
expected_errors, std::move(done_closure)));
}
void RunReportResultExpectingCallbackNeverInvoked(
mojom::SellerWorklet* seller_worklet) {
seller_worklet->ReportResult(
auction_ad_config_non_shared_params_,
direct_from_seller_seller_signals_,
direct_from_seller_seller_signals_header_ad_slot_,
direct_from_seller_auction_signals_,
direct_from_seller_auction_signals_header_ad_slot_,
browser_signals_other_seller_.Clone(),
browser_signal_interest_group_owner_,
browser_signal_buyer_and_seller_reporting_id_,
browser_signal_selected_buyer_and_seller_reporting_id_,
browser_signal_render_url_, bid_, bid_currency_,
browser_signal_desireability_,
browser_signal_highest_scoring_other_bid_,
browser_signal_highest_scoring_other_bid_currency_,
browser_signals_component_auction_report_result_params_.Clone(),
browser_signal_data_version_,
1,
base::BindOnce(
[](const std::optional<std::string>& signals_for_winner,
const std::optional<GURL>& report_url,
const base::flat_map<std::string, GURL>& ad_beacon_map,
PrivateAggregationRequests pa_requests,
auction_worklet::mojom::SellerTimingMetricsPtr timing_metrics,
const std::vector<std::string>& errors) {
ADD_FAILURE() << "This should not be invoked";
}));
}
void RunReportResultExpectingResult(
const std::optional<std::string>& expected_signals_for_winner,
const std::optional<GURL>& expected_report_url,
const base::flat_map<std::string, GURL>& expected_ad_beacon_map =
base::flat_map<std::string, GURL>(),
PrivateAggregationRequests expected_pa_requests = {},
bool expected_reporting_latency_timeout = false,
const std::vector<std::string>& expected_errors =
std::vector<std::string>()) {
auto seller_worklet = CreateWorklet();
ASSERT_TRUE(seller_worklet);
base::RunLoop run_loop;
RunReportResultExpectingResultAsync(
seller_worklet.get(), expected_signals_for_winner, expected_report_url,
expected_ad_beacon_map, std::move(expected_pa_requests),
expected_reporting_latency_timeout, expected_errors,
run_loop.QuitClosure());
run_loop.Run();
}
mojo::Remote<mojom::SellerWorklet> CreateWorklet(
bool pause_for_debugger_on_start = false,
SellerWorklet** out_seller_worklet_impl = nullptr,
bool use_alternate_url_loader_factory = false) {
mojo::PendingRemote<network::mojom::URLLoaderFactory> url_loader_factory;
network::mojom::URLLoaderFactory* used_factory;
if (use_alternate_url_loader_factory) {
alternate_url_loader_factory_.Clone(
url_loader_factory.InitWithNewPipeAndPassReceiver());
used_factory = &alternate_url_loader_factory_;
} else {
url_loader_factory_.Clone(
url_loader_factory.InitWithNewPipeAndPassReceiver());
used_factory = &url_loader_factory_;
}
CHECK_EQ(v8_helpers_.size(), shared_storage_hosts_.size());
mojo::PendingRemote<mojom::LoadSellerWorkletClient>
load_seller_worklet_client;
load_seller_worklet_client_receivers_.Add(
this, load_seller_worklet_client.InitWithNewPipeAndPassReceiver());
mojo::Remote<mojom::SellerWorklet> seller_worklet;
auto load = AuctionDownloader::StartDownload(
*used_factory, decision_logic_url_,
AuctionDownloader::MimeType::kJavascript,
auction_network_events_handler_);
auto seller_worklet_impl = std::make_unique<SellerWorklet>(
v8_helpers_, std::move(shared_storage_hosts_),
pause_for_debugger_on_start, std::move(url_loader_factory),
auction_network_events_handler_.CreateRemote(),
trusted_signals_kvv2_manager_.get(), std::move(load),
trusted_scoring_signals_url_, top_window_origin_,
permissions_policy_state_.Clone(), experiment_group_id_,
send_creative_scanning_metadata_,
public_key_ ? public_key_.Clone() : nullptr,
base::BindRepeating(&SellerWorkletTest::GetNextThreadIndex,
base::Unretained(this)),
std::move(load_seller_worklet_client));
shared_storage_hosts_.resize(NumThreads());
auto* seller_worklet_ptr = seller_worklet_impl.get();
mojo::ReceiverId receiver_id =
seller_worklets_.Add(std::move(seller_worklet_impl),
seller_worklet.BindNewPipeAndPassReceiver());
seller_worklet_ptr->set_close_pipe_callback(
base::BindOnce(&SellerWorkletTest::ClosePipeCallback,
base::Unretained(this), receiver_id));
seller_worklet.set_disconnect_with_reason_handler(base::BindRepeating(
&SellerWorkletTest::OnDisconnectWithReason, base::Unretained(this)));
if (out_seller_worklet_impl) {
*out_seller_worklet_impl = seller_worklet_ptr;
}
return seller_worklet;
}
size_t GetNextThreadIndex() { return next_thread_index_++ % NumThreads(); }
scoped_refptr<AuctionV8Helper> v8_helper() { return v8_helpers_[0]; }
std::string WaitForDisconnect() {
DCHECK(!disconnect_run_loop_);
if (!disconnect_reason_) {
disconnect_run_loop_ = std::make_unique<base::RunLoop>();
disconnect_run_loop_->Run();
disconnect_run_loop_.reset();
}
DCHECK(disconnect_reason_);
std::string disconnect_reason = std::move(disconnect_reason_).value();
disconnect_reason_.reset();
return disconnect_reason;
}
protected:
void ClosePipeCallback(mojo::ReceiverId receiver_id,
const std::string& description) {
seller_worklets_.RemoveWithReason(receiver_id, 0,
description);
}
void OnDisconnectWithReason(uint32_t custom_reason,
const std::string& description) {
DCHECK(!disconnect_reason_);
LOG(WARNING) << "Worklet disconnect with reason: " << description;
disconnect_reason_ = description;
if (disconnect_run_loop_) {
disconnect_run_loop_->Quit();
}
}
bool WaitAndGetTrustedSignalsUrlAllowed() {
if (!trusted_signals_url_allowed_) {
CHECK(!trusted_signals_url_allowed_loop_);
trusted_signals_url_allowed_loop_ = std::make_unique<base::RunLoop>();
trusted_signals_url_allowed_loop_->Run();
trusted_signals_url_allowed_loop_.reset();
}
bool out = *trusted_signals_url_allowed_;
trusted_signals_url_allowed_.reset();
return out;
}
void SellerWorkletLoaded(bool trusted_signals_url_allowed) override {
trusted_signals_url_allowed_ = trusted_signals_url_allowed;
if (trusted_signals_url_allowed_loop_) {
trusted_signals_url_allowed_loop_->Quit();
}
}
base::test::ScopedFeatureList feature_list_;
base::test::TaskEnvironment task_environment_;
std::optional<std::string> extra_js_headers_;
std::string ad_metadata_;
double bid_;
std::optional<blink::AdCurrency> bid_currency_;
GURL decision_logic_url_;
std::optional<GURL> trusted_scoring_signals_url_;
blink::AuctionConfig::NonSharedParams auction_ad_config_non_shared_params_;
mojom::TrustedSignalsCacheKeyPtr trusted_signals_cache_key_;
std::optional<GURL> direct_from_seller_seller_signals_;
std::optional<std::string> direct_from_seller_seller_signals_header_ad_slot_;
std::optional<GURL> direct_from_seller_auction_signals_;
std::optional<std::string> direct_from_seller_auction_signals_header_ad_slot_;
url::Origin top_window_origin_;
mojom::AuctionWorkletPermissionsPolicyStatePtr permissions_policy_state_;
std::optional<uint16_t> experiment_group_id_;
std::optional<bool> send_creative_scanning_metadata_;
mojom::TrustedSignalsPublicKeyPtr public_key_;
mojom::ComponentAuctionOtherSellerPtr browser_signals_other_seller_;
std::optional<blink::AdCurrency> component_expect_bid_currency_;
url::Origin browser_signal_interest_group_owner_;
GURL browser_signal_render_url_;
std::optional<std::string> creative_scanning_metadata_;
std::optional<std::string>
browser_signal_selected_buyer_and_seller_reporting_id_;
std::optional<std::string> browser_signal_buyer_and_seller_reporting_id_;
std::vector<auction_worklet::mojom::CreativeInfoWithoutOwnerPtr>
component_ads_;
uint32_t browser_signal_bidding_duration_msecs_;
std::optional<blink::AdSize> browser_signal_render_size_;
bool browser_signal_for_debugging_only_in_cooldown_or_lockout_;
bool browser_signal_for_debugging_only_sampling_;
double browser_signal_desireability_;
double browser_signal_highest_scoring_other_bid_;
std::optional<blink::AdCurrency>
browser_signal_highest_scoring_other_bid_currency_;
mojom::ComponentAuctionReportResultParamsPtr
browser_signals_component_auction_report_result_params_;
std::optional<uint32_t> browser_signal_data_version_;
std::optional<base::TimeDelta> seller_timeout_;
uint64_t group_by_origin_id_ = 1;
bool allow_group_by_origin_mode_ = false;
url::Origin bidder_joining_origin_;
std::unique_ptr<base::RunLoop> disconnect_run_loop_;
std::optional<std::string> disconnect_reason_;
network::TestURLLoaderFactory url_loader_factory_;
network::TestURLLoaderFactory alternate_url_loader_factory_;
std::vector<scoped_refptr<AuctionV8Helper>> v8_helpers_;
std::unique_ptr<TrustedSignalsKVv2Manager> trusted_signals_kvv2_manager_;
TestAuctionNetworkEventsHandler auction_network_events_handler_;
std::vector<mojo::PendingRemote<mojom::AuctionSharedStorageHost>>
shared_storage_hosts_;
size_t next_thread_index_ = 0;
std::optional<bool> trusted_signals_url_allowed_;
std::unique_ptr<base::RunLoop> trusted_signals_url_allowed_loop_;
mojo::UniqueReceiverSet<mojom::SellerWorklet> seller_worklets_;
mojo::ReceiverSet<mojom::LoadSellerWorkletClient>
load_seller_worklet_client_receivers_;
};
class SellerWorkletTwoThreadsTest : public SellerWorkletTest {
private:
size_t NumThreads() override { return 2u; }
};
class SellerWorkletNoTextConversionsTest : public SellerWorkletTest {
public:
SellerWorkletNoTextConversionsTest() {
feature_list_.InitAndDisableFeature(features::kFledgeTextConversionHelpers);
}
protected:
base::test::ScopedFeatureList feature_list_;
};
class SellerWorkletMultiThreadingTest
: public SellerWorkletTest,
public testing::WithParamInterface<std::tuple<size_t, bool>> {
public:
explicit SellerWorkletMultiThreadingTest() {
if (PrepareContexts()) {
feature_list_.InitAndEnableFeatureWithParameters(
features::kFledgePrepareSellerContextsInAdvance,
{{"MaxSellerContextsPerThread", "4"}});
} else {
feature_list_.InitAndDisableFeature(
features::kFledgePrepareSellerContextsInAdvance);
}
}
bool PrepareContexts() { return std::get<1>(GetParam()); }
size_t NumThreads() override { return std::get<0>(GetParam()); }
private:
base::test::ScopedFeatureList feature_list_;
};
INSTANTIATE_TEST_SUITE_P(
All,
SellerWorkletMultiThreadingTest,
testing::Combine(testing::Values(1, 2), testing::Bool()),
[](const auto& info) {
return base::StrCat(
{std::get<0>(info.param) == 2 ? "TwoThreads" : "SingleThread",
std::get<1>(info.param) ? "WithPreparedContexts"
: "WithoutPreparedContexts"});
});
TEST_F(SellerWorkletTest, PipeClosed) {
base::HistogramTester histogram_tester;
auto seller_worklet = CreateWorklet();
seller_worklet.reset();
task_environment_.RunUntilIdle();
EXPECT_TRUE(seller_worklets_.empty());
histogram_tester.ExpectTotalCount(
"Ads.InterestGroup.Auction.SellerWorkletIsolateUsedHeapSizeKilobytes", 1);
histogram_tester.ExpectTotalCount(
"Ads.InterestGroup.Auction.SellerWorkletIsolateTotalHeapSizeKilobytes",
1);
}
TEST_F(SellerWorkletTest, NetworkError) {
url_loader_factory_.AddResponse(decision_logic_url_.spec(),
CreateBasicSellAdScript(),
net::HTTP_NOT_FOUND);
auto sellet_worklet = CreateWorklet();
EXPECT_EQ("Failed to load https://url.test/ HTTP status = 404 Not Found.",
WaitForDisconnect());
task_environment_.RunUntilIdle();
EXPECT_THAT(
auction_network_events_handler_.GetObservedRequests(),
testing::ElementsAre(
"Sent URL: https://url.test/", "Received URL: https://url.test/",
"Completion Status: net::ERR_HTTP_RESPONSE_CODE_FAILURE"));
}
TEST_F(SellerWorkletTest, CompileError) {
AddJavascriptResponse(&url_loader_factory_, decision_logic_url_,
"Invalid Javascript");
auto sellet_worklet = CreateWorklet();
std::string disconnect_error = WaitForDisconnect();
EXPECT_THAT(disconnect_error, StartsWith("https://url.test/:1 "));
EXPECT_THAT(disconnect_error, HasSubstr("SyntaxError"));
}
TEST_F(SellerWorkletTest, ScoreAd) {
RunScoreAdWithJavascriptExpectingResult(CreateBasicSellAdScript(), 1);
task_environment_.RunUntilIdle();
EXPECT_THAT(auction_network_events_handler_.GetObservedRequests(),
testing::ElementsAre("Sent URL: https://url.test/",
"Received URL: https://url.test/",
"Completion Status: net::OK"));
RunScoreAdWithReturnValueExpectingResult("{desirability:3}", 3);
RunScoreAdWithReturnValueExpectingResult("{desirability:0.5}", 0.5);
RunScoreAdWithReturnValueExpectingResult("{desirability:0}", 0);
RunScoreAdWithReturnValueExpectingResult("{desirability:-10}", 0);
RunScoreAdWithReturnValueExpectingResult("3", 3);
RunScoreAdWithReturnValueExpectingResult("0.5", 0.5);
RunScoreAdWithReturnValueExpectingResult("0", 0);
RunScoreAdWithReturnValueExpectingResult("-10", 0);
RunScoreAdWithReturnValueExpectingResult(
"{desirability:3, snore:1/0, smore:[15], shore:{desirability:2}}", 3);
RunScoreAdWithReturnValueExpectingResult(
"", 0,
{"https://url.test/ scoreAd() return: Required field 'desirability' "
"is undefined."});
RunScoreAdWithReturnValueExpectingResult(
"{hats:15}", 0,
{"https://url.test/ scoreAd() return: Required field 'desirability' "
"is undefined."});
RunScoreAdWithReturnValueExpectingResult(
"{desirability:[15, 16]}", 0,
{"https://url.test/ scoreAd() return: Converting field 'desirability' to "
"a Number did not produce a finite double."});
RunScoreAdWithReturnValueExpectingResult(
"{desirability:1/0}", 0,
{"https://url.test/ scoreAd() return: Converting field 'desirability' to "
"a Number did not produce a finite double."});
RunScoreAdWithReturnValueExpectingResult(
"{desirability:0/0}", 0,
{"https://url.test/ scoreAd() return: Converting field 'desirability' to "
"a Number did not produce a finite double."});
RunScoreAdWithReturnValueExpectingResult(
"[15]", 0,
{"https://url.test/ scoreAd() return: Required field 'desirability' "
"is undefined."});
RunScoreAdWithReturnValueExpectingResult(
"1/0", 0, {"https://url.test/ scoreAd() returned an invalid score."});
RunScoreAdWithReturnValueExpectingResult(
"0/0", 0, {"https://url.test/ scoreAd() returned an invalid score."});
RunScoreAdWithReturnValueExpectingResult(
"-1/0", 0, {"https://url.test/ scoreAd() returned an invalid score."});
RunScoreAdWithReturnValueExpectingResult(
"true", 0,
{"https://url.test/ scoreAd() return: Value passed as dictionary is "
"neither object, null, nor undefined."});
RunScoreAdWithReturnValueExpectingResult(
"shrimp", 0,
{"https://url.test/:5 Uncaught ReferenceError: shrimp is not defined."});
RunScoreAdWithReturnValueExpectingResult("{desirability:[15]}", 15);
RunScoreAdWithReturnValueExpectingResult("{desirability:true}", 1);
}
TEST_F(SellerWorkletTest, ScoreAdAllowComponentAuction) {
const std::vector<std::string> kExpectedErrorsOnFailure{
R"(https://url.test/ scoreAd() return value does not have )"
R"(allowComponentAuction set to true. Ad dropped from component )"
R"(auction.)"};
const mojom::ComponentAuctionModifiedBidParamsPtr
kExpectedComponentAuctionModifiedBidParams =
mojom::ComponentAuctionModifiedBidParams::New(
"null", std::nullopt,
std::nullopt);
browser_signals_other_seller_.reset();
RunScoreAdWithReturnValueExpectingResult("1", 1);
RunScoreAdWithReturnValueExpectingResult(
"{desirability:1, allowComponentAuction:true}", 1);
RunScoreAdWithReturnValueExpectingResult(
"{desirability:1, allowComponentAuction:false}", 1);
RunScoreAdWithReturnValueExpectingResult("{desirability:1}", 1);
RunScoreAdWithReturnValueExpectingResult(
"{desirability:1, allowComponentAuction:1}", 1);
RunScoreAdWithReturnValueExpectingResult(
"{desirability:1, allowComponentAuction:0}", 1);
RunScoreAdWithReturnValueExpectingResult(
"{desirability:1, allowComponentAuction:[32]}", 1);
browser_signals_other_seller_ =
mojom::ComponentAuctionOtherSeller::NewTopLevelSeller(
url::Origin::Create(GURL("https://top.seller.test")));
RunScoreAdWithReturnValueExpectingResult("1", 0, kExpectedErrorsOnFailure);
RunScoreAdWithReturnValueExpectingResult(
"{desirability:1, allowComponentAuction:true}", 1,
{},
kExpectedComponentAuctionModifiedBidParams.Clone());
RunScoreAdWithReturnValueExpectingResult(
"{desirability:1, allowComponentAuction:false}", 0,
kExpectedErrorsOnFailure);
RunScoreAdWithReturnValueExpectingResult("{desirability:1}", 0,
kExpectedErrorsOnFailure);
RunScoreAdWithReturnValueExpectingResult(
"{desirability:1, allowComponentAuction:1}", 1,
{},
kExpectedComponentAuctionModifiedBidParams.Clone());
RunScoreAdWithReturnValueExpectingResult(
"{desirability:1, allowComponentAuction:0}", 0, kExpectedErrorsOnFailure);
RunScoreAdWithReturnValueExpectingResult(
"{desirability:1, allowComponentAuction:[32]}", 1,
{},
kExpectedComponentAuctionModifiedBidParams.Clone());
RunScoreAdWithReturnValueExpectingResult("0", 0);
RunScoreAdWithReturnValueExpectingResult("-1", 0);
RunScoreAdWithReturnValueExpectingResult(
"{desirability:0, allowComponentAuction:false}", 0);
RunScoreAdWithReturnValueExpectingResult(
"{desirability:-1, allowComponentAuction:false}", 0);
RunScoreAdWithReturnValueExpectingResult("{desirability:1}", 0,
kExpectedErrorsOnFailure);
browser_signals_other_seller_ =
mojom::ComponentAuctionOtherSeller::NewComponentSeller(
url::Origin::Create(GURL("https://component.seller.test")));
RunScoreAdWithReturnValueExpectingResult("1", 0, kExpectedErrorsOnFailure);
RunScoreAdWithReturnValueExpectingResult(
"{desirability:1, allowComponentAuction:true}", 1);
RunScoreAdWithReturnValueExpectingResult(
"{desirability:1, allowComponentAuction:false}", 0,
kExpectedErrorsOnFailure);
RunScoreAdWithReturnValueExpectingResult("{desirability:1}", 0,
kExpectedErrorsOnFailure);
RunScoreAdWithReturnValueExpectingResult(
"{desirability:1, allowComponentAuction:1}", 1);
RunScoreAdWithReturnValueExpectingResult(
"{desirability:1, allowComponentAuction:0}", 0, kExpectedErrorsOnFailure);
RunScoreAdWithReturnValueExpectingResult(
"{desirability:1, allowComponentAuction:[32]}", 1);
}
TEST_F(SellerWorkletTest, ScoreAdAd) {
browser_signals_other_seller_.reset();
RunScoreAdWithReturnValueExpectingResult(
"{ad:null, desirability:1}", 1,
{}, mojom::ComponentAuctionModifiedBidParamsPtr());
browser_signals_other_seller_ =
mojom::ComponentAuctionOtherSeller::NewComponentSeller(
url::Origin::Create(GURL("https://component.seller.test")));
RunScoreAdWithReturnValueExpectingResult(
"{ad:null, desirability:1, allowComponentAuction:true}", 1,
{}, mojom::ComponentAuctionModifiedBidParamsPtr());
browser_signals_other_seller_ =
mojom::ComponentAuctionOtherSeller::NewTopLevelSeller(
url::Origin::Create(GURL("https://top.seller.test")));
RunScoreAdWithReturnValueExpectingResult(
"{desirability:1, allowComponentAuction:true}", 1, {},
mojom::ComponentAuctionModifiedBidParams::New(
"null", std::nullopt, std::nullopt));
RunScoreAdWithReturnValueExpectingResult(
"{ad:null, desirability:1, allowComponentAuction:true}", 1,
{},
mojom::ComponentAuctionModifiedBidParams::New(
"null", std::nullopt, std::nullopt));
RunScoreAdWithReturnValueExpectingResult(
R"({ad:"foo", desirability:1, allowComponentAuction:true})", 1,
{},
mojom::ComponentAuctionModifiedBidParams::New(
R"("foo")", std::nullopt,
std::nullopt));
RunScoreAdWithReturnValueExpectingResult(
"{ad:[[35]], desirability:1, allowComponentAuction:true}", 1,
{},
mojom::ComponentAuctionModifiedBidParams::New(
"[[35]]", std::nullopt,
std::nullopt));
}
TEST_F(SellerWorkletTest, ScoreAdRejectReason) {
const struct {
std::string reason_str;
mojom::RejectReason reason_enum;
} kTestCases[] = {
{"not-available", mojom::RejectReason::kNotAvailable},
{"invalid-bid", mojom::RejectReason::kInvalidBid},
{"bid-below-auction-floor", mojom::RejectReason::kBidBelowAuctionFloor},
{"pending-approval-by-exchange",
mojom::RejectReason::kPendingApprovalByExchange},
{"disapproved-by-exchange", mojom::RejectReason::kDisapprovedByExchange},
{"blocked-by-publisher", mojom::RejectReason::kBlockedByPublisher},
{"language-exclusions", mojom::RejectReason::kLanguageExclusions},
{"category-exclusions", mojom::RejectReason::kCategoryExclusions},
};
for (const auto& test_case : kTestCases) {
RunScoreAdWithReturnValueExpectingResult(
base::StringPrintf(R"({desirability:-1, rejectReason: '%s'})",
test_case.reason_str.c_str()),
0,
{}, mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt, test_case.reason_enum);
}
RunScoreAdWithReturnValueExpectingResult(
"{desirability:-1}", 0,
{}, mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable);
RunScoreAdWithReturnValueExpectingResult(
"{desirability:3, rejectReason: 'invalid-bid'}", 3,
{}, mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable);
}
TEST_F(SellerWorkletTest, ScoreAdInvalidRejectReason) {
RunScoreAdWithReturnValueExpectingResult(
"{desirability:-1, rejectReason: 'INVALID-BID'}", 0,
{"https://url.test/ scoreAd() returned an invalid reject reason."},
mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable);
RunScoreAdWithReturnValueExpectingResult(
"{desirability:-1, rejectReason: 2}", 0,
{"https://url.test/ scoreAd() returned an invalid reject reason."},
mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable);
}
TEST_F(SellerWorkletTest, ScoreAdModifiesBid) {
browser_signals_other_seller_.reset();
RunScoreAdWithReturnValueExpectingResult(
"{bid:5, desirability:1}", 1,
{}, mojom::ComponentAuctionModifiedBidParamsPtr());
browser_signals_other_seller_ =
mojom::ComponentAuctionOtherSeller::NewComponentSeller(
url::Origin::Create(GURL("https://component.seller.test")));
RunScoreAdWithReturnValueExpectingResult(
"{bid:10, desirability:1, allowComponentAuction:true}", 1,
{}, mojom::ComponentAuctionModifiedBidParamsPtr());
browser_signals_other_seller_ =
mojom::ComponentAuctionOtherSeller::NewTopLevelSeller(
url::Origin::Create(GURL("https://top.level.seller.test")));
RunScoreAdWithReturnValueExpectingResult(
"{ad:null, desirability:1, allowComponentAuction:true, bid:13}", 1,
{},
mojom::ComponentAuctionModifiedBidParams::New(
"null", 13, std::nullopt));
RunScoreAdWithReturnValueExpectingResult(
"{ad:null, desirability:1, allowComponentAuction:true, bid:1.2}", 1,
{},
mojom::ComponentAuctionModifiedBidParams::New(
"null", 1.2, std::nullopt));
RunScoreAdWithReturnValueExpectingResult(
"{ad:null, desirability:1, allowComponentAuction:true, "
"bid:1.2, bidCurrency: 'USD'}",
1,
{},
mojom::ComponentAuctionModifiedBidParams::New(
"null", 1.2,
blink::AdCurrency::From("USD")));
RunScoreAdWithReturnValueExpectingResult(
"{ad:null, desirability:1, allowComponentAuction:true}", 1,
{},
mojom::ComponentAuctionModifiedBidParams::New(
"null", std::nullopt, std::nullopt));
RunScoreAdWithReturnValueExpectingResult(
R"({ad:null, desirability:1, allowComponentAuction:true, bid:"5"})", 1,
{},
mojom::ComponentAuctionModifiedBidParams::New(
"null", 5, std::nullopt));
RunScoreAdWithReturnValueExpectingResult(
"{ad:null, desirability:1, allowComponentAuction:true, bid:[4]}", 1,
{},
mojom::ComponentAuctionModifiedBidParams::New(
"null", 4, std::nullopt));
RunScoreAdWithReturnValueExpectingResult(
R"({ad:null, desirability:1, allowComponentAuction:true, bid:0})", 0,
{"https://url.test/ scoreAd() returned an invalid bid."});
RunScoreAdWithReturnValueExpectingResult(
R"({ad:null, desirability:1, allowComponentAuction:true, bid:-1})", 0,
{"https://url.test/ scoreAd() returned an invalid bid."});
RunScoreAdWithReturnValueExpectingResult(
"{ad:null, desirability:1, allowComponentAuction:true, bid:1/0}", 0,
{"https://url.test/ scoreAd() return: Converting field 'bid' to a Number "
"did not produce a finite double."});
RunScoreAdWithReturnValueExpectingResult(
"{ad:null, desirability:1, allowComponentAuction:true, bid:-1/0}", 0,
{"https://url.test/ scoreAd() return: Converting field 'bid' to a Number "
"did not produce a finite double."});
RunScoreAdWithReturnValueExpectingResult(
"{ad:null, desirability:1, allowComponentAuction:true, bid:0/0}", 0,
{"https://url.test/ scoreAd() return: Converting field 'bid' to a Number "
"did not produce a finite double."});
RunScoreAdWithReturnValueExpectingResult(
"{ad:null, desirability:1, allowComponentAuction:true, "
"bid:1.2, bidCurrency: 'USSD'}",
0, {"https://url.test/ scoreAd() returned an invalid bidCurrency."},
mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kWrongScoreAdCurrency);
RunScoreAdWithReturnValueExpectingResult(
"{ad:null, desirability:1, allowComponentAuction:true, "
"bid:1.2, bidCurrency: 'USSD', rejectReason: 'category-exclusions'}",
0, {"https://url.test/ scoreAd() returned an invalid bidCurrency."},
mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kCategoryExclusions);
auction_ad_config_non_shared_params_.seller_currency =
blink::AdCurrency::From("CAD");
RunScoreAdWithReturnValueExpectingResult(
"{ad:null, desirability:1, allowComponentAuction:true, "
"bid:1.2, bidCurrency: 'USD'}",
0,
{"https://url.test/ scoreAd() bidCurrency mismatch vs own sellerCurrency,"
" expected 'CAD' got 'USD'."},
mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kWrongScoreAdCurrency);
RunScoreAdWithReturnValueExpectingResult(
"{ad:null, desirability:1, allowComponentAuction:true, "
"bid:1.2, bidCurrency: 'CAD'}",
1,
{},
mojom::ComponentAuctionModifiedBidParams::New(
"null", 1.2,
blink::AdCurrency::From("CAD")));
auction_ad_config_non_shared_params_.seller_currency = std::nullopt;
component_expect_bid_currency_ = blink::AdCurrency::From("EUR");
RunScoreAdWithReturnValueExpectingResult(
"{ad:null, desirability:1, allowComponentAuction:true, "
"bid:1.2, bidCurrency: 'USD'}",
0,
{"https://url.test/ scoreAd() bidCurrency mismatch in component auction "
"vs parent auction bidderCurrency, expected 'EUR' got 'USD'."},
mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kWrongScoreAdCurrency);
RunScoreAdWithReturnValueExpectingResult(
"{ad:null, desirability:1, allowComponentAuction:true, "
"bid:1.2, bidCurrency: 'EUR'}",
1,
{},
mojom::ComponentAuctionModifiedBidParams::New(
"null", 1.2,
blink::AdCurrency::From("EUR")));
component_expect_bid_currency_ = std::nullopt;
RunScoreAdWithReturnValueExpectingResult(
"{ad:null, desirability:0, allowComponentAuction:true, bid:0}", 0);
}
TEST_F(SellerWorkletTest, ScoreAdDoesNotModifyBidCurrency) {
browser_signals_other_seller_ =
mojom::ComponentAuctionOtherSeller::NewTopLevelSeller(
url::Origin::Create(GURL("https://top.level.seller.test")));
bid_currency_ = blink::AdCurrency::From("CAD");
auction_ad_config_non_shared_params_.seller_currency =
blink::AdCurrency::From("USD");
RunScoreAdWithReturnValueExpectingResult(
"{ad:null, desirability:1, allowComponentAuction:true}", 0,
{"https://url.test/ scoreAd() bid passthrough mismatch vs own "
"sellerCurrency, expected 'USD' got 'CAD'."},
mojom::ComponentAuctionModifiedBidParams::New(
"null", std::nullopt, std::nullopt),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kWrongScoreAdCurrency);
}
TEST_F(SellerWorkletTest, ScoreAdIncomingBidInSellerCurrency) {
bid_currency_ = blink::AdCurrency::From("USD");
auction_ad_config_non_shared_params_.seller_currency = std::nullopt;
RunScoreAdWithReturnValueExpectingResult(
"{desirability:1, incomingBidInSellerCurrency: 4}", 0,
{"https://url.test/ scoreAd() attempting to set "
"incomingBidInSellerCurrency without a configured sellerCurrency."});
auction_ad_config_non_shared_params_.seller_currency =
blink::AdCurrency::From("CAD");
RunScoreAdWithReturnValueExpectingResult(
"{desirability:1, incomingBidInSellerCurrency: 'foo'}", 0,
{"https://url.test/ scoreAd() return: Converting field "
"'incomingBidInSellerCurrency' to a Number did not produce a finite "
"double."});
RunScoreAdWithReturnValueExpectingResult(
"{desirability:1, incomingBidInSellerCurrency: -100}", 0,
{"https://url.test/ scoreAd() incomingBidInSellerCurrency not "
"a valid bid."});
RunScoreAdWithReturnValueExpectingResult(
"{desirability:1, incomingBidInSellerCurrency: 100}", 1,
std::vector<std::string>(),
mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
{},
{},
100);
bid_currency_ = blink::AdCurrency::From("CAD");
RunScoreAdWithReturnValueExpectingResult(
"{desirability:1, incomingBidInSellerCurrency: 100}", 0,
{"https://url.test/ scoreAd() attempting to set "
"incomingBidInSellerCurrency inconsistent with incoming bid already in "
"seller currency."});
RunScoreAdWithReturnValueExpectingResult(
"{desirability:1, incomingBidInSellerCurrency: 1}", 1,
std::vector<std::string>(),
mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
{},
{},
1);
bid_ = 3.14;
RunScoreAdWithReturnValueExpectingResult(
"{desirability:1}", 1,
std::vector<std::string>(),
mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
{},
{},
3.14);
RunScoreAdWithReturnValueExpectingResult(
"1", 1,
std::vector<std::string>(),
mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
{},
{},
3.14);
}
TEST_F(SellerWorkletTest, ScoreAdDateNotAvailable) {
RunScoreAdWithReturnValueExpectingResult(
"Date.parse(Date().toString())", 0,
{"https://url.test/:5 Uncaught ReferenceError: Date is not defined."});
}
TEST_F(SellerWorkletTest, ScoreAdMedata) {
ad_metadata_ = R"("foo")";
RunScoreAdWithReturnValueExpectingResult(R"(adMetadata === "foo" ? 4 : 0)",
4);
ad_metadata_ = "[1]";
RunScoreAdWithReturnValueExpectingResult(R"(adMetadata[0] === 1 ? 4 : 0)", 4);
ad_metadata_ = "{invalid_json";
RunScoreAdWithReturnValueExpectingResult("1", 0);
}
TEST_F(SellerWorkletTest, ScoreAdCreativeScanningMetadata) {
send_creative_scanning_metadata_ = false;
creative_scanning_metadata_ = std::nullopt;
RunScoreAdWithReturnValueExpectingResult(
R"('creativeScanningMetadata' in browserSignals ? 4 : 3)", 3);
creative_scanning_metadata_ = "hello";
RunScoreAdWithReturnValueExpectingResult(
R"(browserSignals.creativeScanningMetadata === 'hello' ? 4 : 3)", 4);
}
TEST_F(SellerWorkletTest, ScoreAdSelectedBuyerAndSellerReportingId) {
browser_signal_selected_buyer_and_seller_reporting_id_ = "foo";
RunScoreAdWithReturnValueExpectingResult(
R"(browserSignals.selectedBuyerAndSellerReportingId === "foo" ? 3 : 0)",
3);
}
TEST_F(SellerWorkletTest, ScoreAdBuyerAndSellerReportingIdPresentWithSelected) {
browser_signal_selected_buyer_and_seller_reporting_id_ = "foo";
browser_signal_buyer_and_seller_reporting_id_ = "boo";
RunScoreAdWithReturnValueExpectingResult(
R"((browserSignals.selectedBuyerAndSellerReportingId === "foo" &&
browserSignals.buyerAndSellerReportingId === "boo") ? 3 : 0 )",
3);
}
TEST_F(SellerWorkletTest, ScoreAdTopWindowOrigin) {
top_window_origin_ = url::Origin::Create(GURL("https://foo.test/"));
RunScoreAdWithReturnValueExpectingResult(
R"(browserSignals.topWindowHostname == "foo.test" ? 2 : 0)", 2);
top_window_origin_ = url::Origin::Create(GURL("https://[::1]:40000/"));
RunScoreAdWithReturnValueExpectingResult(
R"(browserSignals.topWindowHostname == "[::1]" ? 3 : 0)", 3);
}
TEST_F(SellerWorkletTest, ScoreAdTopLevelSeller) {
browser_signals_other_seller_.reset();
RunScoreAdWithReturnValueExpectingResult(
R"("topLevelSeller" in browserSignals ? 0 : 1)", 1);
browser_signals_other_seller_ =
mojom::ComponentAuctionOtherSeller::NewTopLevelSeller(
url::Origin::Create(GURL("https://top.seller.test")));
RunScoreAdWithReturnValueExpectingResult(
R"(browserSignals.topLevelSeller === "https://top.seller.test" ?
{desirability: 2, allowComponentAuction: true} : 0)",
2, {},
mojom::ComponentAuctionModifiedBidParams::New(
"null", std::nullopt, std::nullopt));
browser_signals_other_seller_ =
mojom::ComponentAuctionOtherSeller::NewComponentSeller(
url::Origin::Create(GURL("https://component.test")));
RunScoreAdWithReturnValueExpectingResult(
R"("topLevelSeller" in browserSignals ?
0 : {desirability: 3, allowComponentAuction: true})",
3);
}
TEST_F(SellerWorkletTest, ScoreAdComponentSeller) {
browser_signals_other_seller_.reset();
RunScoreAdWithReturnValueExpectingResult(
R"("componentSeller" in browserSignals ? 0 : 1)", 1);
browser_signals_other_seller_ =
mojom::ComponentAuctionOtherSeller::NewTopLevelSeller(
url::Origin::Create(GURL("https://top.seller.test")));
RunScoreAdWithReturnValueExpectingResult(
R"("componentSeller" in browserSignals ?
0 : {desirability: 2, allowComponentAuction: true})",
2, {},
mojom::ComponentAuctionModifiedBidParams::New(
"null", std::nullopt, std::nullopt));
browser_signals_other_seller_ =
mojom::ComponentAuctionOtherSeller::NewComponentSeller(
url::Origin::Create(GURL("https://component.test")));
RunScoreAdWithReturnValueExpectingResult(
R"(browserSignals.componentSeller === "https://component.test" ?
{desirability: 3, allowComponentAuction: true} : 0)",
3);
}
TEST_F(SellerWorkletTest, ScoreAdInterestGroupOwner) {
browser_signal_interest_group_owner_ =
url::Origin::Create(GURL("https://foo.test/"));
RunScoreAdWithReturnValueExpectingResult(
R"(browserSignals.interestGroupOwner == "https://foo.test" ? 2 : 0)", 2);
browser_signal_interest_group_owner_ =
url::Origin::Create(GURL("https://[::1]:40000/"));
RunScoreAdWithReturnValueExpectingResult(
R"(browserSignals.interestGroupOwner == "https://[::1]:40000" ? 3 : 0)",
3);
}
TEST_F(SellerWorkletTest, ScoreAdRenderUrl) {
browser_signal_render_url_ = GURL("https://bar.test/path");
RunScoreAdWithReturnValueExpectingResult(
R"(browserSignals.renderURL === "https://bar.test/path" ? 3 : 0)", 3);
RunScoreAdWithReturnValueExpectingResult(
R"(browserSignals.renderURL === "https://bar.test/" ? 3 : 0)", 0);
}
TEST_F(SellerWorkletTest, ScoreAdRenderUrlDeprecationWarning) {
ScopedInspectorSupport inspector_support(v8_helper().get());
AddJavascriptResponse(
&url_loader_factory_, decision_logic_url_,
CreateScoreAdScript("browserSignals.renderUrl ? 1 : 0"));
SellerWorklet* worklet_impl = nullptr;
auto worklet =
CreateWorklet(false, &worklet_impl);
int id = worklet_impl->context_group_ids_for_testing()[0];
TestChannel* channel =
inspector_support.ConnectDebuggerSessionAndRuntimeEnable(id);
RunScoreAdExpectingResultOnWorklet(worklet_impl, 1);
channel->WaitForAndValidateConsoleMessage(
"warning",
"[{\"type\":\"string\", "
"\"value\":\"browserSignals.renderUrl is deprecated. Please use "
"browserSignals.renderURL instead.\"}]",
1, "scoreAd", decision_logic_url_,
4);
channel->ExpectNoMoreConsoleEvents();
}
TEST_F(SellerWorkletTest, ScoreAdRenderUrlNoDeprecationWarning) {
ScopedInspectorSupport inspector_support(v8_helper().get());
AddJavascriptResponse(
&url_loader_factory_, decision_logic_url_,
CreateScoreAdScript("browserSignals.renderURL ? 1 : 0"));
SellerWorklet* worklet_impl = nullptr;
auto worklet =
CreateWorklet(false, &worklet_impl);
int id = worklet_impl->context_group_ids_for_testing()[0];
TestChannel* channel =
inspector_support.ConnectDebuggerSessionAndRuntimeEnable(id);
RunScoreAdExpectingResultOnWorklet(worklet_impl, 1);
channel->ExpectNoMoreConsoleEvents();
}
TEST_F(SellerWorkletTest, ScoreAdAdComponents) {
component_ads_.clear();
RunScoreAdWithReturnValueExpectingResult(
R"(browserSignals.adComponents === undefined ? 3 : 0)", 3);
component_ads_.push_back(
auction_worklet::mojom::CreativeInfoWithoutOwner::New(
blink::AdDescriptor(GURL("https://bar.test/path")),
std::nullopt));
RunScoreAdWithReturnValueExpectingResult(
R"(browserSignals.adComponents.length)", 1);
RunScoreAdWithReturnValueExpectingResult(
R"(browserSignals.adComponents[0] === "https://bar.test/path" ? 3 : 0)",
3);
component_ads_.clear();
component_ads_.push_back(
auction_worklet::mojom::CreativeInfoWithoutOwner::New(
blink::AdDescriptor(GURL("https://2.test/")),
std::nullopt));
component_ads_.push_back(
auction_worklet::mojom::CreativeInfoWithoutOwner::New(
blink::AdDescriptor(GURL("https://1.test/")),
std::nullopt));
component_ads_.push_back(
auction_worklet::mojom::CreativeInfoWithoutOwner::New(
blink::AdDescriptor(GURL("https://3.test/")),
std::nullopt));
RunScoreAdWithReturnValueExpectingResult(
R"(browserSignals.adComponents.length)", 3);
RunScoreAdWithReturnValueExpectingResult(
R"(browserSignals.adComponents[0] === "https://2.test/" ? 3 : 0)", 3);
RunScoreAdWithReturnValueExpectingResult(
R"(browserSignals.adComponents[1] === "https://1.test/" ? 3 : 0)", 3);
RunScoreAdWithReturnValueExpectingResult(
R"(browserSignals.adComponents[2] === "https://3.test/" ? 3 : 0)", 3);
}
TEST_F(SellerWorkletTest, ScoreAdAdComponentsCreativeScanningMetadata) {
send_creative_scanning_metadata_ = false;
component_ads_.clear();
RunScoreAdWithReturnValueExpectingResult(
R"('adComponentsCreativeScanningMetadata' in browserSignals? 3 : 2)", 2);
component_ads_.push_back(
auction_worklet::mojom::CreativeInfoWithoutOwner::New(
blink::AdDescriptor(GURL("https://bar.test/path")),
std::nullopt));
RunScoreAdWithReturnValueExpectingResult(
R"(browserSignals.adComponentsCreativeScanningMetadata.length)", 1);
RunScoreAdWithReturnValueExpectingResult(
R"(browserSignals.adComponentsCreativeScanningMetadata[0] === null ?
3 : 0)",
3);
component_ads_.clear();
component_ads_.push_back(
auction_worklet::mojom::CreativeInfoWithoutOwner::New(
blink::AdDescriptor(GURL("https://2.test/")),
"2nd"));
component_ads_.push_back(
auction_worklet::mojom::CreativeInfoWithoutOwner::New(
blink::AdDescriptor(GURL("https://1.test/")),
std::nullopt));
component_ads_.push_back(
auction_worklet::mojom::CreativeInfoWithoutOwner::New(
blink::AdDescriptor(GURL("https://3.test/")),
"3rd"));
RunScoreAdWithReturnValueExpectingResult(
R"(browserSignals.adComponentsCreativeScanningMetadata.length)", 3);
RunScoreAdWithReturnValueExpectingResult(
R"(browserSignals.adComponentsCreativeScanningMetadata[0] ===
"2nd" ? 3 : 0)",
3);
RunScoreAdWithReturnValueExpectingResult(
R"(browserSignals.adComponentsCreativeScanningMetadata[1] ===
null ? 3 : 0)",
3);
RunScoreAdWithReturnValueExpectingResult(
R"(browserSignals.adComponentsCreativeScanningMetadata[2] ===
"3rd" ? 3 : 0)",
3);
}
TEST_F(SellerWorkletNoTextConversionsTest, ScoreAdTextConversions) {
RunScoreAdWithReturnValueExpectingResult(
R"('protectedAudience' in globalThis? 3 : 2)", 2);
}
TEST_F(SellerWorkletTest, ScoreAdTextConversions) {
RunScoreAdWithReturnValueExpectingResult(
R"('encodeUtf8' in protectedAudience? 3 : 2)", 3);
RunScoreAdWithReturnValueExpectingResult(
R"('decodeUtf8' in protectedAudience? 3 : 2)", 3);
RunScoreAdWithReturnValueExpectingResult(
"protectedAudience.encodeUtf8('A')[0]", 65);
RunScoreAdWithReturnValueExpectingResult(
"protectedAudience.decodeUtf8(new Uint8Array([65, 68])) === 'AD' ? 3 : 2",
3);
}
TEST_F(SellerWorkletTest, ScoreAdNoGlobalStomp) {
const char kScript[] = R"(
function protectedAudience() {
return 5;
}
function scoreAd() {
return protectedAudience();
}
)";
RunScoreAdWithJavascriptExpectingResult(kScript, 5);
}
TEST_F(SellerWorkletTest, ScoreAdBid) {
bid_ = 5;
RunScoreAdWithReturnValueExpectingResult("bid", 5);
bid_ = 0.5;
RunScoreAdWithReturnValueExpectingResult("bid", 0.5);
bid_ = -1;
RunScoreAdWithReturnValueExpectingResult("bid", 0);
}
TEST_F(SellerWorkletTest, ScoreAdBidCurrency) {
RunScoreAdWithReturnValueExpectingResult(
"browserSignals.bidCurrency === '???' ? 2 : 0", 2);
bid_currency_ = blink::AdCurrency::From("USD");
RunScoreAdWithReturnValueExpectingResult(
"browserSignals.bidCurrency === 'USD' ? 2 : 0", 2);
}
TEST_F(SellerWorkletTest, ScoreAdBiddingDuration) {
browser_signal_bidding_duration_msecs_ = 0;
RunScoreAdWithReturnValueExpectingResult("browserSignals.biddingDurationMsec",
0);
browser_signal_bidding_duration_msecs_ = 100;
RunScoreAdWithReturnValueExpectingResult("browserSignals.biddingDurationMsec",
100);
}
TEST_F(SellerWorkletTest, ScoreAdAuctionConfigParam) {
decision_logic_url_ = GURL("https://url.test/");
RunScoreAdWithReturnValueExpectingResult(
"auctionConfig.decisionLogicURL.length",
decision_logic_url_.spec().length());
decision_logic_url_ = GURL("https://url.test/longer/url");
RunScoreAdWithReturnValueExpectingResult(
"auctionConfig.decisionLogicURL.length",
decision_logic_url_.spec().length());
direct_from_seller_auction_signals_header_ad_slot_ = R"("abcde")";
RunScoreAdWithReturnValueExpectingResult(
"directFromSellerSignals.auctionSignals.length",
direct_from_seller_auction_signals_header_ad_slot_->length() -
std::string(R"("")").length());
direct_from_seller_seller_signals_header_ad_slot_ = R"("abcdefg")";
RunScoreAdWithReturnValueExpectingResult(
"directFromSellerSignals.sellerSignals.length",
direct_from_seller_seller_signals_header_ad_slot_->length() -
std::string(R"("")").length());
}
TEST_F(SellerWorkletTest, ScoreAdAuctionConfigUrlDeprecationWarning) {
ScopedInspectorSupport inspector_support(v8_helper().get());
decision_logic_url_ = GURL("https://url.test/");
trusted_scoring_signals_url_ =
GURL("https://url.test/trusted_scoring_signals");
auto& component_auctions =
auction_ad_config_non_shared_params_.component_auctions;
component_auctions.emplace_back();
component_auctions[0].seller =
url::Origin::Create(GURL("https://component1.test"));
component_auctions[0].decision_logic_url =
GURL("https://component1.test/script.js");
component_auctions[0].trusted_scoring_signals_url =
GURL("https://component1.test/signals.js");
component_auctions.emplace_back(blink::AuctionConfig());
component_auctions[1].seller =
url::Origin::Create(GURL("https://component2.test"));
component_auctions[1].decision_logic_url =
GURL("https://component2.test/script.js");
component_auctions[1].trusted_scoring_signals_url =
GURL("https://component2.test/signals.js");
AddJavascriptResponse(&url_loader_factory_, decision_logic_url_,
CreateScoreAdScript(
R"(auctionConfig.decisionLogicUrl &&
auctionConfig.trustedScoringSignalsUrl &&
auctionConfig.componentAuctions[0].decisionLogicUrl &&
auctionConfig.componentAuctions[0].trustedScoringSignalsUrl &&
auctionConfig.componentAuctions[1].decisionLogicUrl &&
auctionConfig.componentAuctions[1].trustedScoringSignalsUrl ?
1 : 0)"));
AddJsonResponse(
&url_loader_factory_,
GURL("https://url.test/trusted_scoring_signals?hostname=window.test"
"&renderUrls=https%3A%2F%2Frender.url.test%2F"),
kTrustedScoringSignalsResponse);
SellerWorklet* worklet_impl = nullptr;
auto worklet =
CreateWorklet(false, &worklet_impl);
int id = worklet_impl->context_group_ids_for_testing()[0];
TestChannel* channel =
inspector_support.ConnectDebuggerSessionAndRuntimeEnable(id);
RunScoreAdExpectingResultOnWorklet(worklet_impl, 1);
channel->WaitForAndValidateConsoleMessage(
"warning",
"[{\"type\":\"string\", "
"\"value\":\"auctionConfig.decisionLogicUrl is deprecated. Please use "
"auctionConfig.decisionLogicURL instead.\"}]",
1, "scoreAd", decision_logic_url_,
4);
channel->WaitForAndValidateConsoleMessage(
"warning",
"[{\"type\":\"string\", "
"\"value\":\"auctionConfig.trustedScoringSignalsUrl is deprecated. "
"Please use auctionConfig.trustedScoringSignalsURL instead.\"}]",
1, "scoreAd", decision_logic_url_,
5);
channel->WaitForAndValidateConsoleMessage(
"warning",
"[{\"type\":\"string\", "
"\"value\":\"auctionConfig.decisionLogicUrl is deprecated. Please use "
"auctionConfig.decisionLogicURL instead.\"}]",
1, "scoreAd", decision_logic_url_,
6);
channel->WaitForAndValidateConsoleMessage(
"warning",
"[{\"type\":\"string\", "
"\"value\":\"auctionConfig.trustedScoringSignalsUrl is deprecated. "
"Please use auctionConfig.trustedScoringSignalsURL instead.\"}]",
1, "scoreAd", decision_logic_url_,
7);
channel->WaitForAndValidateConsoleMessage(
"warning",
"[{\"type\":\"string\", "
"\"value\":\"auctionConfig.decisionLogicUrl is deprecated. Please use "
"auctionConfig.decisionLogicURL instead.\"}]",
1, "scoreAd", decision_logic_url_,
8);
channel->WaitForAndValidateConsoleMessage(
"warning",
"[{\"type\":\"string\", "
"\"value\":\"auctionConfig.trustedScoringSignalsUrl is deprecated. "
"Please use auctionConfig.trustedScoringSignalsURL instead.\"}]",
1, "scoreAd", decision_logic_url_,
9);
channel->ExpectNoMoreConsoleEvents();
}
TEST_F(SellerWorkletTest, ScoreAdAuctionConfigUrlNoDeprecationWarning) {
ScopedInspectorSupport inspector_support(v8_helper().get());
decision_logic_url_ = GURL("https://url.test/");
trusted_scoring_signals_url_ =
GURL("https://url.test/trusted_scoring_signals");
auto& component_auctions =
auction_ad_config_non_shared_params_.component_auctions;
component_auctions.emplace_back(blink::AuctionConfig());
component_auctions[0].seller =
url::Origin::Create(GURL("https://component1.test"));
component_auctions[0].decision_logic_url =
GURL("https://component1.test/script.js");
component_auctions[0].trusted_scoring_signals_url =
GURL("https://component1.test/signals.js");
component_auctions.emplace_back(blink::AuctionConfig());
component_auctions[1].seller =
url::Origin::Create(GURL("https://component2.test"));
component_auctions[1].decision_logic_url =
GURL("https://component2.test/script.js");
component_auctions[1].trusted_scoring_signals_url =
GURL("https://component2.test/signals.js");
AddJavascriptResponse(&url_loader_factory_, decision_logic_url_,
CreateScoreAdScript(
R"(auctionConfig.decisionLogicURL &&
auctionConfig.trustedScoringSignalsURL &&
auctionConfig.componentAuctions[0].decisionLogicURL &&
auctionConfig.componentAuctions[0].trustedScoringSignalsURL &&
auctionConfig.componentAuctions[1].decisionLogicURL &&
auctionConfig.componentAuctions[1].trustedScoringSignalsURL ?
1 : 0)"));
AddJsonResponse(
&url_loader_factory_,
GURL("https://url.test/trusted_scoring_signals?hostname=window.test"
"&renderUrls=https%3A%2F%2Frender.url.test%2F"),
kTrustedScoringSignalsResponse);
SellerWorklet* worklet_impl = nullptr;
auto worklet =
CreateWorklet(false, &worklet_impl);
int id = worklet_impl->context_group_ids_for_testing()[0];
TestChannel* channel =
inspector_support.ConnectDebuggerSessionAndRuntimeEnable(id);
RunScoreAdExpectingResultOnWorklet(worklet_impl, 1);
channel->ExpectNoMoreConsoleEvents();
}
TEST_F(SellerWorkletTest, ScoreAdExperimentGroupIdParam) {
RunScoreAdWithReturnValueExpectingResult(
R"("experimentGroupId" in auctionConfig ? 1 : 0)", 0);
experiment_group_id_ = 954u;
RunScoreAdWithReturnValueExpectingResult("auctionConfig.experimentGroupId",
954);
}
TEST_F(SellerWorkletTest, ScoreAdSendCreativeScanParam) {
RunScoreAdWithReturnValueExpectingResult(
R"("sendCreativeScanningMetadata" in auctionConfig ? 1 : 0)", 0);
send_creative_scanning_metadata_ = true;
RunScoreAdWithReturnValueExpectingResult(
"auctionConfig.sendCreativeScanningMetadata ? 1 : 0", 1);
auto& component_auctions =
auction_ad_config_non_shared_params_.component_auctions;
component_auctions.emplace_back();
component_auctions[0].seller =
url::Origin::Create(GURL("https://component1.test"));
component_auctions[0].decision_logic_url =
GURL("https://component1.test/script.js");
RunScoreAdWithReturnValueExpectingResult(
R"(("sendCreativeScanningMetadata" in
auctionConfig.componentAuctions[0]) ? 1 : 0)",
0);
component_auctions[0].send_creative_scanning_metadata = false;
RunScoreAdWithReturnValueExpectingResult(
R"(("sendCreativeScanningMetadata" in
auctionConfig.componentAuctions[0]) ? 1 : 0)",
1);
RunScoreAdWithReturnValueExpectingResult(
"auctionConfig.componentAuctions[0].sendCreativeScanningMetadata ? 1 : 0",
0);
}
TEST_P(SellerWorkletMultiThreadingTest, ScoreAdTrustedScoringSignals) {
trusted_scoring_signals_url_ = std::nullopt;
RunScoreAdWithReturnValueExpectingResult(
"trustedScoringSignals === null ? 1 : 0", 1);
trusted_scoring_signals_url_ =
GURL("https://url.test/trusted_scoring_signals");
const GURL kNoComponentSignalsUrl = GURL(
"https://url.test/trusted_scoring_signals?hostname=window.test"
"&renderUrls=https%3A%2F%2Frender.url.test%2F");
AddVersionedJsonResponse(&url_loader_factory_, kNoComponentSignalsUrl,
kTrustedScoringSignalsResponse, 1);
RunScoreAdWithReturnValueExpectingResultInExactTime(
"trustedScoringSignals.renderURL['https://render.url.test/']",
4 ,
mojom::ComponentAuctionModifiedBidParamsPtr(),
TrustedSignalsRequestManager::kAutoSendDelay, {},
1);
RunScoreAdWithReturnValueExpectingResultInExactTime(
"trustedScoringSignals.adComponentRenderURLs === undefined ? 1 : 0", 1,
mojom::ComponentAuctionModifiedBidParamsPtr(),
TrustedSignalsRequestManager::kAutoSendDelay, {},
1);
mojom::RealTimeReportingContribution expected_trusted_signal_histogram(
1024 + auction_worklet::RealTimeReportingPlatformError::
kTrustedScoringSignalsFailure,
1,
std::nullopt);
RealTimeReportingContributions expected_real_time_contributions;
expected_real_time_contributions.push_back(
expected_trusted_signal_histogram.Clone());
url_loader_factory_.AddResponse(kNoComponentSignalsUrl.spec(),
std::string(),
net::HTTP_NOT_FOUND);
RunScoreAdWithReturnValueExpectingResultInExactTime(
"trustedScoringSignals === null ? 1 : 0", 1,
mojom::ComponentAuctionModifiedBidParamsPtr(),
TrustedSignalsRequestManager::kAutoSendDelay,
{base::StringPrintf("Failed to load %s HTTP status = 404 Not Found.",
kNoComponentSignalsUrl.spec().c_str())},
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
{}, std::move(expected_real_time_contributions));
component_ads_.push_back(
auction_worklet::mojom::CreativeInfoWithoutOwner::New(
blink::AdDescriptor(GURL("https://component1.test/")),
std::nullopt));
component_ads_.push_back(
auction_worklet::mojom::CreativeInfoWithoutOwner::New(
blink::AdDescriptor(GURL("https://component2.test/")),
std::nullopt));
AddVersionedJsonResponse(
&url_loader_factory_,
GURL("https://url.test/trusted_scoring_signals?hostname=window.test"
"&renderUrls=https%3A%2F%2Frender.url.test%2F"
"&adComponentRenderUrls=https%3A%2F%2Fcomponent1.test%2F,"
"https%3A%2F%2Fcomponent2.test%2F"),
kTrustedScoringSignalsResponse, 5);
RunScoreAdWithReturnValueExpectingResultInExactTime(
"trustedScoringSignals.renderURL['https://render.url.test/']",
4 ,
mojom::ComponentAuctionModifiedBidParamsPtr(),
TrustedSignalsRequestManager::kAutoSendDelay, {},
5);
RunScoreAdWithReturnValueExpectingResultInExactTime(
"trustedScoringSignals.adComponentRenderURLs['https://component1.test/']",
1 ,
mojom::ComponentAuctionModifiedBidParamsPtr(),
TrustedSignalsRequestManager::kAutoSendDelay, {},
5);
RunScoreAdWithReturnValueExpectingResultInExactTime(
"trustedScoringSignals.adComponentRenderURLs['https://component2.test/']",
2 ,
mojom::ComponentAuctionModifiedBidParamsPtr(),
TrustedSignalsRequestManager::kAutoSendDelay, {},
5);
}
TEST_F(SellerWorkletTest, ScoreAdTrustedScoringSignalsCreativeScanning) {
for (bool send_creative_scanning_metadata : {true, false}) {
SCOPED_TRACE(send_creative_scanning_metadata);
send_creative_scanning_metadata_ = send_creative_scanning_metadata;
creative_scanning_metadata_ = "legit main ad";
browser_signal_buyer_and_seller_reporting_id_ = "comfy";
browser_signal_render_size_ =
blink::AdSize(80, blink::AdSize::LengthUnit::kScreenWidth, 100,
blink::AdSize::LengthUnit::kPixels);
component_ads_.push_back(
auction_worklet::mojom::CreativeInfoWithoutOwner::New(
blink::AdDescriptor(
GURL("https://component1.test/"),
blink::AdSize(100, blink::AdSize::LengthUnit::kPixels, 100,
blink::AdSize::LengthUnit::kPixels)),
"good component"));
component_ads_.push_back(
auction_worklet::mojom::CreativeInfoWithoutOwner::New(
blink::AdDescriptor(GURL("https://component2.test/")),
"dubious component"));
trusted_scoring_signals_url_ =
GURL("https://url.test/trusted_scoring_signals");
GURL signals_with_scanning(
"https://url.test/trusted_scoring_signals?hostname=window.test"
"&renderUrls=https%3A%2F%2Frender.url.test%2F"
"&adComponentRenderUrls=https%3A%2F%2Fcomponent1.test%2F,"
"https%3A%2F%2Fcomponent2.test%2F"
"&adCreativeScanningMetadata=legit+main+ad"
"&adComponentCreativeScanningMetadata=good+component,dubious+component"
"&adSizes=80sw,100px"
"&adComponentSizes=100px,100px,,"
"&adBuyer=https%3A%2F%2Finterest.group.owner.test"
"&adComponentBuyer=https%3A%2F%2Finterest.group.owner.test,"
"https%3A%2F%2Finterest.group.owner.test"
"&adBuyerAndSellerReportingIds=comfy");
GURL signals_without_scanning(
"https://url.test/trusted_scoring_signals?hostname=window.test"
"&renderUrls=https%3A%2F%2Frender.url.test%2F"
"&adComponentRenderUrls=https%3A%2F%2Fcomponent1.test%2F,"
"https%3A%2F%2Fcomponent2.test%2F");
url_loader_factory_.ClearResponses();
AddJsonResponse(&url_loader_factory_,
send_creative_scanning_metadata ? signals_with_scanning
: signals_without_scanning,
kTrustedScoringSignalsResponse);
RunScoreAdWithReturnValueExpectingResult(
"trustedScoringSignals.renderURL['https://render.url.test/']", 4);
RunScoreAdWithReturnValueExpectingResult(
"trustedScoringSignals.adComponentRenderURLs["
"'https://component1.test/']",
1);
RunScoreAdWithReturnValueExpectingResult(
"trustedScoringSignals.adComponentRenderURLs["
"'https://component2.test/']",
2);
}
}
TEST_F(SellerWorkletTest, ScoreAdTrustedScoringSignalsCreativeScanningBatch) {
send_creative_scanning_metadata_ = true;
trusted_scoring_signals_url_ =
GURL("https://url.test/trusted_scoring_signals");
const char kResponse[] = R"(
{
"renderUrls": {
"https://render.url.test/": 4,
"https://other.test/": 5
}
}
)";
AddJavascriptResponse(
&url_loader_factory_, decision_logic_url_,
CreateScoreAdScript(
"trustedScoringSignals.renderURL[browserSignals.renderURL]"));
auto seller_worklet = CreateWorklet();
struct {
blink::AdDescriptor ad_desciptor;
std::optional<std::string> creative_scanning_metadata;
int expected_score;
url::Origin owner;
std::optional<std::string> buyer_and_seller_reporting_id;
} inputs[] = {
{blink::AdDescriptor(
GURL("https://render.url.test/"),
blink::AdSize(1920, blink::mojom::AdSize_LengthUnit::kPixels, 100,
blink::mojom::AdSize_LengthUnit::kScreenHeight)),
"c1", 4, url::Origin::Create(GURL("https://b1.test")), "seat1"},
{blink::AdDescriptor(
GURL("https://other.test/"),
blink::AdSize(400, blink::mojom::AdSize_LengthUnit::kPixels, 200,
blink::mojom::AdSize_LengthUnit::kPixels)),
"c2", 5, url::Origin::Create(GURL("https://b2.test")),
std::nullopt}};
size_t num_completed_worklets = 0;
base::RunLoop run_loop;
for (const auto& input : inputs) {
browser_signal_render_url_ = GURL(input.ad_desciptor.url);
browser_signal_render_size_ = input.ad_desciptor.size;
creative_scanning_metadata_ = input.creative_scanning_metadata;
browser_signal_interest_group_owner_ = input.owner;
browser_signal_buyer_and_seller_reporting_id_ =
input.buyer_and_seller_reporting_id;
RunScoreAdOnWorkletAsync(
seller_worklet.get(), input.expected_score,
std::vector<std::string>(),
mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
{},
{},
std::nullopt,
false,
std::nullopt,
std::nullopt,
base::BindLambdaForTesting([&]() {
++num_completed_worklets;
if (num_completed_worklets == std::size(inputs)) {
run_loop.Quit();
}
}));
}
GURL response_url(
"https://url.test/trusted_scoring_signals?hostname=window.test"
"&renderUrls=https%3A%2F%2Fother.test%2F,https%3A%2F%2Frender.url.test%2F"
"&adCreativeScanningMetadata=c2,c1"
"&adSizes=400px,200px,1920px,100sh"
"&adBuyer=https%3A%2F%2Fb2.test,https%3A%2F%2Fb1.test"
"&adBuyerAndSellerReportingIds=,seat1");
AddJsonResponse(&url_loader_factory_, response_url, kResponse);
run_loop.Run();
}
TEST_F(SellerWorkletTest, ScoreAdTrustedScoringSignalsLatency) {
const base::TimeDelta kDelay = base::Milliseconds(135);
trusted_scoring_signals_url_ =
GURL("https://url.test/trusted_scoring_signals");
const GURL kNoComponentSignalsUrl = GURL(
"https://url.test/trusted_scoring_signals?hostname=window.test"
"&renderUrls=https%3A%2F%2Frender.url.test%2F");
AddJavascriptResponse(&url_loader_factory_, decision_logic_url_,
CreateScoreAdScript("1", ""));
auto seller_worklet = CreateWorklet();
ASSERT_TRUE(seller_worklet);
base::RunLoop run_loop;
RunScoreAdOnWorkletAsync(
seller_worklet.get(), 1,
{}, mojom::ComponentAuctionModifiedBidParamsPtr(),
1,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
{},
{},
std::nullopt,
false,
kDelay,
std::nullopt, run_loop.QuitClosure());
task_environment_.RunUntilIdle();
task_environment_.FastForwardBy(kDelay);
AddVersionedJsonResponse(&url_loader_factory_, kNoComponentSignalsUrl,
kTrustedScoringSignalsResponse, 1);
run_loop.Run();
}
TEST_F(SellerWorkletTest, ScoreAdCodeReadyLatency) {
const base::TimeDelta kDelay = base::Milliseconds(235);
trusted_scoring_signals_url_ =
GURL("https://url.test/trusted_scoring_signals");
const GURL kNoComponentSignalsUrl = GURL(
"https://url.test/trusted_scoring_signals?hostname=window.test"
"&renderUrls=https%3A%2F%2Frender.url.test%2F");
AddVersionedJsonResponse(&url_loader_factory_, kNoComponentSignalsUrl,
kTrustedScoringSignalsResponse, 1);
auto seller_worklet = CreateWorklet();
ASSERT_TRUE(seller_worklet);
base::RunLoop run_loop;
RunScoreAdOnWorkletAsync(
seller_worklet.get(), 1,
{}, mojom::ComponentAuctionModifiedBidParamsPtr(),
1,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
{},
{},
std::nullopt,
false,
std::nullopt,
kDelay, run_loop.QuitClosure());
task_environment_.RunUntilIdle();
task_environment_.FastForwardBy(kDelay);
AddJavascriptResponse(&url_loader_factory_, decision_logic_url_,
CreateScoreAdScript("1", ""));
run_loop.Run();
}
TEST_F(SellerWorkletTest, ScoreAdJsFetchLatency) {
auto seller_worklet = CreateWorklet();
ASSERT_TRUE(seller_worklet);
base::RunLoop run_loop;
seller_worklet->ScoreAd(
ad_metadata_, bid_, bid_currency_, auction_ad_config_non_shared_params_,
auction_worklet::mojom::TrustedSignalsCacheKeyPtr(), MainAd(),
ComponentAds(), direct_from_seller_seller_signals_,
direct_from_seller_seller_signals_header_ad_slot_,
direct_from_seller_auction_signals_,
direct_from_seller_auction_signals_header_ad_slot_,
browser_signals_other_seller_.Clone(), component_expect_bid_currency_,
browser_signal_interest_group_owner_,
browser_signal_selected_buyer_and_seller_reporting_id_,
browser_signal_buyer_and_seller_reporting_id_,
browser_signal_bidding_duration_msecs_,
browser_signal_for_debugging_only_in_cooldown_or_lockout_,
browser_signal_for_debugging_only_sampling_, seller_timeout_,
group_by_origin_id_, allow_group_by_origin_mode_,
1, bidder_joining_origin_,
TestScoreAdClient::Create(base::BindLambdaForTesting(
[&run_loop](double score, mojom::RejectReason reject_reason,
mojom::ComponentAuctionModifiedBidParamsPtr
component_auction_modified_bid_params,
std::optional<double> bid_in_seller_currency,
std::optional<uint32_t> scoring_signals_data_version,
const std::optional<GURL>& debug_loss_report_url,
const std::optional<GURL>& debug_win_report_url,
PrivateAggregationRequests pa_requests,
RealTimeReportingContributions real_time_contributions,
mojom::SellerTimingMetricsPtr score_ad_timing_metrics,
mojom::ScoreAdDependencyLatenciesPtr
score_ad_dependency_latencies,
const std::vector<std::string>& errors) {
ASSERT_TRUE(score_ad_timing_metrics->js_fetch_latency.has_value());
EXPECT_EQ(base::Milliseconds(235),
*score_ad_timing_metrics->js_fetch_latency);
run_loop.Quit();
})));
task_environment_.FastForwardBy(base::Milliseconds(235));
AddJavascriptResponse(&url_loader_factory_, decision_logic_url_,
CreateScoreAdScript("1", ""));
run_loop.Run();
}
TEST_F(SellerWorkletTest, ScoreAdDataVersion) {
trusted_scoring_signals_url_ =
GURL("https://url.test/trusted_scoring_signals");
const GURL kNoComponentSignalsUrl = GURL(
"https://url.test/trusted_scoring_signals?hostname=window.test"
"&renderUrls=https%3A%2F%2Frender.url.test%2F");
AddVersionedJsonResponse(&url_loader_factory_, kNoComponentSignalsUrl,
kTrustedScoringSignalsResponse,
100);
RunScoreAdWithReturnValueExpectingResult(
"browserSignals.dataVersion", 100,
{}, mojom::ComponentAuctionModifiedBidParamsPtr(),
100);
}
TEST_F(SellerWorkletTest, ScoreAdExperimentGroupId) {
experiment_group_id_ = 3948u;
trusted_scoring_signals_url_ =
GURL("https://url.test/trusted_scoring_signals");
const GURL kSignalsUrl = GURL(
"https://url.test/trusted_scoring_signals?hostname=window.test"
"&renderUrls=https%3A%2F%2Frender.url.test%2F"
"&experimentGroupId=3948");
AddJsonResponse(&url_loader_factory_, kSignalsUrl,
kTrustedScoringSignalsResponse);
RunScoreAdWithReturnValueExpectingResult("auctionConfig.experimentGroupId",
3948,
{});
}
TEST_P(SellerWorkletMultiThreadingTest, ScoreAdParallelBeforeLoadComplete) {
ScopedInspectorSupport inspector_support(v8_helper().get());
SellerWorklet* worklet_impl = nullptr;
auto seller_worklet =
CreateWorklet(false, &worklet_impl);
int id = worklet_impl->context_group_ids_for_testing()[0];
TestChannel* channel =
inspector_support.ConnectDebuggerSessionAndRuntimeEnable(id);
const size_t kNumWorklets = 10;
size_t num_completed_worklets = 0;
base::RunLoop run_loop;
for (size_t i = 0; i < kNumWorklets; ++i) {
browser_signal_render_url_ = GURL(base::StringPrintf("https://foo/%zu", i));
RunScoreAdOnWorkletAsync(seller_worklet.get(), i,
std::vector<std::string>(),
mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
{},
{},
std::nullopt,
false,
std::nullopt,
std::nullopt,
base::BindLambdaForTesting([&]() {
++num_completed_worklets;
if (num_completed_worklets == kNumWorklets) {
run_loop.Quit();
}
}));
}
task_environment_.RunUntilIdle();
EXPECT_EQ(0u, num_completed_worklets);
AddJavascriptResponse(
&url_loader_factory_, decision_logic_url_,
CreateScoreAdScript("parseInt(browserSignals.renderURL.slice(-1))",
"console.log(browserSignals.renderURL.slice(-1));"));
run_loop.Run();
if (NumThreads() == 1) {
for (size_t i = 0; i < kNumWorklets; ++i) {
channel->WaitForAndValidateConsoleMessage(
"log",
base::StringPrintf("[{\"type\":\"string\", \"value\":\"%i\"}]", i),
1, "scoreAd", decision_logic_url_,
3);
}
}
}
TEST_P(SellerWorkletMultiThreadingTest, ScoreAdParallelAfterLoadComplete) {
base::HistogramTester histogram_tester;
AddJavascriptResponse(
&url_loader_factory_, decision_logic_url_,
CreateScoreAdScript("parseInt(browserSignals.renderURL.slice(-1))",
"console.log(browserSignals.renderURL.slice(-1));"));
ScopedInspectorSupport inspector_support(v8_helper().get());
SellerWorklet* worklet_impl = nullptr;
auto seller_worklet =
CreateWorklet(false, &worklet_impl);
int id = worklet_impl->context_group_ids_for_testing()[0];
TestChannel* channel =
inspector_support.ConnectDebuggerSessionAndRuntimeEnable(id);
task_environment_.RunUntilIdle();
const size_t kNumWorklets = 10;
size_t num_completed_worklets = 0;
base::RunLoop run_loop;
for (size_t i = 0; i < kNumWorklets; ++i) {
browser_signal_render_url_ = GURL(base::StringPrintf("https://foo/%zu", i));
RunScoreAdOnWorkletAsync(seller_worklet.get(), i,
std::vector<std::string>(),
mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
{},
{},
std::nullopt,
false,
std::nullopt,
std::nullopt,
base::BindLambdaForTesting([&]() {
++num_completed_worklets;
if (num_completed_worklets == kNumWorklets) {
run_loop.Quit();
}
}));
}
run_loop.Run();
if (NumThreads() == 1) {
for (size_t i = 0; i < kNumWorklets; ++i) {
channel->WaitForAndValidateConsoleMessage(
"log",
base::StringPrintf("[{\"type\":\"string\", \"value\":\"%i\"}]", i),
1, "scoreAd", decision_logic_url_,
3);
}
}
histogram_tester.ExpectUniqueSample(
"Ads.InterestGroup.Auction.UsedPremadeContextForSellerWorklet",
PrepareContexts(), kNumWorklets);
}
TEST_P(SellerWorkletMultiThreadingTest, ScoreAdParallelLoadFails) {
auto seller_worklet = CreateWorklet();
for (size_t i = 0; i < 10; ++i) {
browser_signal_render_url_ = GURL(base::StringPrintf("https://foo/%zu", i));
RunScoreAdOnWorkletExpectingCallbackNeverInvoked(seller_worklet.get());
}
task_environment_.RunUntilIdle();
url_loader_factory_.AddResponse(decision_logic_url_.spec(),
std::string(),
net::HTTP_NOT_FOUND);
EXPECT_EQ("Failed to load https://url.test/ HTTP status = 404 Not Found.",
WaitForDisconnect());
task_environment_.RunUntilIdle();
}
TEST_P(SellerWorkletMultiThreadingTest,
ScoreAdParallelTrustedScoringSignalsNotBatched) {
base::Time start_time = base::Time::Now();
base::HistogramTester histogram_tester;
AddJavascriptResponse(
&url_loader_factory_, decision_logic_url_,
CreateScoreAdScript(
"trustedScoringSignals.renderURL[browserSignals.renderURL]"));
trusted_scoring_signals_url_ =
GURL("https://url.test/trusted_scoring_signals");
auto seller_worklet = CreateWorklet();
task_environment_.RunUntilIdle();
const size_t kNumWorklets = 10;
size_t num_completed_worklets = 0;
base::RunLoop run_loop;
for (size_t i = 0; i < kNumWorklets; ++i) {
browser_signal_render_url_ = GURL(base::StringPrintf("https://foo/%zu", i));
RunScoreAdOnWorkletAsync(seller_worklet.get(), 2 * i,
std::vector<std::string>(),
mojom::ComponentAuctionModifiedBidParamsPtr(),
i,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
{},
{},
std::nullopt,
false,
std::nullopt,
std::nullopt,
base::BindLambdaForTesting([&]() {
++num_completed_worklets;
if (num_completed_worklets == kNumWorklets) {
run_loop.Quit();
}
}));
seller_worklet->SendPendingSignalsRequests();
}
run_loop.RunUntilIdle();
EXPECT_EQ(0u, num_completed_worklets);
for (size_t i = 0; i < kNumWorklets; ++i) {
GURL trusted_scoring_signals = GURL(base::StringPrintf(
"%s?hostname=%s&renderUrls=https%%3A%%2F%%2Ffoo%%2F%zu",
trusted_scoring_signals_url_->spec().c_str(),
top_window_origin_.host().c_str(), i));
std::string response_body = base::StringPrintf(
R"({"renderUrls": {"https://foo/%zu": %zu}})", i, 2 * i);
AddVersionedJsonResponse(&url_loader_factory_, trusted_scoring_signals,
response_body, i);
}
run_loop.Run();
EXPECT_EQ(base::Time::Now(), start_time);
if (PrepareContexts()) {
histogram_tester.ExpectBucketCount(
"Ads.InterestGroup.Auction.UsedPremadeContextForSellerWorklet", 1,
4 * NumThreads());
histogram_tester.ExpectBucketCount(
"Ads.InterestGroup.Auction.UsedPremadeContextForSellerWorklet", 0,
10 - (4 * NumThreads()));
} else {
histogram_tester.ExpectUniqueSample(
"Ads.InterestGroup.Auction.UsedPremadeContextForSellerWorklet", 0,
kNumWorklets);
}
}
TEST_P(SellerWorkletMultiThreadingTest,
ScoreAdParallelTrustedScoringSignalsBatched1) {
AddJavascriptResponse(
&url_loader_factory_, decision_logic_url_,
CreateScoreAdScript(
"trustedScoringSignals.renderURL[browserSignals.renderURL]",
"console.log(browserSignals.renderURL.slice(-1));"));
trusted_scoring_signals_url_ =
GURL("https://url.test/trusted_scoring_signals");
ScopedInspectorSupport inspector_support(v8_helper().get());
SellerWorklet* worklet_impl = nullptr;
auto seller_worklet =
CreateWorklet(false, &worklet_impl);
int id = worklet_impl->context_group_ids_for_testing()[0];
TestChannel* channel =
inspector_support.ConnectDebuggerSessionAndRuntimeEnable(id);
const size_t kNumWorklets = 10;
size_t num_completed_worklets = 0;
base::RunLoop run_loop;
for (size_t i = 0; i < kNumWorklets; ++i) {
browser_signal_render_url_ = GURL(base::StringPrintf("https://foo/%zu", i));
RunScoreAdOnWorkletAsync(seller_worklet.get(), 2 * i,
std::vector<std::string>(),
mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
{},
{},
std::nullopt,
false,
std::nullopt,
std::nullopt,
base::BindLambdaForTesting([&]() {
++num_completed_worklets;
if (num_completed_worklets == kNumWorklets) {
run_loop.Quit();
}
}));
}
run_loop.RunUntilIdle();
EXPECT_EQ(0u, num_completed_worklets);
std::string request_url =
base::StringPrintf("%s?hostname=%s&renderUrls=",
trusted_scoring_signals_url_->spec().c_str(),
top_window_origin_.host().c_str());
std::string response_body;
for (size_t i = 0; i < kNumWorklets; ++i) {
if (i > 0) {
request_url += ",";
response_body += ",";
}
request_url += base::StringPrintf("https%%3A%%2F%%2Ffoo%%2F%zu", i);
response_body += base::StringPrintf(R"("https://foo/%zu": %zu)", i, 2 * i);
}
response_body =
base::StringPrintf(R"({"renderUrls": {%s}})", response_body.c_str());
AddJsonResponse(&url_loader_factory_, GURL(request_url), response_body);
run_loop.Run();
if (NumThreads() == 1) {
for (size_t i = 0; i < kNumWorklets; ++i) {
channel->WaitForAndValidateConsoleMessage(
"log",
base::StringPrintf("[{\"type\":\"string\", \"value\":\"%i\"}]", i),
1, "scoreAd", decision_logic_url_,
3);
}
}
}
TEST_P(SellerWorkletMultiThreadingTest,
ScoreAdParallelTrustedScoringSignalsBatched2) {
trusted_scoring_signals_url_ =
GURL("https://url.test/trusted_scoring_signals");
auto seller_worklet = CreateWorklet();
const size_t kNumWorklets = 10;
size_t num_completed_worklets = 0;
base::RunLoop run_loop;
for (size_t i = 0; i < kNumWorklets; ++i) {
browser_signal_render_url_ = GURL(base::StringPrintf("https://foo/%zu", i));
RunScoreAdOnWorkletAsync(seller_worklet.get(), 2 * i,
std::vector<std::string>(),
mojom::ComponentAuctionModifiedBidParamsPtr(),
10,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
{},
{},
std::nullopt,
false,
std::nullopt,
std::nullopt,
base::BindLambdaForTesting([&]() {
++num_completed_worklets;
if (num_completed_worklets == kNumWorklets) {
run_loop.Quit();
}
}));
}
run_loop.RunUntilIdle();
EXPECT_EQ(0u, num_completed_worklets);
AddJavascriptResponse(
&url_loader_factory_, decision_logic_url_,
CreateScoreAdScript(
"trustedScoringSignals.renderURL[browserSignals.renderURL]"));
task_environment_.RunUntilIdle();
std::string request_url =
base::StringPrintf("%s?hostname=%s&renderUrls=",
trusted_scoring_signals_url_->spec().c_str(),
top_window_origin_.host().c_str());
std::string response_body;
for (size_t i = 0; i < kNumWorklets; ++i) {
if (i > 0) {
request_url += ",";
response_body += ",";
}
request_url += base::StringPrintf("https%%3A%%2F%%2Ffoo%%2F%zu", i);
response_body += base::StringPrintf(R"("https://foo/%zu": %zu)", i, 2 * i);
}
response_body =
base::StringPrintf(R"({"renderUrls": {%s}})", response_body.c_str());
AddVersionedJsonResponse(&url_loader_factory_, GURL(request_url),
response_body, 10);
run_loop.Run();
}
TEST_P(SellerWorkletMultiThreadingTest,
ScoreAdParallelTrustedScoringSignalsBatched3) {
trusted_scoring_signals_url_ =
GURL("https://url.test/trusted_scoring_signals");
auto seller_worklet = CreateWorklet();
const size_t kNumWorklets = 10;
size_t num_completed_worklets = 0;
base::RunLoop run_loop;
for (size_t i = 0; i < kNumWorklets; ++i) {
browser_signal_render_url_ = GURL(base::StringPrintf("https://foo/%zu", i));
RunScoreAdOnWorkletAsync(seller_worklet.get(), 2 * i,
std::vector<std::string>(),
mojom::ComponentAuctionModifiedBidParamsPtr(),
10,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
{},
{},
std::nullopt,
false,
std::nullopt,
std::nullopt,
base::BindLambdaForTesting([&]() {
++num_completed_worklets;
if (num_completed_worklets == kNumWorklets) {
run_loop.Quit();
}
}));
}
run_loop.RunUntilIdle();
EXPECT_EQ(0u, num_completed_worklets);
std::string request_url =
base::StringPrintf("%s?hostname=%s&renderUrls=",
trusted_scoring_signals_url_->spec().c_str(),
top_window_origin_.host().c_str());
std::string response_body;
for (size_t i = 0; i < kNumWorklets; ++i) {
if (i > 0) {
request_url += ",";
response_body += ",";
}
request_url += base::StringPrintf("https%%3A%%2F%%2Ffoo%%2F%zu", i);
response_body += base::StringPrintf(R"("https://foo/%zu": %zu)", i, 2 * i);
}
response_body =
base::StringPrintf(R"({"renderUrls": {%s}})", response_body.c_str());
AddVersionedJsonResponse(&url_loader_factory_, GURL(request_url),
response_body, 10);
run_loop.RunUntilIdle();
EXPECT_EQ(0u, num_completed_worklets);
AddJavascriptResponse(
&url_loader_factory_, decision_logic_url_,
CreateScoreAdScript(
"trustedScoringSignals.renderURL[browserSignals.renderURL]"));
run_loop.Run();
}
TEST_F(SellerWorkletTest, ScoreAdLoadCompletionOrder) {
constexpr char kJsonResponse[] = "{}";
constexpr char kDirectFromSellerSignalsHeaders[] =
"Ad-Auction-Allowed: true\nAd-Auction-Only: true";
direct_from_seller_seller_signals_ = GURL("https://url.test/sellersignals");
direct_from_seller_auction_signals_ = GURL("https://url.test/auctionsignals");
trusted_scoring_signals_url_ = GURL("https://url.test/trustedsignals");
struct Response {
GURL response_url;
std::string response_type;
std::string headers;
std::string content;
};
const auto kResponses = std::to_array<Response>(
{{decision_logic_url_, kJavascriptMimeType, kAllowFledgeHeader,
CreateScoreAdScript("1")},
{*direct_from_seller_seller_signals_, kJsonMimeType,
kDirectFromSellerSignalsHeaders, kJsonResponse},
{*direct_from_seller_auction_signals_, kJsonMimeType,
kDirectFromSellerSignalsHeaders, kJsonResponse},
{GURL(trusted_scoring_signals_url_->spec() +
"?hostname=window.test"
"&renderUrls=https%3A%2F%2Frender.url.test%2F"),
kJsonMimeType, kAllowFledgeHeader, kTrustedScoringSignalsResponse}});
for (size_t offset = 0; offset < std::size(kResponses); ++offset) {
SCOPED_TRACE(offset);
mojo::Remote<mojom::SellerWorklet> seller_worklet = CreateWorklet();
url_loader_factory_.ClearResponses();
auto run_loop = std::make_unique<base::RunLoop>();
RunScoreAdOnWorkletAsync(
seller_worklet.get(), 1.0,
{}, mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
{},
{},
std::nullopt,
false,
std::nullopt,
std::nullopt, run_loop->QuitClosure());
for (size_t i = 0; i < std::size(kResponses); ++i) {
SCOPED_TRACE(i);
const Response& response =
kResponses[(i + offset) % std::size(kResponses)];
AddResponse(&url_loader_factory_, response.response_url,
response.response_type,
std::nullopt, response.content, response.headers);
task_environment_.RunUntilIdle();
if (i < std::size(kResponses) - 1) {
EXPECT_FALSE(run_loop->AnyQuitCalled());
}
}
run_loop->Run();
}
}
TEST_F(SellerWorkletTest, ScoreAdDirectFromSellerSignalsMultipleWorklets) {
constexpr char kWorklet1JsonResponse[] = R"({"worklet":1})";
constexpr char kWorklet2JsonResponse[] = R"({"worklet":2})";
constexpr char kWorklet1ExtraCode[] = R"(
const sellerSignalsJson =
JSON.stringify(directFromSellerSignals.sellerSignals);
if (sellerSignalsJson !== '{"worklet":1}') {
throw 'Wrong directFromSellerSignals.sellerSignals ' +
sellerSignalsJson;
}
const auctionSignalsJson =
JSON.stringify(directFromSellerSignals.auctionSignals);
if (auctionSignalsJson !== '{"worklet":1}') {
throw 'Wrong directFromSellerSignals.auctionSignals ' +
auctionSignalsJson;
}
)";
constexpr char kWorklet2ExtraCode[] = R"(
const sellerSignalsJson =
JSON.stringify(directFromSellerSignals.sellerSignals);
if (sellerSignalsJson !== '{"worklet":2}') {
throw 'Wrong directFromSellerSignals.sellerSignals ' +
sellerSignalsJson;
}
const auctionSignalsJson =
JSON.stringify(directFromSellerSignals.auctionSignals);
if (auctionSignalsJson !== '{"worklet":2}') {
throw 'Wrong directFromSellerSignals.auctionSignals ' +
auctionSignalsJson;
}
)";
constexpr char kDirectFromSellerSignalsHeaders[] =
"Ad-Auction-Allowed: true\nAd-Auction-Only: true";
direct_from_seller_seller_signals_ = GURL("https://url.test/sellersignals");
direct_from_seller_auction_signals_ = GURL("https://url.test/auctionsignals");
mojo::Remote<mojom::SellerWorklet> seller_worklet1 = CreateWorklet();
AddResponse(&url_loader_factory_, *direct_from_seller_seller_signals_,
kJsonMimeType, std::nullopt, kWorklet1JsonResponse,
kDirectFromSellerSignalsHeaders);
AddResponse(&url_loader_factory_, *direct_from_seller_auction_signals_,
kJsonMimeType, std::nullopt, kWorklet1JsonResponse,
kDirectFromSellerSignalsHeaders);
AddJavascriptResponse(
&url_loader_factory_, decision_logic_url_,
CreateScoreAdScript("1", kWorklet1ExtraCode));
decision_logic_url_ = GURL("https://url2.test/");
mojo::Remote<mojom::SellerWorklet> seller_worklet2 = CreateWorklet(
false,
nullptr,
true);
AddResponse(&alternate_url_loader_factory_,
*direct_from_seller_seller_signals_, kJsonMimeType,
std::nullopt, kWorklet2JsonResponse,
kDirectFromSellerSignalsHeaders);
AddResponse(&alternate_url_loader_factory_,
*direct_from_seller_auction_signals_, kJsonMimeType,
std::nullopt, kWorklet2JsonResponse,
kDirectFromSellerSignalsHeaders);
AddJavascriptResponse(
&alternate_url_loader_factory_, decision_logic_url_,
CreateScoreAdScript("1", kWorklet2ExtraCode));
auto run_loop = std::make_unique<base::RunLoop>();
RunScoreAdOnWorkletAsync(
seller_worklet1.get(), 1.0,
{}, mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
{},
{},
std::nullopt,
false,
std::nullopt,
std::nullopt, run_loop->QuitClosure());
run_loop->Run();
run_loop = std::make_unique<base::RunLoop>();
RunScoreAdOnWorkletAsync(
seller_worklet2.get(), 1.0,
{}, mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
{},
{},
std::nullopt,
false,
std::nullopt,
std::nullopt, run_loop->QuitClosure());
run_loop->Run();
}
TEST_F(SellerWorkletTest, ReportResultParallel) {
auto seller_worklet = CreateWorklet();
for (bool report_result_invoked_before_worklet_script_loaded :
{false, true}) {
SCOPED_TRACE(report_result_invoked_before_worklet_script_loaded);
base::RunLoop run_loop;
const size_t kNumReportResultCalls = 10;
size_t num_report_result_calls = 0;
for (size_t i = 0; i < kNumReportResultCalls; ++i) {
bid_ = i + 1;
RunReportResultExpectingResultAsync(
seller_worklet.get(),
base::NumberToString(bid_),
GURL("https://" + base::NumberToString(bid_)),
{},
{},
false,
{},
base::BindLambdaForTesting([&run_loop, &num_report_result_calls]() {
++num_report_result_calls;
if (num_report_result_calls == kNumReportResultCalls) {
run_loop.Quit();
}
}));
}
if (report_result_invoked_before_worklet_script_loaded == false) {
task_environment_.RunUntilIdle();
EXPECT_FALSE(run_loop.AnyQuitCalled());
AddJavascriptResponse(
&url_loader_factory_, decision_logic_url_,
CreateReportToScript(
"browserSignals.bid",
R"(sendReportTo("https://" + browserSignals.bid))"));
}
run_loop.Run();
EXPECT_EQ(kNumReportResultCalls, num_report_result_calls);
}
}
TEST_F(SellerWorkletTest, ReportResultParallelLoadFails) {
auto seller_worklet = CreateWorklet();
for (size_t i = 0; i < 10; ++i) {
RunReportResultExpectingCallbackNeverInvoked(seller_worklet.get());
}
url_loader_factory_.AddResponse(decision_logic_url_.spec(), "Response body",
net::HTTP_NOT_FOUND);
EXPECT_EQ("Failed to load https://url.test/ HTTP status = 404 Not Found.",
WaitForDisconnect());
}
TEST_F(SellerWorkletTest, ReportResult) {
RunReportResultCreatedScriptExpectingResult(
"1", std::string(),
"1",
std::nullopt);
RunReportResultCreatedScriptExpectingResult(
R"(" 1 ")", std::string(),
R"(" 1 ")",
std::nullopt);
RunReportResultCreatedScriptExpectingResult(
"[ null ]", std::string(), "[null]",
std::nullopt);
RunReportResultCreatedScriptExpectingResult(
"", std::string(), "null",
std::nullopt);
RunReportResultCreatedScriptExpectingResult(
"shrimp", std::string(),
std::nullopt,
std::nullopt,
{},
{},
{"https://url.test/:11 Uncaught ReferenceError: "
"shrimp is not defined."});
}
TEST_F(SellerWorkletTest, ReportResultSendReportTo) {
RunReportResultCreatedScriptExpectingResult(
"1", R"(sendReportTo("https://foo.test"))",
"1", GURL("https://foo.test/"));
RunReportResultCreatedScriptExpectingResult(
"1", R"(sendReportTo("https://foo.test/bar"))",
"1", GURL("https://foo.test/bar"));
RunReportResultCreatedScriptExpectingResult(
"1", R"(sendReportTo("http://foo.test/"))",
std::nullopt,
std::nullopt,
{},
{},
{"https://url.test/:10 Uncaught TypeError: "
"sendReportTo must be passed a valid HTTPS url."});
RunReportResultCreatedScriptExpectingResult(
"1", R"(sendReportTo("file:///foo/"))",
std::nullopt,
std::nullopt,
{},
{},
{"https://url.test/:10 Uncaught TypeError: "
"sendReportTo must be passed a valid HTTPS url."});
RunReportResultCreatedScriptExpectingResult(
"1",
R"(sendReportTo("https://foo.test/"); sendReportTo("https://foo.test/"))",
std::nullopt,
std::nullopt,
{},
{},
{"https://url.test/:10 Uncaught TypeError: "
"sendReportTo may be called at most once."});
RunReportResultCreatedScriptExpectingResult(
"1",
R"(try {
sendReportTo("https://foo.test/");
sendReportTo("https://foo.test/")} catch(e) {})",
"1",
std::nullopt);
RunReportResultCreatedScriptExpectingResult(
"1", R"(sendReportTo("France"))",
std::nullopt,
std::nullopt,
{},
{},
{"https://url.test/:10 Uncaught TypeError: "
"sendReportTo must be passed a valid HTTPS url."});
RunReportResultCreatedScriptExpectingResult(
"1", R"(sendReportTo(null))",
std::nullopt,
std::nullopt,
{},
{},
{"https://url.test/:10 Uncaught TypeError: "
"sendReportTo must be passed a valid HTTPS url."});
RunReportResultCreatedScriptExpectingResult(
"1", R"(sendReportTo([5]))",
std::nullopt,
std::nullopt,
{},
{},
{"https://url.test/:10 Uncaught TypeError: "
"sendReportTo must be passed a valid HTTPS url."});
}
TEST_F(SellerWorkletTest, ReportResultDateNotAvailable) {
RunReportResultCreatedScriptExpectingResult(
"1", R"(sendReportTo("https://foo.test/" + Date().toString()))",
std::nullopt,
std::nullopt,
{},
{},
{"https://url.test/:10 Uncaught ReferenceError: Date is not defined."});
}
TEST_F(SellerWorkletTest, ReportResultNoAdComponentsCreativeScanningMetadata) {
send_creative_scanning_metadata_ = true;
component_ads_.clear();
component_ads_.push_back(
auction_worklet::mojom::CreativeInfoWithoutOwner::New(
blink::AdDescriptor(GURL("https://bar.test/path")),
"stuff"));
const char kScript[] = R"(
sendReportTo("https://foo.test?" +
('adComponentsCreativeScanningMetadata' in browserSignals? 3 : 2));
)";
RunReportResultCreatedScriptExpectingResult(
"1", kScript,
"1", GURL("https://foo.test/?2"));
}
TEST_F(SellerWorkletNoTextConversionsTest, ReportResultTextConversions) {
RunReportResultCreatedScriptExpectingResult(
"('protectedAudience' in globalThis) ? 2 : 1",
std::string(), "1",
std::nullopt);
}
TEST_F(SellerWorkletTest, ReportResultTextConversions) {
RunReportResultCreatedScriptExpectingResult(
"('encodeUtf8' in protectedAudience) ? 2 : 1",
std::string(), "2",
std::nullopt);
RunReportResultCreatedScriptExpectingResult(
"('decodeUtf8' in protectedAudience) ? 2 : 1",
std::string(), "2",
std::nullopt);
}
TEST_F(SellerWorkletTest, ReportResultNoGlobalStomp) {
const char kScript[] = R"(
function protectedAudience() {
sendReportTo('https://report.test/');
}
function reportResult() {
protectedAudience();
}
)";
RunReportResultWithJavascriptExpectingResult(
kScript,
"null",
GURL("https://report.test"));
}
TEST_F(SellerWorkletTest, ReportResultTopWindowOrigin) {
top_window_origin_ = url::Origin::Create(GURL("https://foo.test/"));
RunReportResultCreatedScriptExpectingResult(
R"(browserSignals.topWindowHostname == "foo.test" ? 2 : 1)",
std::string(), "2",
std::nullopt);
top_window_origin_ = url::Origin::Create(GURL("https://[::1]:40000/"));
RunReportResultCreatedScriptExpectingResult(
R"(browserSignals.topWindowHostname == "[::1]" ? 3 : 1)",
std::string(), "3",
std::nullopt);
}
TEST_F(SellerWorkletTest, ReportResultTopLevelSeller) {
browser_signals_other_seller_.reset();
RunReportResultCreatedScriptExpectingResult(
R"("topLevelSeller" in browserSignals ? 0 : 1)",
std::string(), "1",
std::nullopt);
browser_signals_other_seller_ =
mojom::ComponentAuctionOtherSeller::NewTopLevelSeller(
url::Origin::Create(GURL("https://top.seller.test")));
browser_signals_component_auction_report_result_params_ =
mojom::ComponentAuctionReportResultParams::New(
"null",
std::nullopt);
RunReportResultCreatedScriptExpectingResult(
R"(browserSignals.topLevelSeller === "https://top.seller.test" ? 2 : 0)",
std::string(), "2",
std::nullopt);
browser_signals_component_auction_report_result_params_.reset();
browser_signals_other_seller_ =
mojom::ComponentAuctionOtherSeller::NewComponentSeller(
url::Origin::Create(GURL("https://component.test")));
RunReportResultCreatedScriptExpectingResult(
R"("topLevelSeller" in browserSignals ? 0 : 3)",
std::string(), "3",
std::nullopt);
}
TEST_F(SellerWorkletTest, ReportResultComponentSeller) {
browser_signals_other_seller_.reset();
RunReportResultCreatedScriptExpectingResult(
R"("componentSeller" in browserSignals ? 0 : 1)",
std::string(), "1",
std::nullopt);
browser_signals_other_seller_ =
mojom::ComponentAuctionOtherSeller::NewTopLevelSeller(
url::Origin::Create(GURL("https://top.seller.test")));
browser_signals_component_auction_report_result_params_ =
mojom::ComponentAuctionReportResultParams::New(
"null",
std::nullopt);
RunReportResultCreatedScriptExpectingResult(
R"("componentSeller" in browserSignals ? 0 : 2)",
std::string(), "2",
std::nullopt);
browser_signals_component_auction_report_result_params_.reset();
browser_signals_other_seller_ =
mojom::ComponentAuctionOtherSeller::NewComponentSeller(
url::Origin::Create(GURL("https://component.test")));
RunReportResultCreatedScriptExpectingResult(
R"(browserSignals.componentSeller === "https://component.test" ? 3 : 0)",
std::string(), "3",
std::nullopt);
}
TEST_F(SellerWorkletTest, ReportResultTopLevelSellerSignals) {
browser_signals_other_seller_.reset();
RunReportResultCreatedScriptExpectingResult(
"'topLevelSellerSignals' in browserSignals ? 0 : 1",
std::string(), "1",
std::nullopt);
browser_signals_other_seller_ =
mojom::ComponentAuctionOtherSeller::NewComponentSeller(
url::Origin::Create(GURL("https://component.test")));
RunReportResultCreatedScriptExpectingResult(
"'topLevelSellerSignals' in browserSignals ? 0 : 2",
std::string(), "2",
std::nullopt);
browser_signals_other_seller_ =
mojom::ComponentAuctionOtherSeller::NewTopLevelSeller(
url::Origin::Create(GURL("https://top.seller.test")));
browser_signals_component_auction_report_result_params_ =
mojom::ComponentAuctionReportResultParams::New(
"null",
std::nullopt);
RunReportResultCreatedScriptExpectingResult(
"browserSignals.topLevelSellerSignals === null ? 3 : 0",
std::string(), "3",
std::nullopt);
browser_signals_component_auction_report_result_params_
->top_level_seller_signals = "[4]";
RunReportResultCreatedScriptExpectingResult(
"browserSignals.topLevelSellerSignals[0]",
std::string(), "4",
std::nullopt);
}
TEST_F(SellerWorkletTest, ReportResultModifiedBid) {
browser_signals_other_seller_.reset();
RunReportResultCreatedScriptExpectingResult(
"'modifiedBid' in browserSignals ? 0 : 1",
std::string(), "1",
std::nullopt);
browser_signals_other_seller_ =
mojom::ComponentAuctionOtherSeller::NewComponentSeller(
url::Origin::Create(GURL("https://component.test")));
RunReportResultCreatedScriptExpectingResult(
"'modifiedBid' in browserSignals ? 0 : 2",
std::string(), "2",
std::nullopt);
browser_signals_other_seller_ =
mojom::ComponentAuctionOtherSeller::NewTopLevelSeller(
url::Origin::Create(GURL("https://top.seller.test")));
browser_signals_component_auction_report_result_params_ =
mojom::ComponentAuctionReportResultParams::New(
"null",
4);
RunReportResultCreatedScriptExpectingResult(
"browserSignals.modifiedBid",
std::string(), "4",
std::nullopt);
browser_signals_component_auction_report_result_params_->modified_bid =
std::nullopt;
RunReportResultCreatedScriptExpectingResult(
"'modifiedBid' in browserSignals ? 0 : 3",
std::string(), "3",
std::nullopt);
}
TEST_F(SellerWorkletTest, ReportResultInterestGroupOwner) {
browser_signal_interest_group_owner_ =
url::Origin::Create(GURL("https://foo.test/"));
RunReportResultCreatedScriptExpectingResult(
R"(browserSignals.interestGroupOwner == "https://foo.test" ? 2 : 1)",
std::string(), "2",
std::nullopt);
browser_signal_interest_group_owner_ =
url::Origin::Create(GURL("https://[::1]:40000/"));
RunReportResultCreatedScriptExpectingResult(
R"(browserSignals.interestGroupOwner == "https://[::1]:40000" ? 3 : 1)",
std::string(), "3",
std::nullopt);
}
TEST_F(SellerWorkletTest, ReportResultBuyerAndSellerReportingId) {
browser_signal_buyer_and_seller_reporting_id_ = "campaign";
RunReportResultCreatedScriptExpectingResult(
R"(browserSignals.buyerAndSellerReportingId === "campaign" ? 2 : 1)",
std::string(), "2",
std::nullopt);
}
TEST_F(SellerWorkletTest, ReportResultSelectedBuyerAndSellerReportingId) {
browser_signal_selected_buyer_and_seller_reporting_id_ = "selectable_id1";
RunReportResultCreatedScriptExpectingResult(
R"(browserSignals.selectedBuyerAndSellerReportingId === "selectable_id1" ? 2 : 1)",
std::string(), "2",
std::nullopt);
}
TEST_F(SellerWorkletTest, ReportResultRenderUrl) {
browser_signal_render_url_ = GURL("https://foo/");
RunReportResultCreatedScriptExpectingResult(
"browserSignals.renderURL", "sendReportTo(browserSignals.renderURL)",
R"("https://foo/")", browser_signal_render_url_);
}
TEST_F(SellerWorkletTest, ReportResultRenderUrlDeprecationWarning) {
ScopedInspectorSupport inspector_support(v8_helper().get());
AddJavascriptResponse(&url_loader_factory_, decision_logic_url_,
CreateReportToScript("browserSignals.renderUrl"));
SellerWorklet* worklet_impl = nullptr;
auto worklet =
CreateWorklet(false, &worklet_impl);
int id = worklet_impl->context_group_ids_for_testing()[0];
TestChannel* channel =
inspector_support.ConnectDebuggerSessionAndRuntimeEnable(id);
base::RunLoop run_loop;
RunReportResultExpectingResultAsync(
worklet_impl,
base::StringPrintf("\"%s\"", browser_signal_render_url_.spec().c_str()),
std::nullopt,
{}, {},
false, {},
run_loop.QuitClosure());
run_loop.Run();
channel->WaitForAndValidateConsoleMessage(
"warning",
"[{\"type\":\"string\", "
"\"value\":\"browserSignals.renderUrl is deprecated. Please use "
"browserSignals.renderURL instead.\"}]",
1, "reportResult", decision_logic_url_,
10);
channel->ExpectNoMoreConsoleEvents();
}
TEST_F(SellerWorkletTest, ReportResultRenderUrlNoDeprecationWarning) {
ScopedInspectorSupport inspector_support(v8_helper().get());
AddJavascriptResponse(&url_loader_factory_, decision_logic_url_,
CreateReportToScript("browserSignals.renderURL"));
SellerWorklet* worklet_impl = nullptr;
auto worklet =
CreateWorklet(false, &worklet_impl);
int id = worklet_impl->context_group_ids_for_testing()[0];
TestChannel* channel =
inspector_support.ConnectDebuggerSessionAndRuntimeEnable(id);
base::RunLoop run_loop;
RunReportResultExpectingResultAsync(
worklet_impl,
base::StringPrintf("\"%s\"", browser_signal_render_url_.spec().c_str()),
std::nullopt,
{}, {},
false, {},
run_loop.QuitClosure());
run_loop.Run();
channel->ExpectNoMoreConsoleEvents();
}
TEST_F(SellerWorkletTest, ReportResultRegisterAdBeacon) {
bid_ = 5;
base::flat_map<std::string, GURL> expected_ad_beacon_map = {
{"click", GURL("https://click.example.test/")},
{"view", GURL("https://view.example.test/")},
};
RunReportResultCreatedScriptExpectingResult(
R"(5)",
R"(registerAdBeacon({
'click': "https://click.example.test/",
'view': "https://view.example.test/",
}))",
"5",
std::nullopt, expected_ad_beacon_map);
browser_signal_render_url_ = GURL("https://foo/");
RunReportResultCreatedScriptExpectingResult(
R"(5)",
R"(registerAdBeacon({
'click': "https://click.example.test/",
'view': "https://view.example.test/",
});
sendReportTo(browserSignals.renderURL))",
"5",
browser_signal_render_url_,
expected_ad_beacon_map);
RunReportResultCreatedScriptExpectingResult(
R"(5)",
R"(sendReportTo(browserSignals.renderURL);
registerAdBeacon({
'click': "https://click.example.test/",
'view': "https://view.example.test/",
}))",
"5",
browser_signal_render_url_,
expected_ad_beacon_map);
RunReportResultCreatedScriptExpectingResult(
R"(5)",
R"(registerAdBeacon({
'click': "https://click.example.test/",
'view': "https://view.example.test/",
});
registerAdBeacon())",
{},
std::nullopt,
{},
{},
{"https://url.test/:14 Uncaught TypeError: registerAdBeacon may be "
"called at most once."});
RunReportResultCreatedScriptExpectingResult(
R"(5)",
R"(registerAdBeacon({
'click': "https://click.example.test/",
'view': "https://view.example.test/",
});
try { registerAdBeacon() }
catch (e) {})",
"5",
std::nullopt, expected_ad_beacon_map);
RunReportResultCreatedScriptExpectingResult(
R"(5)",
R"(try { registerAdBeacon() }
catch (e) {}
registerAdBeacon({
'click': "https://click.example.test/",
'view': "https://view.example.test/",
}))",
"5",
std::nullopt, expected_ad_beacon_map);
RunReportResultCreatedScriptExpectingResult(
R"(5)", R"(registerAdBeacon())",
{},
std::nullopt,
{},
{},
{"https://url.test/:10 Uncaught TypeError: registerAdBeacon(): at least "
"1 argument(s) are required."});
RunReportResultCreatedScriptExpectingResult(
R"(5)", R"(registerAdBeacon("foo"))",
{},
std::nullopt,
{},
{},
{"https://url.test/:10 Uncaught TypeError: registerAdBeacon(): Cannot "
"convert argument 'map' to a record since it's not an Object."});
RunReportResultCreatedScriptExpectingResult(
R"(5)",
R"(registerAdBeacon({
'click': "https://click.example.test/",
1: "https://view.example.test/",
}))",
"5",
std::nullopt,
{
{"click", GURL("https://click.example.test/")},
{"1", GURL("https://view.example.test/")},
},
{}, {});
RunReportResultCreatedScriptExpectingResult(
R"(5)",
R"(let map = {
'click': "https://click.example.test/"
}
map[Symbol('a')] = "https://view.example.test/";
registerAdBeacon(map))",
{},
std::nullopt,
{},
{},
{"https://url.test/:14 Uncaught TypeError: Cannot convert a Symbol value "
"to a string."});
RunReportResultCreatedScriptExpectingResult(
R"(5)",
R"(registerAdBeacon({
'click': "https://click.example.test/",
'view': "gopher://view.example.test/",
}))",
{},
std::nullopt,
{},
{},
{"https://url.test/:10 Uncaught TypeError: registerAdBeacon(): invalid "
"reporting url for key 'view': 'gopher://view.example.test/'."});
RunReportResultCreatedScriptExpectingResult(
R"(5)",
R"(registerAdBeacon({
'click': "https://127.0.0.1/",
'view': "http://view.example.test/",
}))",
{},
std::nullopt,
{},
{},
{"https://url.test/:10 Uncaught TypeError: registerAdBeacon(): invalid "
"reporting url for key 'view': 'http://view.example.test/'."});
RunReportResultCreatedScriptExpectingResult(
R"(5)",
R"(registerAdBeacon({
'click': "https://127.0.0.1/",
'reserved.bogus': "https://view.example.test/",
}))",
{},
std::nullopt,
{},
{},
{"https://url.test/:10 Uncaught TypeError: registerAdBeacon(): Invalid "
"reserved type 'reserved.bogus' cannot be used."});
RunReportResultCreatedScriptExpectingResult(
R"(5)",
R"(registerAdBeacon({
'\ud835': "http://127.0.0.1/",
}))",
{},
std::nullopt,
{},
{},
{"https://url.test/:10 Uncaught TypeError: registerAdBeacon(): invalid "
"reporting url."});
}
TEST_F(SellerWorkletTest, ReportResultBid) {
bid_ = 5;
RunReportResultCreatedScriptExpectingResult(
"browserSignals.bid + typeof browserSignals.bid",
std::string(), R"("5number")",
std::nullopt);
}
TEST_F(SellerWorkletTest, ReportResultBidCurrency) {
bid_currency_ = blink::AdCurrency::From("EUR");
RunReportResultCreatedScriptExpectingResult(
"browserSignals.bidCurrency + typeof browserSignals.bidCurrency",
std::string(), R"("EURstring")",
std::nullopt);
}
TEST_F(SellerWorkletTest, ReportResultDesireability) {
browser_signal_desireability_ = 10;
RunReportResultCreatedScriptExpectingResult(
"browserSignals.desirability + typeof browserSignals.desirability",
std::string(), R"("10number")",
std::nullopt);
}
TEST_F(SellerWorkletTest, ReportResultHighestScoringOtherBid) {
browser_signal_highest_scoring_other_bid_ = 5;
RunReportResultCreatedScriptExpectingResult(
"browserSignals.highestScoringOtherBid + typeof "
"browserSignals.highestScoringOtherBid",
std::string(), R"("5number")",
std::nullopt);
}
TEST_F(SellerWorkletTest, ReportResultHighestScoringOtherBidCurrency) {
browser_signal_highest_scoring_other_bid_currency_ =
blink::AdCurrency::From("EUR");
RunReportResultCreatedScriptExpectingResult(
"browserSignals.highestScoringOtherBidCurrency + typeof "
"browserSignals.highestScoringOtherBidCurrency",
std::string(), R"("EURstring")",
std::nullopt);
}
TEST_F(SellerWorkletTest, ReportResultAuctionConfigParam) {
decision_logic_url_ = GURL("https://example.test/auction.js");
RunReportResultCreatedScriptExpectingResult(
"auctionConfig", std::string(),
base::StringPrintf(
R"({"seller":"https://example.test",)"
R"("decisionLogicURL":"https://example.test/auction.js",)"
R"("decisionLogicUrl":"https://example.test/auction.js",)"
R"("reportingTimeout": %s})",
base::NumberToString(AuctionV8Helper::kScriptTimeout.InMilliseconds())
.c_str()),
std::nullopt);
decision_logic_url_ = GURL("https://example.test/auction.js");
trusted_scoring_signals_url_ =
GURL("https://example.test/scoring_signals.json");
auction_ad_config_non_shared_params_.interest_group_buyers = {
url::Origin::Create(GURL("https://buyer1.com")),
url::Origin::Create(GURL("https://another-buyer.com"))};
auction_ad_config_non_shared_params_.auction_signals =
blink::AuctionConfig::MaybePromiseJson::FromValue(
R"({"is_auction_signals": true})");
auction_ad_config_non_shared_params_.seller_signals =
blink::AuctionConfig::MaybePromiseJson::FromValue(
R"({"is_seller_signals": true})");
auction_ad_config_non_shared_params_.seller_timeout = base::Milliseconds(200);
base::flat_map<url::Origin, std::string> per_buyer_signals;
per_buyer_signals[url::Origin::Create(GURL("https://a.com"))] =
R"({"signals_a": "A"})";
per_buyer_signals[url::Origin::Create(GURL("https://b.com"))] =
R"({"signals_b": "B"})";
auction_ad_config_non_shared_params_.per_buyer_signals =
blink::AuctionConfig::MaybePromisePerBuyerSignals::FromValue(
std::move(per_buyer_signals));
blink::AuctionConfig::BuyerTimeouts buyer_timeouts;
buyer_timeouts.per_buyer_timeouts.emplace();
buyer_timeouts.per_buyer_timeouts
.value()[url::Origin::Create(GURL("https://a.com"))] =
base::Milliseconds(100);
buyer_timeouts.all_buyers_timeout = base::Milliseconds(150);
auction_ad_config_non_shared_params_.buyer_timeouts =
blink::AuctionConfig::MaybePromiseBuyerTimeouts::FromValue(
std::move(buyer_timeouts));
blink::AuctionConfig::BuyerTimeouts buyer_cumulative_timeouts;
buyer_cumulative_timeouts.per_buyer_timeouts.emplace();
buyer_cumulative_timeouts.per_buyer_timeouts
.value()[url::Origin::Create(GURL("https://a.com"))] =
base::Milliseconds(101);
buyer_cumulative_timeouts.all_buyers_timeout = base::Milliseconds(151);
auction_ad_config_non_shared_params_.buyer_cumulative_timeouts =
blink::AuctionConfig::MaybePromiseBuyerTimeouts::FromValue(
std::move(buyer_cumulative_timeouts));
auction_ad_config_non_shared_params_.reporting_timeout =
base::Milliseconds(200);
std::vector<blink::AuctionConfig::AdKeywordReplacement> example_replacement =
{blink::AuctionConfig::AdKeywordReplacement({"${SELLER}", "ExampleSSP"})};
auction_ad_config_non_shared_params_.deprecated_render_url_replacements =
blink::AuctionConfig::MaybePromiseDeprecatedRenderURLReplacements::
FromValue(std::move(example_replacement));
blink::AuctionConfig::BuyerCurrencies buyer_currencies;
buyer_currencies.per_buyer_currencies.emplace();
buyer_currencies.per_buyer_currencies
.value()[url::Origin::Create(GURL("https://ca.test"))] =
blink::AdCurrency::From("CAD");
buyer_currencies.all_buyers_currency = blink::AdCurrency::From("USD");
auction_ad_config_non_shared_params_.buyer_currencies =
blink::AuctionConfig::MaybePromiseBuyerCurrencies::FromValue(
std::move(buyer_currencies));
auction_ad_config_non_shared_params_.per_buyer_priority_signals = {
{url::Origin::Create(GURL("https://a.com")), {{"signals_c", 0.5}}}};
auction_ad_config_non_shared_params_.all_buyers_priority_signals = {
{"signals_d", 0}};
const char kExpectedJson1[] =
R"({"seller":"https://example.test",
"decisionLogicURL":"https://example.test/auction.js",
"decisionLogicUrl":"https://example.test/auction.js",
"trustedScoringSignalsURL":"https://example.test/scoring_signals.json",
"trustedScoringSignalsUrl":"https://example.test/scoring_signals.json",
"interestGroupBuyers":["https://buyer1.com",
"https://another-buyer.com"],
"auctionSignals":{"is_auction_signals":true},
"sellerSignals":{"is_seller_signals":true},
"sellerTimeout":200,
"perBuyerSignals":{"https://a.com":{"signals_a":"A"},
"https://b.com":{"signals_b":"B"}},
"perBuyerCurrencies":{"*": "USD",
"https://ca.test": "CAD"},
"perBuyerTimeouts":{"https://a.com":100,"*":150},
"perBuyerCumulativeTimeouts":{"https://a.com":101,"*":151},
"reportingTimeout":200,
"perBuyerPrioritySignals":{"https://a.com":{"signals_c":0.5},
"*": {"signals_d":0}},
"deprecatedRenderURLReplacements": {"${SELLER}":"ExampleSSP"}
})";
RunReportResultCreatedScriptExpectingResult(
"auctionConfig", std::string(), kExpectedJson1,
std::nullopt);
auction_ad_config_non_shared_params_ =
blink::AuctionConfig::NonSharedParams();
auto& component_auctions =
auction_ad_config_non_shared_params_.component_auctions;
component_auctions.emplace_back(blink::AuctionConfig());
component_auctions[0].seller =
url::Origin::Create(GURL("https://component1.com"));
component_auctions[0].decision_logic_url =
GURL("https://component1.com/script.js");
component_auctions[0].non_shared_params.seller_timeout =
base::Milliseconds(111);
component_auctions.emplace_back(blink::AuctionConfig());
component_auctions[1].seller =
url::Origin::Create(GURL("https://component2.com"));
component_auctions[1].decision_logic_url =
GURL("https://component2.com/script.js");
component_auctions[1].trusted_scoring_signals_url =
GURL("https://component2.com/signals.json");
std::string default_reporting_timeout =
base::NumberToString(AuctionV8Helper::kScriptTimeout.InMilliseconds());
std::string kExpectedJson2 = base::StringPrintf(
R"({"seller":"https://example.test",
"decisionLogicURL":"https://example.test/auction.js",
"decisionLogicUrl":"https://example.test/auction.js",
"reportingTimeout":%s,
"trustedScoringSignalsURL":"https://example.test/scoring_signals.json",
"trustedScoringSignalsUrl":"https://example.test/scoring_signals.json",
"componentAuctions":[
{"seller":"https://component1.com",
"decisionLogicURL":"https://component1.com/script.js",
"decisionLogicUrl":"https://component1.com/script.js",
"reportingTimeout":%s,
"sellerTimeout":111},
{"seller":"https://component2.com",
"decisionLogicURL":"https://component2.com/script.js",
"decisionLogicUrl":"https://component2.com/script.js",
"reportingTimeout":%s,
"trustedScoringSignalsURL":"https://component2.com/signals.json",
"trustedScoringSignalsUrl":"https://component2.com/signals.json"}
]})",
default_reporting_timeout.c_str(), default_reporting_timeout.c_str(),
default_reporting_timeout.c_str());
RunReportResultCreatedScriptExpectingResult(
"auctionConfig", std::string(), kExpectedJson2,
std::nullopt);
}
TEST_F(SellerWorkletTest, ReportResultAuctionConfigUrlDeprecationWarning) {
ScopedInspectorSupport inspector_support(v8_helper().get());
decision_logic_url_ = GURL("https://url.test/");
trusted_scoring_signals_url_ =
GURL("https://url.test/trusted_scoring_signals");
auto& component_auctions =
auction_ad_config_non_shared_params_.component_auctions;
component_auctions.emplace_back(blink::AuctionConfig());
component_auctions[0].seller =
url::Origin::Create(GURL("https://component1.test"));
component_auctions[0].decision_logic_url =
GURL("https://component1.test/script.js");
component_auctions[0].trusted_scoring_signals_url =
GURL("https://component1.test/signals.js");
component_auctions.emplace_back(blink::AuctionConfig());
component_auctions[1].seller =
url::Origin::Create(GURL("https://component2.test"));
component_auctions[1].decision_logic_url =
GURL("https://component2.test/script.js");
component_auctions[1].trusted_scoring_signals_url =
GURL("https://component2.test/signals.js");
AddJavascriptResponse(&url_loader_factory_, decision_logic_url_,
CreateReportToScript(
R"(auctionConfig.decisionLogicUrl + '|' +
auctionConfig.trustedScoringSignalsUrl + '|' +
auctionConfig.componentAuctions[0].decisionLogicUrl + '|' +
auctionConfig.componentAuctions[0].trustedScoringSignalsUrl + '|' +
auctionConfig.componentAuctions[1].decisionLogicUrl + '|' +
auctionConfig.componentAuctions[1].trustedScoringSignalsUrl)"));
SellerWorklet* worklet_impl = nullptr;
auto worklet =
CreateWorklet(false, &worklet_impl);
int id = worklet_impl->context_group_ids_for_testing()[0];
TestChannel* channel =
inspector_support.ConnectDebuggerSessionAndRuntimeEnable(id);
base::RunLoop run_loop;
RunReportResultExpectingResultAsync(
worklet_impl,
"\"https://url.test/|"
"https://url.test/trusted_scoring_signals|"
"https://component1.test/script.js|"
"https://component1.test/signals.js|"
"https://component2.test/script.js|"
"https://component2.test/signals.js\"",
std::nullopt,
{}, {},
false, {},
run_loop.QuitClosure());
run_loop.Run();
channel->WaitForAndValidateConsoleMessage(
"warning",
"[{\"type\":\"string\", "
"\"value\":\"auctionConfig.decisionLogicUrl is deprecated. Please use "
"auctionConfig.decisionLogicURL instead.\"}]",
1, "reportResult", decision_logic_url_,
10);
channel->WaitForAndValidateConsoleMessage(
"warning",
"[{\"type\":\"string\", "
"\"value\":\"auctionConfig.trustedScoringSignalsUrl is deprecated. "
"Please use auctionConfig.trustedScoringSignalsURL instead.\"}]",
1, "reportResult", decision_logic_url_,
11);
channel->WaitForAndValidateConsoleMessage(
"warning",
"[{\"type\":\"string\", "
"\"value\":\"auctionConfig.decisionLogicUrl is deprecated. Please use "
"auctionConfig.decisionLogicURL instead.\"}]",
1, "reportResult", decision_logic_url_,
12);
channel->WaitForAndValidateConsoleMessage(
"warning",
"[{\"type\":\"string\", "
"\"value\":\"auctionConfig.trustedScoringSignalsUrl is deprecated. "
"Please use auctionConfig.trustedScoringSignalsURL instead.\"}]",
1, "reportResult", decision_logic_url_,
13);
channel->WaitForAndValidateConsoleMessage(
"warning",
"[{\"type\":\"string\", "
"\"value\":\"auctionConfig.decisionLogicUrl is deprecated. Please use "
"auctionConfig.decisionLogicURL instead.\"}]",
1, "reportResult", decision_logic_url_,
14);
channel->WaitForAndValidateConsoleMessage(
"warning",
"[{\"type\":\"string\", "
"\"value\":\"auctionConfig.trustedScoringSignalsUrl is deprecated. "
"Please use auctionConfig.trustedScoringSignalsURL instead.\"}]",
1, "reportResult", decision_logic_url_,
15);
channel->ExpectNoMoreConsoleEvents();
}
TEST_F(SellerWorkletTest, ReportResultAuctionConfigUrlNoDeprecationWarning) {
ScopedInspectorSupport inspector_support(v8_helper().get());
decision_logic_url_ = GURL("https://url.test/");
trusted_scoring_signals_url_ =
GURL("https://url.test/trusted_scoring_signals");
auto& component_auctions =
auction_ad_config_non_shared_params_.component_auctions;
component_auctions.emplace_back(blink::AuctionConfig());
component_auctions[0].seller =
url::Origin::Create(GURL("https://component1.test"));
component_auctions[0].decision_logic_url =
GURL("https://component1.test/script.js");
component_auctions[0].trusted_scoring_signals_url =
GURL("https://component1.test/signals.js");
component_auctions.emplace_back(blink::AuctionConfig());
component_auctions[1].seller =
url::Origin::Create(GURL("https://component2.test"));
component_auctions[1].decision_logic_url =
GURL("https://component2.test/script.js");
component_auctions[1].trusted_scoring_signals_url =
GURL("https://component2.test/signals.js");
AddJavascriptResponse(&url_loader_factory_, decision_logic_url_,
CreateReportToScript(
R"(auctionConfig.decisionLogicURL + '|' +
auctionConfig.trustedScoringSignalsURL + '|' +
auctionConfig.componentAuctions[0].decisionLogicURL + '|' +
auctionConfig.componentAuctions[0].trustedScoringSignalsURL + '|' +
auctionConfig.componentAuctions[1].decisionLogicURL + '|' +
auctionConfig.componentAuctions[1].trustedScoringSignalsURL)"));
SellerWorklet* worklet_impl = nullptr;
auto worklet =
CreateWorklet(false, &worklet_impl);
int id = worklet_impl->context_group_ids_for_testing()[0];
TestChannel* channel =
inspector_support.ConnectDebuggerSessionAndRuntimeEnable(id);
base::RunLoop run_loop;
RunReportResultExpectingResultAsync(
worklet_impl,
"\"https://url.test/|"
"https://url.test/trusted_scoring_signals|"
"https://component1.test/script.js|"
"https://component1.test/signals.js|"
"https://component2.test/script.js|"
"https://component2.test/signals.js\"",
std::nullopt,
{}, {},
false, {},
run_loop.QuitClosure());
run_loop.Run();
channel->ExpectNoMoreConsoleEvents();
}
TEST_F(SellerWorkletTest,
ReportResultDirectFromSellerSignalsHeaderAdSlotParam) {
direct_from_seller_auction_signals_header_ad_slot_ = R"("abcde")";
direct_from_seller_seller_signals_header_ad_slot_ = R"("abcdefg")";
const char kExpectedJson[] =
R"({"auctionSignals":"abcde", "sellerSignals":"abcdefg"})";
RunReportResultCreatedScriptExpectingResult(
"directFromSellerSignals", std::string(), kExpectedJson,
std::nullopt);
}
TEST_F(SellerWorkletTest, ReportResultAuctionConfigParamPerBuyerTimeouts) {
decision_logic_url_ = GURL("https://example.test/auction.js");
RunReportResultCreatedScriptExpectingResult(
"auctionConfig", std::string(),
base::StringPrintf(
R"({"seller":"https://example.test",)"
R"("decisionLogicURL":"https://example.test/auction.js",)"
R"("decisionLogicUrl":"https://example.test/auction.js",)"
R"("reportingTimeout": %s})",
base::NumberToString(AuctionV8Helper::kScriptTimeout.InMilliseconds())
.c_str()),
std::nullopt);
{
blink::AuctionConfig::BuyerTimeouts buyer_timeouts;
buyer_timeouts.per_buyer_timeouts.emplace();
auction_ad_config_non_shared_params_.buyer_timeouts =
blink::AuctionConfig::MaybePromiseBuyerTimeouts::FromValue(
std::move(buyer_timeouts));
RunReportResultCreatedScriptExpectingResult(
"auctionConfig", std::string(),
base::StringPrintf(
R"({"seller":"https://example.test",)"
R"("decisionLogicURL":"https://example.test/auction.js",)"
R"("decisionLogicUrl":"https://example.test/auction.js",)"
R"("perBuyerTimeouts":{},)"
R"("reportingTimeout": %s})",
base::NumberToString(
AuctionV8Helper::kScriptTimeout.InMilliseconds())
.c_str()),
std::nullopt);
}
{
blink::AuctionConfig::BuyerTimeouts buyer_timeouts;
buyer_timeouts.per_buyer_timeouts.emplace();
buyer_timeouts.all_buyers_timeout = base::Milliseconds(150);
auction_ad_config_non_shared_params_.buyer_timeouts =
blink::AuctionConfig::MaybePromiseBuyerTimeouts::FromValue(
std::move(buyer_timeouts));
RunReportResultCreatedScriptExpectingResult(
"auctionConfig", std::string(),
base::StringPrintf(
R"({"seller":"https://example.test",)"
R"("decisionLogicURL":"https://example.test/auction.js",)"
R"("decisionLogicUrl":"https://example.test/auction.js",)"
R"("perBuyerTimeouts":{"*":150},)"
R"("reportingTimeout": %s})",
base::NumberToString(
AuctionV8Helper::kScriptTimeout.InMilliseconds())
.c_str()),
std::nullopt);
}
}
TEST_F(SellerWorkletTest, ReportResultExperimentGroupIdParam) {
RunReportResultCreatedScriptExpectingResult(
R"("experimentGroupId" in auctionConfig ? 1 : 0)",
std::string(), "0",
std::nullopt);
experiment_group_id_ = 954u;
RunReportResultCreatedScriptExpectingResult(
"auctionConfig.experimentGroupId",
std::string(), "954",
std::nullopt);
}
TEST_F(SellerWorkletTest, ReportResultDataVersion) {
browser_signal_data_version_ = 20;
RunReportResultCreatedScriptExpectingResult(
"browserSignals.dataVersion", std::string(),
"20",
std::nullopt);
}
TEST_F(SellerWorkletTest, ReportResultJsFetchLatency) {
auto seller_worklet = CreateWorklet();
ASSERT_TRUE(seller_worklet);
base::RunLoop run_loop;
seller_worklet->ReportResult(
auction_ad_config_non_shared_params_, direct_from_seller_seller_signals_,
direct_from_seller_seller_signals_header_ad_slot_,
direct_from_seller_auction_signals_,
direct_from_seller_auction_signals_header_ad_slot_,
browser_signals_other_seller_.Clone(),
browser_signal_interest_group_owner_,
browser_signal_buyer_and_seller_reporting_id_,
browser_signal_selected_buyer_and_seller_reporting_id_,
browser_signal_render_url_, bid_, bid_currency_,
browser_signal_desireability_, browser_signal_highest_scoring_other_bid_,
browser_signal_highest_scoring_other_bid_currency_,
browser_signals_component_auction_report_result_params_.Clone(),
browser_signal_data_version_,
1,
base::BindOnce(
[](base::OnceClosure done_closure,
const std::optional<std::string>& signals_for_winner,
const std::optional<GURL>& report_url,
const base::flat_map<std::string, GURL>& ad_beacon_map,
PrivateAggregationRequests pa_requests,
auction_worklet::mojom::SellerTimingMetricsPtr timing_metrics,
const std::vector<std::string>& errors) {
ASSERT_TRUE(timing_metrics->js_fetch_latency.has_value());
EXPECT_EQ(base::Milliseconds(235),
*timing_metrics->js_fetch_latency);
std::move(done_closure).Run();
},
run_loop.QuitClosure()));
task_environment_.FastForwardBy(base::Milliseconds(235));
AddJavascriptResponse(&url_loader_factory_, decision_logic_url_,
CreateScoreAdScript("1", ""));
run_loop.Run();
}
TEST_F(SellerWorkletTest, ReportResultLoadCompletionOrder) {
constexpr char kJsonResponse[] = "{}";
constexpr char kDirectFromSellerSignalsHeaders[] =
"Ad-Auction-Allowed: true\nAd-Auction-Only: true";
direct_from_seller_seller_signals_ = GURL("https://url.test/sellersignals");
direct_from_seller_auction_signals_ = GURL("https://url.test/auctionsignals");
struct Response {
GURL response_url;
std::string response_type;
std::string headers;
std::string content;
};
const auto kResponses = std::to_array<Response>(
{{decision_logic_url_, kJavascriptMimeType, kAllowFledgeHeader,
CreateReportToScript(
"1",
R"(sendReportTo("https://foo.test"))")},
{*direct_from_seller_seller_signals_, kJsonMimeType,
kDirectFromSellerSignalsHeaders, kJsonResponse},
{*direct_from_seller_auction_signals_, kJsonMimeType,
kDirectFromSellerSignalsHeaders, kJsonResponse}});
for (size_t offset = 0; offset < std::size(kResponses); ++offset) {
SCOPED_TRACE(offset);
url_loader_factory_.ClearResponses();
mojo::Remote<mojom::SellerWorklet> seller_worklet = CreateWorklet();
auto run_loop = std::make_unique<base::RunLoop>();
RunReportResultExpectingResultAsync(
seller_worklet.get(), "1", GURL("https://foo.test/"),
{}, {},
false,
{}, run_loop->QuitClosure());
for (size_t i = 0; i < std::size(kResponses); ++i) {
SCOPED_TRACE(i);
const Response& response =
kResponses[(i + offset) % std::size(kResponses)];
AddResponse(&url_loader_factory_, response.response_url,
response.response_type,
std::nullopt, response.content, response.headers);
task_environment_.RunUntilIdle();
if (i < std::size(kResponses) - 1) {
EXPECT_FALSE(run_loop->AnyQuitCalled());
}
}
run_loop->Run();
}
}
TEST_P(SellerWorkletMultiThreadingTest, ScriptIsolation) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndDisableFeature(
features::kFledgeAlwaysReuseSellerContext);
AddJavascriptResponse(&url_loader_factory_, decision_logic_url_,
R"(
// Globally scoped variable.
if (!globalThis.var1)
globalThis.var1 = [1];
scoreAd = function() {
// Value only visible within this closure.
var var2 = [2];
return function() {
if (2 == ++globalThis.var1[0] && 3 == ++var2[0])
return 2;
return 1;
}
}();
reportResult = scoreAd;
)");
auto seller_worklet = CreateWorklet();
ASSERT_TRUE(seller_worklet);
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 2; ++j) {
RunScoreAdExpectingResultOnWorklet(seller_worklet.get(), 2);
}
for (int j = 0; j < 2; ++j) {
base::RunLoop run_loop;
seller_worklet->ReportResult(
auction_ad_config_non_shared_params_,
direct_from_seller_seller_signals_,
direct_from_seller_seller_signals_header_ad_slot_,
direct_from_seller_auction_signals_,
direct_from_seller_auction_signals_header_ad_slot_,
browser_signals_other_seller_.Clone(),
browser_signal_interest_group_owner_,
browser_signal_buyer_and_seller_reporting_id_,
browser_signal_selected_buyer_and_seller_reporting_id_,
browser_signal_render_url_, bid_, bid_currency_,
browser_signal_desireability_,
browser_signal_highest_scoring_other_bid_,
browser_signal_highest_scoring_other_bid_currency_,
browser_signals_component_auction_report_result_params_.Clone(),
browser_signal_data_version_,
1,
base::BindLambdaForTesting(
[&run_loop](
const std::optional<std::string>& signals_for_winner,
const std::optional<GURL>& report_url,
const base::flat_map<std::string, GURL>& ad_beacon_map,
PrivateAggregationRequests pa_requests,
auction_worklet::mojom::SellerTimingMetricsPtr timing_metrics,
const std::vector<std::string>& errors) {
EXPECT_EQ("2", signals_for_winner);
EXPECT_TRUE(errors.empty());
run_loop.Quit();
}));
run_loop.Run();
}
}
}
TEST_F(SellerWorkletTest,
ContextIsReusedIfFledgeAlwaysReuseSellerContextEnabled) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(
features::kFledgeAlwaysReuseSellerContext);
AddJavascriptResponse(&url_loader_factory_, decision_logic_url_,
R"(
// Globally scoped variable.
if (!globalThis.var1)
globalThis.var1 = [1];
scoreAd = function() {
// Value only visible within this closure.
var var2 = [2];
return function() {
if (2 == ++globalThis.var1[0] && 3 == ++var2[0])
return 2;
return 1;
}
}();
reportResult = scoreAd;
)");
auto seller_worklet = CreateWorklet();
ASSERT_TRUE(seller_worklet);
std::vector<double> expected_scores = {2, 1, 1};
for (int i = 0; i < 3; ++i) {
RunScoreAdExpectingResultOnWorklet(seller_worklet.get(),
expected_scores[i]);
}
base::RunLoop run_loop;
seller_worklet->ReportResult(
auction_ad_config_non_shared_params_, direct_from_seller_seller_signals_,
direct_from_seller_seller_signals_header_ad_slot_,
direct_from_seller_auction_signals_,
direct_from_seller_auction_signals_header_ad_slot_,
browser_signals_other_seller_.Clone(),
browser_signal_interest_group_owner_,
browser_signal_buyer_and_seller_reporting_id_,
browser_signal_selected_buyer_and_seller_reporting_id_,
browser_signal_render_url_, bid_, bid_currency_,
browser_signal_desireability_, browser_signal_highest_scoring_other_bid_,
browser_signal_highest_scoring_other_bid_currency_,
browser_signals_component_auction_report_result_params_.Clone(),
browser_signal_data_version_,
1,
base::BindLambdaForTesting(
[&run_loop](
const std::optional<std::string>& signals_for_winner,
const std::optional<GURL>& report_url,
const base::flat_map<std::string, GURL>& ad_beacon_map,
PrivateAggregationRequests pa_requests,
auction_worklet::mojom::SellerTimingMetricsPtr timing_metrics,
const std::vector<std::string>& errors) {
EXPECT_EQ("2", signals_for_winner);
EXPECT_TRUE(errors.empty());
run_loop.Quit();
}));
run_loop.Run();
}
TEST_F(SellerWorkletTest, ExecutionModeFrozenContext) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(
blink::features::kFledgeSellerScriptExecutionMode);
auction_ad_config_non_shared_params_.execution_mode =
blink::mojom::InterestGroup::ExecutionMode::kFrozenContext;
base::HistogramTester histogram_tester;
const char kScript[] = R"(
if (!('count' in globalThis)){
globalThis.count = 1;
}
scoreAd = function() {
++count;
return count;
}
reportResult = function() {
++globalThis.count;
return globalThis.count;
}
)";
AddJavascriptResponse(&url_loader_factory_, decision_logic_url_, kScript);
auto seller_worklet = CreateWorklet();
ASSERT_TRUE(seller_worklet);
std::vector<int> expected_scores_frozen = {1, 1, 1};
bool reused_context = false;
int reused_context_count = 0;
for (int i = 0; i < 3; ++i) {
RunScoreAdExpectingResultOnWorklet(seller_worklet.get(),
expected_scores_frozen[i]);
if (reused_context) {
histogram_tester.ExpectBucketCount(
"Ads.InterestGroup.Auction.SellerWorkletContextReused",
reused_context, reused_context_count);
} else {
histogram_tester.ExpectBucketCount(
"Ads.InterestGroup.Auction.SellerWorkletContextReused", 0, 1);
}
reused_context = true;
reused_context_count += 1;
}
base::RunLoop report_run_loop;
RunReportResultExpectingResultAsync(
seller_worklet.get(),
"1",
std::nullopt,
{}, {},
false, {},
report_run_loop.QuitClosure());
}
TEST_F(SellerWorkletTest, ExecutionModeFrozenContextFails) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(
blink::features::kFledgeSellerScriptExecutionMode);
auction_ad_config_non_shared_params_.execution_mode =
blink::mojom::InterestGroup::ExecutionMode::kFrozenContext;
const char kScript[] = R"(
const incrementer = (function() {
let a = 1;
return function() { a += 1; return a; };
})();
scoreAd = function() {
return incrementer();
}
)";
AddJavascriptResponse(&url_loader_factory_, decision_logic_url_, kScript);
RunScoreAdWithJavascriptExpectingResult(
kScript, 0,
{"undefined:0 Uncaught TypeError: Cannot "
"DeepFreeze non-const value a."},
mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt);
}
TEST_F(SellerWorkletTest, ExecutionModeCompatibility) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(
blink::features::kFledgeSellerScriptExecutionMode);
auction_ad_config_non_shared_params_.execution_mode =
blink::mojom::InterestGroup::ExecutionMode::kCompatibilityMode;
base::HistogramTester histogram_tester;
AddJavascriptResponse(&url_loader_factory_, decision_logic_url_,
R"(
scoreAd = function() {
if ('fresh' in globalThis) {
return 1; // Context was not fresh
}
globalThis.fresh = true;
return 2; // Context is fresh
};
reportResult = scoreAd;
)");
auto seller_worklet = CreateWorklet();
ASSERT_TRUE(seller_worklet);
std::vector<double> expected_scores = {2, 2, 2};
for (int i = 0; i < 3; ++i) {
RunScoreAdExpectingResultOnWorklet(seller_worklet.get(),
expected_scores[i]);
histogram_tester.ExpectBucketCount(
"Ads.InterestGroup.Auction.SellerWorkletContextReused", 0, i + 1);
}
base::RunLoop run_loop;
RunReportResultExpectingResultAsync(
seller_worklet.get(),
"1",
std::nullopt,
{}, {},
false, {},
run_loop.QuitClosure());
}
TEST_F(SellerWorkletTest, ExecutionModeGroupByOrigin) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(
blink::features::kFledgeSellerScriptExecutionMode);
allow_group_by_origin_mode_ = true;
auction_ad_config_non_shared_params_.execution_mode =
blink::mojom::InterestGroup::ExecutionMode::kGroupedByOriginMode;
base::HistogramTester histogram_tester;
AddJavascriptResponse(&url_loader_factory_, decision_logic_url_,
R"(
if (!globalThis.count) {
globalThis.count = 0;
}
scoreAd = function() {
globalThis.count++;
return globalThis.count;
};
reportResult = scoreAd;
)");
auto seller_worklet = CreateWorklet();
ASSERT_TRUE(seller_worklet);
std::vector<double> expected_scores = {1, 2, 3};
bool reused_context = false;
int reused_context_count = 0;
for (int i = 0; i < 3; ++i) {
RunScoreAdExpectingResultOnWorklet(seller_worklet.get(),
expected_scores[i]);
if (reused_context) {
histogram_tester.ExpectBucketCount(
"Ads.InterestGroup.Auction.SellerWorkletContextReused",
reused_context, reused_context_count);
} else {
histogram_tester.ExpectBucketCount(
"Ads.InterestGroup.Auction.SellerWorkletContextReused", 0, 1);
}
reused_context = true;
reused_context_count += 1;
}
base::RunLoop run_loop;
RunReportResultExpectingResultAsync(
seller_worklet.get(),
"1",
std::nullopt,
{}, {},
false, {},
run_loop.QuitClosure());
}
TEST_F(SellerWorkletTest, ExecutionModeGroupByOriginDifferentGroupByOriginIds) {
allow_group_by_origin_mode_ = true;
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(
blink::features::kFledgeSellerScriptExecutionMode);
auction_ad_config_non_shared_params_.execution_mode =
blink::mojom::InterestGroup::ExecutionMode::kGroupedByOriginMode;
const char kScript[] = R"(
globalThis.counter = globalThis.counter || 0;
scoreAd = function() {
globalThis.counter++;
return globalThis.counter;
};
reportResult = scoreAd;
)";
mojo::Remote<mojom::SellerWorklet> seller_worklet = CreateWorklet();
AddJavascriptResponse(&url_loader_factory_, decision_logic_url_, kScript);
auto run_score_ad = [&](double expected_score,
const uint64_t group_by_origin_id) {
group_by_origin_id_ = group_by_origin_id;
RunScoreAdExpectingResultOnWorklet(seller_worklet.get(), expected_score);
};
run_score_ad(1, 1);
run_score_ad(2, 1);
run_score_ad(1, 2);
run_score_ad(3, 1);
run_score_ad(2, 2);
run_score_ad(1, 3);
base::RunLoop run_loop;
RunReportResultExpectingResultAsync(
seller_worklet.get(),
"1",
std::nullopt,
{}, {},
false, {},
run_loop.QuitClosure());
}
TEST_F(SellerWorkletTest,
ExecutionModeGroupByOriginSaveMultipleInterestGroups) {
allow_group_by_origin_mode_ = true;
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitWithFeaturesAndParameters(
{{blink::features::kFledgeSellerScriptExecutionMode, {}},
{features::kFledgeNumberSellerWorkletGroupByOriginContextsToKeep,
{{"SellerGroupByOriginContextLimit", "2"}}}},
{});
auction_ad_config_non_shared_params_.execution_mode =
blink::mojom::InterestGroup::ExecutionMode::kGroupedByOriginMode;
const char kScript[] = R"(
globalThis.counter = globalThis.counter || 0;
scoreAd = function() {
globalThis.counter++;
return globalThis.counter;
};
reportResult = scoreAd;
)";
mojo::Remote<mojom::SellerWorklet> seller_worklet = CreateWorklet();
AddJavascriptResponse(&url_loader_factory_, decision_logic_url_, kScript);
auto run_score_ad = [&](double expected_score,
const uint64_t group_by_origin_id) {
group_by_origin_id_ = group_by_origin_id;
RunScoreAdExpectingResultOnWorklet(seller_worklet.get(), expected_score);
};
run_score_ad(1, 1);
run_score_ad(1, 2);
run_score_ad(1, 3);
run_score_ad(2, 2);
run_score_ad(2, 3);
run_score_ad(1, 1);
run_score_ad(3, 3);
run_score_ad(1, 2);
base::RunLoop run_loop;
RunReportResultExpectingResultAsync(
seller_worklet.get(),
"1",
std::nullopt,
{}, {},
false, {},
run_loop.QuitClosure());
}
TEST_F(SellerWorkletTwoThreadsTest, OneWorklet_ExecutionModeFrozenContext) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(
blink::features::kFledgeSellerScriptExecutionMode);
auction_ad_config_non_shared_params_.execution_mode =
blink::mojom::InterestGroup::ExecutionMode::kFrozenContext;
const char kScript[] = R"(
if (!('count' in globalThis))
globalThis.count = 1;
scoreAd = function() {
++count;
return count;
}
reportResult = function() {
return globalThis.count;
}
)";
AddJavascriptResponse(&url_loader_factory_, decision_logic_url_, kScript);
auto seller_worklet = CreateWorklet();
ASSERT_TRUE(seller_worklet);
for (int i = 0; i < 6; ++i) {
RunScoreAdExpectingResultOnWorklet(seller_worklet.get(),
1);
}
base::RunLoop run_loop;
RunReportResultExpectingResultAsync(
seller_worklet.get(),
"1",
std::nullopt,
{}, {},
false, {},
run_loop.QuitClosure());
}
TEST_F(SellerWorkletTwoThreadsTest, TwoWorklets_ExecutionModeFrozenContext) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(
blink::features::kFledgeSellerScriptExecutionMode);
auction_ad_config_non_shared_params_.execution_mode =
blink::mojom::InterestGroup::ExecutionMode::kFrozenContext;
const char kScript[] = R"(
if (!('count' in globalThis))
globalThis.count = 1;
scoreAd = function() {
++count;
return count;
}
reportResult = function() {
return globalThis.count;
}
)";
AddJavascriptResponse(&url_loader_factory_, decision_logic_url_, kScript);
auto seller_worklet1 = CreateWorklet();
auto seller_worklet2 = CreateWorklet();
ASSERT_TRUE(seller_worklet1);
ASSERT_TRUE(seller_worklet2);
for (int i = 0; i < 6; ++i) {
auto* seller_worklet = (i % 2 == 0) ? &seller_worklet1 : &seller_worklet2;
RunScoreAdExpectingResultOnWorklet(seller_worklet->get(),
1);
}
for (auto* seller_worklet : {&seller_worklet1, &seller_worklet2}) {
base::RunLoop run_loop;
RunReportResultExpectingResultAsync(
seller_worklet->get(),
"1",
std::nullopt,
{}, {},
false, {},
run_loop.QuitClosure());
}
}
TEST_F(SellerWorkletTwoThreadsTest, OneWorklet_ExecutionModeGroupByOrigin) {
base::test::ScopedFeatureList scoped_feature_list;
allow_group_by_origin_mode_ = true;
scoped_feature_list.InitAndEnableFeature(
blink::features::kFledgeSellerScriptExecutionMode);
auction_ad_config_non_shared_params_.execution_mode =
blink::mojom::InterestGroup::ExecutionMode::kGroupedByOriginMode;
AddJavascriptResponse(&url_loader_factory_, decision_logic_url_,
R"(
// Globally scoped variable.
if (!globalThis.var1)
globalThis.var1 = [1];
// Globally scoped variable for var2's state.
if (!globalThis.var2)
globalThis.var2 = [2];
scoreAd = function() {
if (2 == ++globalThis.var1[0] && 3 == ++globalThis.var2[0])
return 2;
return 1;
};
reportResult = scoreAd;
)");
auto seller_worklet = CreateWorklet();
ASSERT_TRUE(seller_worklet);
std::vector<double> expected_scores = {2, 2, 1, 1, 1, 1};
for (int i = 0; i < 6; ++i) {
RunScoreAdExpectingResultOnWorklet(seller_worklet.get(),
expected_scores[i]);
}
base::RunLoop run_loop;
RunReportResultExpectingResultAsync(
seller_worklet.get(),
"2",
std::nullopt,
{}, {},
false, {},
run_loop.QuitClosure());
}
TEST_F(SellerWorkletTwoThreadsTest, TwoWorklets_ExecutionModeGroupByOrigin) {
auction_ad_config_non_shared_params_.execution_mode =
blink::mojom::InterestGroup::ExecutionMode::kGroupedByOriginMode;
allow_group_by_origin_mode_ = true;
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(
blink::features::kFledgeSellerScriptExecutionMode);
AddJavascriptResponse(&url_loader_factory_, decision_logic_url_,
R"(
globalThis.var1 = globalThis.var1 || 1;
globalThis.var2 = globalThis.var2 || 2;
scoreAd = function() {
if (2 == ++globalThis.var1 && 3 == ++globalThis.var2)
return 2;
return 1;
};
reportResult = scoreAd;
)");
auto seller_worklet1 = CreateWorklet();
auto seller_worklet2 = CreateWorklet();
std::vector<double> expected_scores = {2, 2, 1, 1, 1, 1};
for (int i = 0; i < 6; ++i) {
auto* seller_worklet = (i % 2 == 0) ? &seller_worklet1 : &seller_worklet2;
RunScoreAdExpectingResultOnWorklet(seller_worklet->get(),
expected_scores[i]);
}
for (auto* seller_worklet : {&seller_worklet1, &seller_worklet2}) {
base::RunLoop run_loop;
RunReportResultExpectingResultAsync(
seller_worklet->get(),
"2",
std::nullopt,
{}, {},
false, {},
run_loop.QuitClosure());
}
}
TEST_F(
SellerWorkletTwoThreadsTest,
OneWorklet_ContextIsReusedInSameThreadIfFledgeAlwaysReuseSellerContextEnabled) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(
features::kFledgeAlwaysReuseSellerContext);
AddJavascriptResponse(&url_loader_factory_, decision_logic_url_,
R"(
globalThis.var1 = globalThis.var1 || 1;
globalThis.var2 = globalThis.var2 || 2;
scoreAd = function() {
if (2 == ++globalThis.var1 && 3 == ++globalThis.var2)
return 2;
return 1;
};
reportResult = scoreAd;
)");
auto seller_worklet = CreateWorklet();
ASSERT_TRUE(seller_worklet);
std::vector<double> expected_scores = {2, 2, 1, 1, 1, 1};
for (int i = 0; i < 6; ++i) {
RunScoreAdExpectingResultOnWorklet(seller_worklet.get(),
expected_scores[i]);
}
base::RunLoop run_loop;
seller_worklet->ReportResult(
auction_ad_config_non_shared_params_, direct_from_seller_seller_signals_,
direct_from_seller_seller_signals_header_ad_slot_,
direct_from_seller_auction_signals_,
direct_from_seller_auction_signals_header_ad_slot_,
browser_signals_other_seller_.Clone(),
browser_signal_interest_group_owner_,
browser_signal_buyer_and_seller_reporting_id_,
browser_signal_selected_buyer_and_seller_reporting_id_,
browser_signal_render_url_, bid_, bid_currency_,
browser_signal_desireability_, browser_signal_highest_scoring_other_bid_,
browser_signal_highest_scoring_other_bid_currency_,
browser_signals_component_auction_report_result_params_.Clone(),
browser_signal_data_version_,
1,
base::BindLambdaForTesting(
[&run_loop](
const std::optional<std::string>& signals_for_winner,
const std::optional<GURL>& report_url,
const base::flat_map<std::string, GURL>& ad_beacon_map,
PrivateAggregationRequests pa_requests,
auction_worklet::mojom::SellerTimingMetricsPtr timing_metrics,
const std::vector<std::string>& errors) {
EXPECT_EQ("2", signals_for_winner);
EXPECT_TRUE(errors.empty());
run_loop.Quit();
}));
run_loop.Run();
}
TEST_F(
SellerWorkletTwoThreadsTest,
TwoWorklets_ContextIsReusedInSameThreadIfFledgeAlwaysReuseSellerContextEnabled) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(
features::kFledgeAlwaysReuseSellerContext);
AddJavascriptResponse(&url_loader_factory_, decision_logic_url_,
R"(
globalThis.var1 = globalThis.var1 || 1;
globalThis.var2 = globalThis.var2 || 2;
scoreAd = function() {
if (2 == ++globalThis.var1 && 3 == ++globalThis.var2)
return 2;
return 1;
};
reportResult = scoreAd;
)");
auto seller_worklet1 = CreateWorklet();
auto seller_worklet2 = CreateWorklet();
std::vector<double> expected_scores = {2, 2, 1, 1, 1, 1};
for (int i = 0; i < 6; ++i) {
auto* seller_worklet = (i % 2 == 0) ? &seller_worklet1 : &seller_worklet2;
RunScoreAdExpectingResultOnWorklet(seller_worklet->get(),
expected_scores[i]);
}
for (auto* seller_worklet : {&seller_worklet1, &seller_worklet2}) {
base::RunLoop run_loop;
(*seller_worklet)
->ReportResult(
auction_ad_config_non_shared_params_,
direct_from_seller_seller_signals_,
direct_from_seller_seller_signals_header_ad_slot_,
direct_from_seller_auction_signals_,
direct_from_seller_auction_signals_header_ad_slot_,
browser_signals_other_seller_.Clone(),
browser_signal_interest_group_owner_,
browser_signal_buyer_and_seller_reporting_id_,
browser_signal_selected_buyer_and_seller_reporting_id_,
browser_signal_render_url_, bid_, bid_currency_,
browser_signal_desireability_,
browser_signal_highest_scoring_other_bid_,
browser_signal_highest_scoring_other_bid_currency_,
browser_signals_component_auction_report_result_params_.Clone(),
browser_signal_data_version_,
1,
base::BindLambdaForTesting(
[&run_loop](
const std::optional<std::string>& signals_for_winner,
const std::optional<GURL>& report_url,
const base::flat_map<std::string, GURL>& ad_beacon_map,
PrivateAggregationRequests pa_requests,
auction_worklet::mojom::SellerTimingMetricsPtr
timing_metrics,
const std::vector<std::string>& errors) {
EXPECT_EQ("2", signals_for_winner);
EXPECT_TRUE(errors.empty());
run_loop.Quit();
}));
run_loop.Run();
}
}
TEST_F(SellerWorkletTwoThreadsTest,
TrustedScoringSignalsTaskTriggersNextThreadIndexCallback) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(
features::kFledgeAlwaysReuseSellerContext);
AddJavascriptResponse(&url_loader_factory_, decision_logic_url_,
R"(
// Globally scoped variable.
if (!globalThis.var1)
globalThis.var1 = [1];
scoreAd = function() {
// Value only visible within this closure.
var var2 = [2];
return function() {
if (2 == ++globalThis.var1[0] && 3 == ++var2[0])
return 2;
return 1;
}
}();
reportResult = scoreAd;
)");
auto seller_worklet = CreateWorklet();
ASSERT_TRUE(seller_worklet);
std::vector<double> expected_scores = {2, 1, 2, 1, 1, 1};
for (int i = 0; i < 6; ++i) {
if (i == 1) {
trusted_scoring_signals_url_ = GURL("https://url.test/trustedsignals");
auto new_worklet = CreateWorklet();
}
RunScoreAdExpectingResultOnWorklet(seller_worklet.get(),
expected_scores[i]);
}
}
TEST_F(SellerWorkletTest, ContextReuseDoesNotCrashLazyFiller) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(
features::kFledgeAlwaysReuseSellerContext);
AddJavascriptResponse(&url_loader_factory_, decision_logic_url_,
R"(
scoreAd = function(adMetadata, bid, auctionConfig){
if (!globalThis.auctionConfig) {
globalThis.auctionConfig = auctionConfig;
} else {
// Access a lazily loaded attribute from a prior run
// of this function.
console.log(globalThis.auctionConfig.decisionLogicUrl);
}
return 1;
};
reportResult = scoreAd;
)");
auto seller_worklet = CreateWorklet();
ASSERT_TRUE(seller_worklet);
for (int i = 0; i < 3; ++i) {
double expected_score = 1;
base::RunLoop run_loop;
RunScoreAdExpectingResultOnWorklet(seller_worklet.get(), expected_score);
}
}
TEST_F(SellerWorkletTest, DeleteBeforeScoreAdCallback) {
AddJavascriptResponse(&url_loader_factory_, decision_logic_url_,
CreateBasicSellAdScript());
auto seller_worklet = CreateWorklet();
ASSERT_TRUE(seller_worklet);
base::WaitableEvent* event_handle = WedgeV8Thread(v8_helper().get());
seller_worklet->ScoreAd(
ad_metadata_, bid_, bid_currency_, auction_ad_config_non_shared_params_,
auction_worklet::mojom::TrustedSignalsCacheKeyPtr(), MainAd(),
ComponentAds(), direct_from_seller_seller_signals_,
direct_from_seller_seller_signals_header_ad_slot_,
direct_from_seller_auction_signals_,
direct_from_seller_auction_signals_header_ad_slot_,
browser_signals_other_seller_.Clone(), component_expect_bid_currency_,
browser_signal_interest_group_owner_,
browser_signal_selected_buyer_and_seller_reporting_id_,
browser_signal_buyer_and_seller_reporting_id_,
browser_signal_bidding_duration_msecs_,
browser_signal_for_debugging_only_in_cooldown_or_lockout_,
browser_signal_for_debugging_only_sampling_, seller_timeout_,
group_by_origin_id_, allow_group_by_origin_mode_,
1, bidder_joining_origin_,
TestScoreAdClient::Create(
TestScoreAdClient::ScoreAdNeverInvokedCallback()));
base::RunLoop().RunUntilIdle();
seller_worklet.reset();
event_handle->Signal();
}
TEST_F(SellerWorkletTest, DeleteBeforeReportResultCallback) {
AddJavascriptResponse(
&url_loader_factory_, decision_logic_url_,
CreateReportToScript("1", R"(sendReportTo("https://foo.test"))"));
auto seller_worklet = CreateWorklet();
ASSERT_TRUE(seller_worklet);
RunScoreAdExpectingResultOnWorklet(seller_worklet.get(), 1);
base::WaitableEvent* event_handle = WedgeV8Thread(v8_helper().get());
seller_worklet->ReportResult(
auction_ad_config_non_shared_params_, direct_from_seller_seller_signals_,
direct_from_seller_seller_signals_header_ad_slot_,
direct_from_seller_auction_signals_,
direct_from_seller_auction_signals_header_ad_slot_,
browser_signals_other_seller_.Clone(),
browser_signal_interest_group_owner_,
browser_signal_buyer_and_seller_reporting_id_,
browser_signal_selected_buyer_and_seller_reporting_id_,
browser_signal_render_url_, bid_, bid_currency_,
browser_signal_desireability_, browser_signal_highest_scoring_other_bid_,
browser_signal_highest_scoring_other_bid_currency_,
browser_signals_component_auction_report_result_params_.Clone(),
browser_signal_data_version_,
1,
base::BindOnce(
[](const std::optional<std::string>& signals_for_winner,
const std::optional<GURL>& report_url,
const base::flat_map<std::string, GURL>& ad_beacon_map,
PrivateAggregationRequests pa_requests,
auction_worklet::mojom::SellerTimingMetricsPtr timing_metrics,
const std::vector<std::string>& errors) {
ADD_FAILURE()
<< "Callback should not be invoked since worklet deleted";
}));
base::RunLoop().RunUntilIdle();
seller_worklet.reset();
event_handle->Signal();
}
TEST_F(SellerWorkletTest, PauseOnStart) {
AddJavascriptResponse(&url_loader_factory_, decision_logic_url_,
CreateScoreAdScript("10"));
SellerWorklet* worklet_impl = nullptr;
auto worklet =
CreateWorklet(true, &worklet_impl);
int id = worklet_impl->context_group_ids_for_testing()[0];
base::RunLoop run_loop;
RunScoreAdOnWorkletAsync(
worklet.get(), 10,
{}, mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
{},
{},
std::nullopt,
false,
std::nullopt,
std::nullopt, run_loop.QuitClosure());
task_environment_.RunUntilIdle();
EXPECT_FALSE(run_loop.AnyQuitCalled());
v8_helper()->v8_runner()->PostTask(
FROM_HERE, base::BindOnce([](scoped_refptr<AuctionV8Helper> v8_helper,
int id) { v8_helper->Resume(id); },
v8_helper(), id));
run_loop.RunUntilIdle();
}
TEST_P(SellerWorkletMultiThreadingTest, PauseOnStartDelete) {
AddJavascriptResponse(&url_loader_factory_, decision_logic_url_,
CreateScoreAdScript("10"));
SellerWorklet* worklet_impl = nullptr;
auto worklet =
CreateWorklet(true, &worklet_impl);
base::RunLoop run_loop;
RunScoreAdOnWorkletExpectingCallbackNeverInvoked(worklet.get());
task_environment_.RunUntilIdle();
int id = worklet_impl->context_group_ids_for_testing()[0];
worklet.reset();
task_environment_.RunUntilIdle();
v8_helper()->v8_runner()->PostTask(
FROM_HERE, base::BindOnce([](scoped_refptr<AuctionV8Helper> v8_helper,
int id) { v8_helper->Resume(id); },
v8_helper(), id));
task_environment_.RunUntilIdle();
}
TEST_F(SellerWorkletTest, BasicV8Debug) {
ScopedInspectorSupport inspector_support(v8_helper().get());
auto is_script_parsed = [](const TestChannel::Event& event) -> bool {
if (event.type != TestChannel::Event::Type::Notification) {
return false;
}
const std::string* candidate_method =
event.value.GetDict().FindString("method");
return (candidate_method && *candidate_method == "Debugger.scriptParsed");
};
const GURL kUrl1 = GURL("http://example.test/first.js");
const GURL kUrl2 = GURL("http://example2.test/second.js");
AddJavascriptResponse(&url_loader_factory_, kUrl1, CreateScoreAdScript("1"));
AddJavascriptResponse(&url_loader_factory_, kUrl2, CreateScoreAdScript("2"));
SellerWorklet* worklet_impl1 = nullptr;
decision_logic_url_ = kUrl1;
auto worklet1 = CreateWorklet(
true, &worklet_impl1);
base::RunLoop run_loop1;
RunScoreAdOnWorkletAsync(
worklet1.get(), 1,
{}, mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
{},
{},
std::nullopt,
false,
std::nullopt,
std::nullopt, run_loop1.QuitClosure());
decision_logic_url_ = kUrl2;
SellerWorklet* worklet_impl2 = nullptr;
auto worklet2 = CreateWorklet(
true, &worklet_impl2);
base::RunLoop run_loop2;
RunScoreAdOnWorkletAsync(
worklet2.get(), 2,
{}, mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
{},
{},
std::nullopt,
false,
std::nullopt,
std::nullopt, run_loop2.QuitClosure());
int id1 = worklet_impl1->context_group_ids_for_testing()[0];
int id2 = worklet_impl2->context_group_ids_for_testing()[0];
TestChannel* channel1 = inspector_support.ConnectDebuggerSession(id1);
TestChannel* channel2 = inspector_support.ConnectDebuggerSession(id2);
channel1->RunCommandAndWaitForResult(
1, "Runtime.enable", R"({"id":1,"method":"Runtime.enable","params":{}})");
channel1->RunCommandAndWaitForResult(
2, "Debugger.enable",
R"({"id":2,"method":"Debugger.enable","params":{}})");
channel2->RunCommandAndWaitForResult(
1, "Runtime.enable", R"({"id":1,"method":"Runtime.enable","params":{}})");
channel2->RunCommandAndWaitForResult(
2, "Debugger.enable",
R"({"id":2,"method":"Debugger.enable","params":{}})");
std::list<TestChannel::Event> events1 = channel1->TakeAllEvents();
EXPECT_TRUE(std::ranges::none_of(events1, is_script_parsed));
EXPECT_FALSE(run_loop1.AnyQuitCalled());
channel1->RunCommandAndWaitForResult(
3, "Runtime.runIfWaitingForDebugger",
R"({"id":3,"method":"Runtime.runIfWaitingForDebugger","params":{}})");
run_loop1.Run();
TestChannel::Event script_parsed1 =
channel1->WaitForMethodNotification("Debugger.scriptParsed");
const std::string* url1 =
script_parsed1.value.GetDict().FindStringByDottedPath("params.url");
ASSERT_TRUE(url1);
EXPECT_EQ(kUrl1.spec(), *url1);
std::list<TestChannel::Event> events2 = channel2->TakeAllEvents();
EXPECT_TRUE(std::ranges::none_of(events2, is_script_parsed));
EXPECT_FALSE(run_loop2.AnyQuitCalled());
channel2->RunCommandAndWaitForResult(
3, "Runtime.runIfWaitingForDebugger",
R"({"id":3,"method":"Runtime.runIfWaitingForDebugger","params":{}})");
run_loop2.Run();
TestChannel::Event script_parsed2 =
channel2->WaitForMethodNotification("Debugger.scriptParsed");
const std::string* url2 =
script_parsed2.value.GetDict().FindStringByDottedPath("params.url");
ASSERT_TRUE(url2);
EXPECT_EQ(kUrl2, *url2);
worklet1.reset();
worklet2.reset();
task_environment_.RunUntilIdle();
events1 = channel1->TakeAllEvents();
events2 = channel2->TakeAllEvents();
EXPECT_TRUE(std::ranges::none_of(events1, is_script_parsed));
EXPECT_TRUE(std::ranges::none_of(events2, is_script_parsed));
}
TEST_F(SellerWorkletTwoThreadsTest, BasicV8Debug) {
ScopedInspectorSupport inspector_support0(v8_helpers_[0].get());
ScopedInspectorSupport inspector_support1(v8_helpers_[1].get());
auto is_script_parsed = [](const TestChannel::Event& event) -> bool {
if (event.type != TestChannel::Event::Type::Notification) {
return false;
}
const std::string* candidate_method =
event.value.GetDict().FindString("method");
return (candidate_method && *candidate_method == "Debugger.scriptParsed");
};
const GURL kUrl1 = GURL("http://example.test/first.js");
AddJavascriptResponse(&url_loader_factory_, kUrl1, CreateScoreAdScript("1"));
SellerWorklet* worklet_impl = nullptr;
decision_logic_url_ = kUrl1;
auto worklet = CreateWorklet(
true, &worklet_impl);
base::RunLoop run_loop;
RunScoreAdOnWorkletAsync(
worklet.get(), 1,
{}, mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
{},
{},
std::nullopt,
false,
std::nullopt,
std::nullopt, run_loop.QuitClosure());
std::vector<int> ids = worklet_impl->context_group_ids_for_testing();
ASSERT_EQ(ids.size(), 2u);
TestChannel* channel0 = inspector_support0.ConnectDebuggerSession(ids[0]);
TestChannel* channel1 = inspector_support1.ConnectDebuggerSession(ids[1]);
channel0->RunCommandAndWaitForResult(
1, "Runtime.enable", R"({"id":1,"method":"Runtime.enable","params":{}})");
channel0->RunCommandAndWaitForResult(
2, "Debugger.enable",
R"({"id":2,"method":"Debugger.enable","params":{}})");
channel1->RunCommandAndWaitForResult(
1, "Runtime.enable", R"({"id":1,"method":"Runtime.enable","params":{}})");
channel1->RunCommandAndWaitForResult(
2, "Debugger.enable",
R"({"id":2,"method":"Debugger.enable","params":{}})");
std::list<TestChannel::Event> events0 = channel0->TakeAllEvents();
EXPECT_TRUE(std::ranges::none_of(events0, is_script_parsed));
std::list<TestChannel::Event> events1 = channel1->TakeAllEvents();
EXPECT_TRUE(std::ranges::none_of(events1, is_script_parsed));
EXPECT_FALSE(run_loop.AnyQuitCalled());
channel0->RunCommandAndWaitForResult(
3, "Runtime.runIfWaitingForDebugger",
R"({"id":3,"method":"Runtime.runIfWaitingForDebugger","params":{}})");
task_environment_.RunUntilIdle();
EXPECT_FALSE(run_loop.AnyQuitCalled());
channel1->RunCommandAndWaitForResult(
3, "Runtime.runIfWaitingForDebugger",
R"({"id":3,"method":"Runtime.runIfWaitingForDebugger","params":{}})");
task_environment_.RunUntilIdle();
EXPECT_TRUE(run_loop.AnyQuitCalled());
events1 = channel1->TakeAllEvents();
EXPECT_TRUE(std::ranges::none_of(events1, is_script_parsed));
TestChannel::Event script_parsed0 =
channel0->WaitForMethodNotification("Debugger.scriptParsed");
const std::string* url =
script_parsed0.value.GetDict().FindStringByDottedPath("params.url");
ASSERT_TRUE(url);
EXPECT_EQ(kUrl1.spec(), *url);
worklet.reset();
task_environment_.RunUntilIdle();
events0 = channel0->TakeAllEvents();
events1 = channel1->TakeAllEvents();
EXPECT_TRUE(std::ranges::none_of(events0, is_script_parsed));
EXPECT_TRUE(std::ranges::none_of(events1, is_script_parsed));
}
TEST_F(SellerWorkletTest, ParseErrorV8Debug) {
ScopedInspectorSupport inspector_support(v8_helper().get());
AddJavascriptResponse(&url_loader_factory_, decision_logic_url_,
"Invalid Javascript");
SellerWorklet* worklet_impl = nullptr;
auto worklet =
CreateWorklet(true, &worklet_impl);
int id = worklet_impl->context_group_ids_for_testing()[0];
TestChannel* channel = inspector_support.ConnectDebuggerSession(id);
channel->RunCommandAndWaitForResult(
1, "Runtime.enable", R"({"id":1,"method":"Runtime.enable","params":{}})");
channel->RunCommandAndWaitForResult(
2, "Debugger.enable",
R"({"id":2,"method":"Debugger.enable","params":{}})");
channel->RunCommandAndWaitForResult(
3, "Runtime.runIfWaitingForDebugger",
R"({"id":3,"method":"Runtime.runIfWaitingForDebugger","params":{}})");
EXPECT_FALSE(WaitForDisconnect().empty());
TestChannel::Event parse_error =
channel->WaitForMethodNotification("Debugger.scriptFailedToParse");
const std::string* error_url =
parse_error.value.GetDict().FindStringByDottedPath("params.url");
ASSERT_TRUE(error_url);
EXPECT_EQ(decision_logic_url_.spec(), *error_url);
}
TEST_F(SellerWorkletTwoThreadsTest, ParseErrorV8Debug) {
ScopedInspectorSupport inspector_support0(v8_helpers_[0].get());
ScopedInspectorSupport inspector_support1(v8_helpers_[1].get());
AddJavascriptResponse(&url_loader_factory_, decision_logic_url_,
"Invalid Javascript");
SellerWorklet* worklet_impl = nullptr;
auto worklet =
CreateWorklet(true, &worklet_impl);
std::vector<int> ids = worklet_impl->context_group_ids_for_testing();
EXPECT_EQ(ids.size(), 2u);
TestChannel* channel0 = inspector_support0.ConnectDebuggerSession(ids[0]);
TestChannel* channel1 = inspector_support1.ConnectDebuggerSession(ids[1]);
channel0->RunCommandAndWaitForResult(
1, "Runtime.enable", R"({"id":1,"method":"Runtime.enable","params":{}})");
channel0->RunCommandAndWaitForResult(
2, "Debugger.enable",
R"({"id":2,"method":"Debugger.enable","params":{}})");
channel1->RunCommandAndWaitForResult(
1, "Runtime.enable", R"({"id":1,"method":"Runtime.enable","params":{}})");
channel1->RunCommandAndWaitForResult(
2, "Debugger.enable",
R"({"id":2,"method":"Debugger.enable","params":{}})");
channel0->RunCommandAndWaitForResult(
3, "Runtime.runIfWaitingForDebugger",
R"({"id":3,"method":"Runtime.runIfWaitingForDebugger","params":{}})");
channel1->RunCommandAndWaitForResult(
3, "Runtime.runIfWaitingForDebugger",
R"({"id":3,"method":"Runtime.runIfWaitingForDebugger","params":{}})");
EXPECT_FALSE(WaitForDisconnect().empty());
TestChannel::Event parse_error0 =
channel0->WaitForMethodNotification("Debugger.scriptFailedToParse");
const std::string* error_url0 =
parse_error0.value.GetDict().FindStringByDottedPath("params.url");
ASSERT_TRUE(error_url0);
EXPECT_EQ(decision_logic_url_.spec(), *error_url0);
}
TEST_F(SellerWorkletTest, BasicDevToolsDebug) {
const char kScriptResult[] = "this.global_score ? this.global_score : 10";
const char kUrl1[] = "http://example.test/first.js";
const char kUrl2[] = "http://example2.test/second.js";
AddJavascriptResponse(&url_loader_factory_, GURL(kUrl1),
CreateScoreAdScript(kScriptResult));
AddJavascriptResponse(&url_loader_factory_, GURL(kUrl2),
CreateScoreAdScript(kScriptResult));
decision_logic_url_ = GURL(kUrl1);
auto worklet1 = CreateWorklet(true);
base::RunLoop run_loop1;
RunScoreAdOnWorkletAsync(
worklet1.get(), 100.5,
{}, mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
{},
{},
std::nullopt,
false,
std::nullopt,
std::nullopt, run_loop1.QuitClosure());
decision_logic_url_ = GURL(kUrl2);
auto worklet2 = CreateWorklet(true);
base::RunLoop run_loop2;
RunScoreAdOnWorkletAsync(
worklet2.get(), 0,
{"http://example2.test/second.js scoreAd() return: Value passed as "
"dictionary is neither object, null, nor undefined."},
mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
{},
{},
std::nullopt,
false,
std::nullopt,
std::nullopt, run_loop2.QuitClosure());
mojo::AssociatedRemote<blink::mojom::DevToolsAgent> agent1, agent2;
worklet1->ConnectDevToolsAgent(agent1.BindNewEndpointAndPassReceiver(),
0);
worklet2->ConnectDevToolsAgent(agent2.BindNewEndpointAndPassReceiver(),
0);
TestDevToolsAgentClient debug1(std::move(agent1), "123",
true);
TestDevToolsAgentClient debug2(std::move(agent2), "456",
true);
debug1.RunCommandAndWaitForResult(
TestDevToolsAgentClient::Channel::kMain, 1, "Runtime.enable",
R"({"id":1,"method":"Runtime.enable","params":{}})");
debug1.RunCommandAndWaitForResult(
TestDevToolsAgentClient::Channel::kMain, 2, "Debugger.enable",
R"({"id":2,"method":"Debugger.enable","params":{}})");
debug2.RunCommandAndWaitForResult(
TestDevToolsAgentClient::Channel::kMain, 1, "Runtime.enable",
R"({"id":1,"method":"Runtime.enable","params":{}})");
debug2.RunCommandAndWaitForResult(
TestDevToolsAgentClient::Channel::kMain, 2, "Debugger.enable",
R"({"id":2,"method":"Debugger.enable","params":{}})");
const char kBreakpointTemplate[] = R"({
"id":3,
"method":"Debugger.setBreakpointByUrl",
"params": {
"lineNumber": 2,
"url": "%s",
"columnNumber": 0,
"condition": ""
}})";
debug1.RunCommandAndWaitForResult(
TestDevToolsAgentClient::Channel::kMain, 3, "Debugger.setBreakpointByUrl",
base::StringPrintf(kBreakpointTemplate, kUrl1));
debug2.RunCommandAndWaitForResult(
TestDevToolsAgentClient::Channel::kMain, 3, "Debugger.setBreakpointByUrl",
base::StringPrintf(kBreakpointTemplate, kUrl2));
debug1.RunCommandAndWaitForResult(
TestDevToolsAgentClient::Channel::kMain, 4,
"Runtime.runIfWaitingForDebugger",
R"({"id":4,"method":"Runtime.runIfWaitingForDebugger","params":{}})");
TestDevToolsAgentClient::Event script_parsed1 =
debug1.WaitForMethodNotification("Debugger.scriptParsed");
const std::string* url1 =
script_parsed1.value.GetDict().FindStringByDottedPath("params.url");
ASSERT_TRUE(url1);
EXPECT_EQ(*url1, kUrl1);
TestDevToolsAgentClient::Event breakpoint_hit1 =
debug1.WaitForMethodNotification("Debugger.paused");
base::Value::List* hit_breakpoints =
breakpoint_hit1.value.GetDict().FindDict("params")->FindList(
"hitBreakpoints");
ASSERT_TRUE(hit_breakpoints);
ASSERT_EQ(1u, hit_breakpoints->size());
ASSERT_TRUE((*hit_breakpoints)[0].is_string());
EXPECT_EQ("1:2:0:http://example.test/first.js",
(*hit_breakpoints)[0].GetString());
std::string* callframe_id1 = breakpoint_hit1.value.GetDict()
.FindDict("params")
->FindList("callFrames")
->front()
.GetDict()
.FindString("callFrameId");
const char kCommandTemplate[] = R"({
"id": 5,
"method": "Debugger.evaluateOnCallFrame",
"params": {
"callFrameId": "%s",
"expression": "global_score = %s"
}
})";
debug1.RunCommandAndWaitForResult(
TestDevToolsAgentClient::Channel::kIO, 5, "Debugger.evaluateOnCallFrame",
base::StringPrintf(kCommandTemplate, callframe_id1->c_str(), "100.5"));
EXPECT_FALSE(run_loop1.AnyQuitCalled());
debug1.RunCommandAndWaitForResult(
TestDevToolsAgentClient::Channel::kIO, 6, "Debugger.resume",
R"({"id":6,"method":"Debugger.resume","params":{}})");
run_loop1.Run();
debug2.RunCommandAndWaitForResult(
TestDevToolsAgentClient::Channel::kMain, 4,
"Runtime.runIfWaitingForDebugger",
R"({"id":4,"method":"Runtime.runIfWaitingForDebugger","params":{}})");
TestDevToolsAgentClient::Event script_parsed2 =
debug2.WaitForMethodNotification("Debugger.scriptParsed");
const std::string* url2 =
script_parsed2.value.GetDict().FindStringByDottedPath("params.url");
ASSERT_TRUE(url2);
EXPECT_EQ(*url2, kUrl2);
TestDevToolsAgentClient::Event breakpoint_hit2 =
debug2.WaitForMethodNotification("Debugger.paused");
std::string* callframe_id2 = breakpoint_hit2.value.GetDict()
.FindDict("params")
->FindList("callFrames")
->front()
.GetDict()
.FindString("callFrameId");
debug2.RunCommandAndWaitForResult(
TestDevToolsAgentClient::Channel::kIO, 5, "Debugger.evaluateOnCallFrame",
base::StringPrintf(kCommandTemplate, callframe_id2->c_str(),
R"(\"not a score\")"));
debug2.RunCommandAndWaitForResult(
TestDevToolsAgentClient::Channel::kIO, 6, "Debugger.resume",
R"({"id":6,"method":"Debugger.resume","params":{}})");
run_loop2.Run();
}
TEST_F(SellerWorkletTwoThreadsTest, BasicDevToolsDebug) {
const char kScriptResult[] = "this.global_score ? this.global_score : 10";
const char kUrl1[] = "http://example.test/first.js";
AddJavascriptResponse(&url_loader_factory_, GURL(kUrl1),
CreateScoreAdScript(kScriptResult));
decision_logic_url_ = GURL(kUrl1);
auto worklet = CreateWorklet(true);
base::RunLoop run_loop0;
RunScoreAdOnWorkletAsync(
worklet.get(), 100.5,
{}, mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
{},
{},
std::nullopt,
false,
std::nullopt,
std::nullopt, run_loop0.QuitClosure());
base::RunLoop run_loop1;
RunScoreAdOnWorkletAsync(
worklet.get(), 100.6,
{}, mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
{},
{},
std::nullopt,
false,
std::nullopt,
std::nullopt, run_loop1.QuitClosure());
mojo::AssociatedRemote<blink::mojom::DevToolsAgent> agent0, agent1;
worklet->ConnectDevToolsAgent(agent0.BindNewEndpointAndPassReceiver(),
0);
worklet->ConnectDevToolsAgent(agent1.BindNewEndpointAndPassReceiver(),
1);
TestDevToolsAgentClient debug0(std::move(agent0), "123",
true);
TestDevToolsAgentClient debug1(std::move(agent1), "456",
true);
debug0.RunCommandAndWaitForResult(
TestDevToolsAgentClient::Channel::kMain, 1, "Runtime.enable",
R"({"id":1,"method":"Runtime.enable","params":{}})");
debug0.RunCommandAndWaitForResult(
TestDevToolsAgentClient::Channel::kMain, 2, "Debugger.enable",
R"({"id":2,"method":"Debugger.enable","params":{}})");
debug1.RunCommandAndWaitForResult(
TestDevToolsAgentClient::Channel::kMain, 1, "Runtime.enable",
R"({"id":1,"method":"Runtime.enable","params":{}})");
debug1.RunCommandAndWaitForResult(
TestDevToolsAgentClient::Channel::kMain, 2, "Debugger.enable",
R"({"id":2,"method":"Debugger.enable","params":{}})");
const char kBreakpointTemplate[] = R"({
"id":3,
"method":"Debugger.setBreakpointByUrl",
"params": {
"lineNumber": 2,
"url": "%s",
"columnNumber": 0,
"condition": ""
}})";
debug0.RunCommandAndWaitForResult(
TestDevToolsAgentClient::Channel::kMain, 3, "Debugger.setBreakpointByUrl",
base::StringPrintf(kBreakpointTemplate, kUrl1));
debug1.RunCommandAndWaitForResult(
TestDevToolsAgentClient::Channel::kMain, 3, "Debugger.setBreakpointByUrl",
base::StringPrintf(kBreakpointTemplate, kUrl1));
debug0.RunCommandAndWaitForResult(
TestDevToolsAgentClient::Channel::kMain, 4,
"Runtime.runIfWaitingForDebugger",
R"({"id":4,"method":"Runtime.runIfWaitingForDebugger","params":{}})");
debug1.RunCommandAndWaitForResult(
TestDevToolsAgentClient::Channel::kMain, 4,
"Runtime.runIfWaitingForDebugger",
R"({"id":4,"method":"Runtime.runIfWaitingForDebugger","params":{}})");
TestDevToolsAgentClient::Event script_parsed0 =
debug0.WaitForMethodNotification("Debugger.scriptParsed");
const std::string* url0 =
script_parsed0.value.GetDict().FindStringByDottedPath("params.url");
ASSERT_TRUE(url0);
EXPECT_EQ(*url0, kUrl1);
TestDevToolsAgentClient::Event script_parsed1 =
debug1.WaitForMethodNotification("Debugger.scriptParsed");
const std::string* url1 =
script_parsed1.value.GetDict().FindStringByDottedPath("params.url");
ASSERT_TRUE(url1);
EXPECT_EQ(*url1, kUrl1);
TestDevToolsAgentClient::Event breakpoint_hit0 =
debug0.WaitForMethodNotification("Debugger.paused");
TestDevToolsAgentClient::Event breakpoint_hit1 =
debug1.WaitForMethodNotification("Debugger.paused");
base::Value::List* hit_breakpoints0 =
breakpoint_hit0.value.GetDict().FindDict("params")->FindList(
"hitBreakpoints");
ASSERT_TRUE(hit_breakpoints0);
ASSERT_EQ(1u, hit_breakpoints0->size());
ASSERT_TRUE((*hit_breakpoints0)[0].is_string());
EXPECT_EQ("1:2:0:http://example.test/first.js",
(*hit_breakpoints0)[0].GetString());
std::string* callframe_id0 = breakpoint_hit0.value.GetDict()
.FindDict("params")
->FindList("callFrames")
->front()
.GetDict()
.FindString("callFrameId");
base::Value::List* hit_breakpoints1 =
breakpoint_hit1.value.GetDict().FindDict("params")->FindList(
"hitBreakpoints");
ASSERT_TRUE(hit_breakpoints1);
ASSERT_EQ(1u, hit_breakpoints1->size());
ASSERT_TRUE((*hit_breakpoints1)[0].is_string());
EXPECT_EQ("1:2:0:http://example.test/first.js",
(*hit_breakpoints1)[0].GetString());
std::string* callframe_id1 = breakpoint_hit1.value.GetDict()
.FindDict("params")
->FindList("callFrames")
->front()
.GetDict()
.FindString("callFrameId");
const char kCommandTemplate[] = R"({
"id": 5,
"method": "Debugger.evaluateOnCallFrame",
"params": {
"callFrameId": "%s",
"expression": "global_score = %s"
}
})";
debug0.RunCommandAndWaitForResult(
TestDevToolsAgentClient::Channel::kIO, 5, "Debugger.evaluateOnCallFrame",
base::StringPrintf(kCommandTemplate, callframe_id0->c_str(), "100.5"));
debug1.RunCommandAndWaitForResult(
TestDevToolsAgentClient::Channel::kIO, 5, "Debugger.evaluateOnCallFrame",
base::StringPrintf(kCommandTemplate, callframe_id1->c_str(), "100.6"));
EXPECT_FALSE(run_loop0.AnyQuitCalled());
debug0.RunCommandAndWaitForResult(
TestDevToolsAgentClient::Channel::kIO, 6, "Debugger.resume",
R"({"id":6,"method":"Debugger.resume","params":{}})");
run_loop0.Run();
EXPECT_FALSE(run_loop1.AnyQuitCalled());
debug1.RunCommandAndWaitForResult(
TestDevToolsAgentClient::Channel::kIO, 6, "Debugger.resume",
R"({"id":6,"method":"Debugger.resume","params":{}})");
run_loop1.Run();
}
TEST_F(SellerWorkletTest, InstrumentationBreakpoints) {
const char kUrl[] = "http://example.test/script.js";
std::string script_body =
CreateBasicSellAdScript() +
CreateReportToScript("1", R"(sendReportTo("https://foo.test"))");
AddJavascriptResponse(&url_loader_factory_, GURL(kUrl), script_body);
decision_logic_url_ = GURL(kUrl);
auto worklet = CreateWorklet(true);
base::RunLoop run_loop;
RunScoreAdOnWorkletAsync(
worklet.get(), 1.0,
{}, mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
{},
{},
std::nullopt,
false,
std::nullopt,
std::nullopt, run_loop.QuitClosure());
mojo::AssociatedRemote<blink::mojom::DevToolsAgent> agent;
worklet->ConnectDevToolsAgent(agent.BindNewEndpointAndPassReceiver(),
0);
TestDevToolsAgentClient debug(std::move(agent), "123",
true);
debug.RunCommandAndWaitForResult(
TestDevToolsAgentClient::Channel::kMain, 1, "Runtime.enable",
R"({"id":1,"method":"Runtime.enable","params":{}})");
debug.RunCommandAndWaitForResult(
TestDevToolsAgentClient::Channel::kMain, 2, "Debugger.enable",
R"({"id":2,"method":"Debugger.enable","params":{}})");
debug.RunCommandAndWaitForResult(
TestDevToolsAgentClient::Channel::kMain, 3,
"EventBreakpoints.setInstrumentationBreakpoint",
MakeInstrumentationBreakpointCommand(3, "set",
"beforeSellerWorkletScoringStart"));
debug.RunCommandAndWaitForResult(
TestDevToolsAgentClient::Channel::kMain, 4,
"EventBreakpoints.setInstrumentationBreakpoint",
MakeInstrumentationBreakpointCommand(
4, "set", "beforeSellerWorkletReportingStart"));
debug.RunCommandAndWaitForResult(
TestDevToolsAgentClient::Channel::kMain, 5,
"Runtime.runIfWaitingForDebugger",
R"({"id":5,"method":"Runtime.runIfWaitingForDebugger","params":{}})");
TestDevToolsAgentClient::Event breakpoint_hit1 =
debug.WaitForMethodNotification("Debugger.paused");
const std::string* breakpoint1 =
breakpoint_hit1.value.GetDict().FindStringByDottedPath(
"params.data.eventName");
ASSERT_TRUE(breakpoint1);
EXPECT_EQ("instrumentation:beforeSellerWorkletScoringStart", *breakpoint1);
EXPECT_FALSE(run_loop.AnyQuitCalled());
debug.RunCommandAndWaitForResult(
TestDevToolsAgentClient::Channel::kIO, 6, "Debugger.resume",
R"({"id":6,"method":"Debugger.resume","params":{}})");
run_loop.Run();
base::RunLoop run_loop2;
RunReportResultExpectingResultAsync(
worklet.get(), "1", GURL("https://foo.test/"),
{}, {},
false,
{}, run_loop2.QuitClosure());
TestDevToolsAgentClient::Event breakpoint_hit2 =
debug.WaitForMethodNotification("Debugger.paused");
const std::string* breakpoint2 =
breakpoint_hit2.value.GetDict().FindStringByDottedPath(
"params.data.eventName");
ASSERT_TRUE(breakpoint2);
EXPECT_EQ("instrumentation:beforeSellerWorkletReportingStart", *breakpoint2);
debug.RunCommandAndWaitForResult(
TestDevToolsAgentClient::Channel::kIO, 7, "Debugger.resume",
R"({"id":7,"method":"Debugger.resume","params":{}})");
run_loop2.Run();
base::RunLoop run_loop3;
RunScoreAdOnWorkletAsync(
worklet.get(), 1.0,
{}, mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
{},
{},
std::nullopt,
false,
std::nullopt,
std::nullopt, run_loop3.QuitClosure());
TestDevToolsAgentClient::Event breakpoint_hit3 =
debug.WaitForMethodNotification("Debugger.paused");
const std::string* breakpoint3 =
breakpoint_hit1.value.GetDict().FindStringByDottedPath(
"params.data.eventName");
ASSERT_TRUE(breakpoint3);
EXPECT_EQ("instrumentation:beforeSellerWorkletScoringStart", *breakpoint3);
debug.RunCommandAndWaitForResult(
TestDevToolsAgentClient::Channel::kIO, 8, "Debugger.resume",
R"({"id":8,"method":"Debugger.resume","params":{}})");
run_loop3.Run();
}
TEST_F(SellerWorkletTest, UnloadWhilePaused) {
const char kUrl[] = "http://example.test/script.js";
std::string script_body =
CreateBasicSellAdScript() +
CreateReportToScript("1", R"(sendReportTo("https://foo.test"))");
AddJavascriptResponse(&url_loader_factory_, GURL(kUrl), script_body);
decision_logic_url_ = GURL(kUrl);
auto worklet = CreateWorklet(true);
RunScoreAdOnWorkletExpectingCallbackNeverInvoked(worklet.get());
mojo::AssociatedRemote<blink::mojom::DevToolsAgent> agent;
worklet->ConnectDevToolsAgent(agent.BindNewEndpointAndPassReceiver(),
0);
TestDevToolsAgentClient debug(std::move(agent), "123",
true);
debug.RunCommandAndWaitForResult(
TestDevToolsAgentClient::Channel::kMain, 1, "Runtime.enable",
R"({"id":1,"method":"Runtime.enable","params":{}})");
debug.RunCommandAndWaitForResult(
TestDevToolsAgentClient::Channel::kMain, 2, "Debugger.enable",
R"({"id":2,"method":"Debugger.enable","params":{}})");
debug.RunCommandAndWaitForResult(
TestDevToolsAgentClient::Channel::kMain, 3,
"EventBreakpoints.setInstrumentationBreakpoint",
MakeInstrumentationBreakpointCommand(3, "set",
"beforeSellerWorkletScoringStart"));
debug.RunCommandAndWaitForResult(
TestDevToolsAgentClient::Channel::kMain, 4,
"Runtime.runIfWaitingForDebugger",
R"({"id":4,"method":"Runtime.runIfWaitingForDebugger","params":{}})");
RunScoreAdOnWorkletAsync(
worklet.get(), 1.0, {},
mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
{},
{},
std::nullopt,
false,
std::nullopt,
std::nullopt, base::BindOnce([]() {
ADD_FAILURE() << "scoreAd shouldn't actually get to finish.";
}));
debug.WaitForMethodNotification("Debugger.paused");
worklet.reset();
task_environment_.RunUntilIdle();
}
TEST_F(SellerWorkletTest, Cancelation) {
seller_timeout_ = base::Days(360);
AddJavascriptResponse(&url_loader_factory_, decision_logic_url_,
"while(true) {}");
mojo::Remote<mojom::SellerWorklet> seller_worklet = CreateWorklet();
task_environment_.RunUntilIdle();
base::WaitableEvent* event_handle = WedgeV8Thread(v8_helper().get());
TestScoreAdClient client(TestScoreAdClient::ScoreAdNeverInvokedCallback());
mojo::Receiver<mojom::ScoreAdClient> client_receiver(&client);
seller_worklet->ScoreAd(
ad_metadata_, bid_, bid_currency_, auction_ad_config_non_shared_params_,
auction_worklet::mojom::TrustedSignalsCacheKeyPtr(), MainAd(),
ComponentAds(), direct_from_seller_seller_signals_,
direct_from_seller_seller_signals_header_ad_slot_,
direct_from_seller_auction_signals_,
direct_from_seller_auction_signals_header_ad_slot_,
browser_signals_other_seller_.Clone(), component_expect_bid_currency_,
browser_signal_interest_group_owner_,
browser_signal_selected_buyer_and_seller_reporting_id_,
browser_signal_buyer_and_seller_reporting_id_,
browser_signal_bidding_duration_msecs_,
browser_signal_for_debugging_only_in_cooldown_or_lockout_,
browser_signal_for_debugging_only_sampling_, seller_timeout_,
group_by_origin_id_, allow_group_by_origin_mode_,
1, bidder_joining_origin_,
client_receiver.BindNewPipeAndPassRemote());
client_receiver.reset();
base::RunLoop().RunUntilIdle();
event_handle->Signal();
task_environment_.RunUntilIdle();
}
TEST_F(SellerWorkletTest, CancelationDtor) {
seller_timeout_ = base::Days(360);
v8_helper()->v8_runner()->PostTask(
FROM_HERE,
base::BindOnce(
[](scoped_refptr<AuctionV8Helper> v8_helper) {
v8_helper->set_script_timeout_for_testing(base::Days(360));
},
v8_helper()));
AddJavascriptResponse(&url_loader_factory_, decision_logic_url_,
"while(true) {}");
mojo::Remote<mojom::SellerWorklet> seller_worklet = CreateWorklet();
task_environment_.RunUntilIdle();
base::WaitableEvent* event_handle = WedgeV8Thread(v8_helper().get());
RunScoreAdOnWorkletExpectingCallbackNeverInvoked(seller_worklet.get());
RunReportResultExpectingCallbackNeverInvoked(seller_worklet.get());
seller_worklet.reset();
base::RunLoop().RunUntilIdle();
event_handle->Signal();
}
TEST_F(SellerWorkletTest, CancelBeforeFetch) {
seller_timeout_ = base::Days(360);
mojo::Remote<mojom::SellerWorklet> seller_worklet = CreateWorklet();
TestScoreAdClient client(TestScoreAdClient::ScoreAdNeverInvokedCallback());
mojo::Receiver<mojom::ScoreAdClient> client_receiver(&client);
seller_worklet->ScoreAd(
ad_metadata_, bid_, bid_currency_, auction_ad_config_non_shared_params_,
auction_worklet::mojom::TrustedSignalsCacheKeyPtr(), MainAd(),
ComponentAds(), direct_from_seller_seller_signals_,
direct_from_seller_seller_signals_header_ad_slot_,
direct_from_seller_auction_signals_,
direct_from_seller_auction_signals_header_ad_slot_,
browser_signals_other_seller_.Clone(), component_expect_bid_currency_,
browser_signal_interest_group_owner_,
browser_signal_selected_buyer_and_seller_reporting_id_,
browser_signal_buyer_and_seller_reporting_id_,
browser_signal_bidding_duration_msecs_,
browser_signal_for_debugging_only_in_cooldown_or_lockout_,
browser_signal_for_debugging_only_sampling_, seller_timeout_,
group_by_origin_id_, allow_group_by_origin_mode_,
1, bidder_joining_origin_,
client_receiver.BindNewPipeAndPassRemote());
task_environment_.RunUntilIdle();
client_receiver.reset();
AddJavascriptResponse(&url_loader_factory_, decision_logic_url_,
"while (true) {}");
task_environment_.RunUntilIdle();
}
TEST_F(SellerWorkletTest, AuctionRequestedSizeIsPresentInScoreAdJavascript) {
auction_ad_config_non_shared_params_.requested_size = blink::AdSize(
1920,
blink::mojom::AdSize_LengthUnit::kPixels,
100,
blink::mojom::AdSize_LengthUnit::kScreenHeight);
std::string requested_size_validator =
R"(if (!(auctionConfig.requestedSize.width === '1920px' &&
auctionConfig.requestedSize.height === '100sh')) {
throw new Error('Requested size is incorrect or missing.');
})";
RunScoreAdWithJavascriptExpectingResult(
CreateScoreAdScript("1", requested_size_validator), 1,
{}, mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt);
}
TEST_F(SellerWorkletTest,
AuctionRequestedSizeIsMissingFromScoreAdJavascriptWhenNotProvided) {
std::string requested_size_validator =
R"(if (auctionConfig.hasOwnProperty('requestedSize')) {
throw new Error('Requested size is present but should be missing.');
})";
RunScoreAdWithJavascriptExpectingResult(
CreateScoreAdScript("1", requested_size_validator), 1,
{}, mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt);
}
TEST_F(SellerWorkletTest, AuctionRequestedSizeIsPresentReportResultJavascript) {
auction_ad_config_non_shared_params_.requested_size = blink::AdSize(
1920,
blink::mojom::AdSize_LengthUnit::kPixels,
100,
blink::mojom::AdSize_LengthUnit::kScreenHeight);
std::string requested_size_validator =
R"(if (!(auctionConfig.requestedSize.width === '1920px' &&
auctionConfig.requestedSize.height === '100sh')) {
throw new Error('Requested size is incorrect or missing.');
})";
RunReportResultCreatedScriptExpectingResult(
"1", requested_size_validator,
"1",
std::nullopt);
}
TEST_F(SellerWorkletTest,
AuctionRequestedSizeIsMissingFromReportResultJavascriptWhenNotProvided) {
std::string requested_size_validator =
R"(if (auctionConfig.hasOwnProperty('requestedSize')) {
throw new Error('Requested size is present but should be missing.');
})";
RunReportResultCreatedScriptExpectingResult(
"1", requested_size_validator,
"1",
std::nullopt);
}
TEST_F(SellerWorkletTest,
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);
}
TEST_F(SellerWorkletTest, ScoreAdBrowserSignalForDebuggingOnlySampling) {
RunScoreAdWithReturnValueExpectingResult(
R"(browserSignals.hasOwnProperty('forDebuggingOnlySampling') ?
3 : 0)",
0);
}
class ScoreAdBrowserSignalRenderSizeTest
: public base::test::WithFeatureOverride,
public SellerWorkletTest {
public:
ScoreAdBrowserSignalRenderSizeTest()
: base::test::WithFeatureOverride(
blink::features::kRenderSizeInScoreAdBrowserSignals) {}
};
TEST_P(ScoreAdBrowserSignalRenderSizeTest, ScoreAdBrowserSignalRenderSize) {
RunScoreAdWithReturnValueExpectingResult(
R"(browserSignals.hasOwnProperty('renderSize') ? 3 : 0)", 0);
browser_signal_render_size_ =
blink::AdSize(100, blink::AdSize::LengthUnit::kScreenWidth, 50,
blink::AdSize::LengthUnit::kPixels);
RunScoreAdWithReturnValueExpectingResult(
R"((browserSignals.hasOwnProperty('renderSize') &&
browserSignals.renderSize.width === '100sw' &&
browserSignals.renderSize.height === '50px') ? 3 : 0)",
IsParamFeatureEnabled() ? 3 : 0);
}
INSTANTIATE_FEATURE_OVERRIDE_TEST_SUITE(ScoreAdBrowserSignalRenderSizeTest);
class SellerWorkletSharedStorageAPIDisabledTest : public SellerWorkletTest {
public:
SellerWorkletSharedStorageAPIDisabledTest() {
feature_list_.InitAndDisableFeature(network::features::kSharedStorageAPI);
}
protected:
base::test::ScopedFeatureList feature_list_;
};
TEST_F(SellerWorkletSharedStorageAPIDisabledTest, SharedStorageNotExposed) {
RunScoreAdWithJavascriptExpectingResult(
CreateScoreAdScript("5", R"(
sharedStorage.clear();
)"),
0,
{"https://url.test/:5 Uncaught ReferenceError: sharedStorage is not "
"defined."},
mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
{});
RunReportResultCreatedScriptExpectingResult(
R"(5)",
R"(
sharedStorage.clear();
)",
std::nullopt,
std::nullopt, {},
{},
{"https://url.test/:11 Uncaught ReferenceError: sharedStorage is not "
"defined."});
}
class SellerWorkletSharedStorageAPIEnabledTest : public SellerWorkletTest {
public:
SellerWorkletSharedStorageAPIEnabledTest() {
feature_list_.InitAndEnableFeature(network::features::kSharedStorageAPI);
permissions_policy_state_ =
mojom::AuctionWorkletPermissionsPolicyState::New(
true,
true);
}
protected:
base::test::ScopedFeatureList feature_list_;
};
TEST_F(SellerWorkletSharedStorageAPIEnabledTest, SharedStorageWriteInScoreAd) {
auction_worklet::TestAuctionSharedStorageHost test_shared_storage_host;
{
mojo::Receiver<auction_worklet::mojom::AuctionSharedStorageHost> receiver(
&test_shared_storage_host);
shared_storage_hosts_[0] = receiver.BindNewPipeAndPassRemote();
RunScoreAdWithJavascriptExpectingResult(
CreateScoreAdScript("5", R"(
sharedStorage.set('a', 'b');
sharedStorage.set('a', 'b', {ignoreIfPresent: true});
sharedStorage.append('a', 'b');
sharedStorage.delete('a');
sharedStorage.clear();
sharedStorage.clear({withLock: 'lock1'});
)"),
5,
{}, mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
{});
task_environment_.RunUntilIdle();
using Request = auction_worklet::TestAuctionSharedStorageHost::Request;
EXPECT_THAT(test_shared_storage_host.observed_requests(),
testing::ElementsAre(
Request(MojomSetMethod(u"a", u"b",
false),
mojom::AuctionWorkletFunction::kSellerScoreAd),
Request(MojomSetMethod(u"a", u"b",
true),
mojom::AuctionWorkletFunction::kSellerScoreAd),
Request(MojomAppendMethod(u"a", u"b"),
mojom::AuctionWorkletFunction::kSellerScoreAd),
Request(MojomDeleteMethod(u"a"),
mojom::AuctionWorkletFunction::kSellerScoreAd),
Request(MojomClearMethod(),
mojom::AuctionWorkletFunction::kSellerScoreAd),
Request(MojomClearMethod("lock1"),
mojom::AuctionWorkletFunction::kSellerScoreAd)));
}
{
shared_storage_hosts_[0] =
mojo::PendingRemote<mojom::AuctionSharedStorageHost>();
permissions_policy_state_ =
mojom::AuctionWorkletPermissionsPolicyState::New(
true,
false);
RunScoreAdWithJavascriptExpectingResult(
CreateScoreAdScript("5", R"(
sharedStorage.clear();
)"),
0,
{"https://url.test/:5 Uncaught TypeError: The \"shared-storage\" "
"Permissions Policy denied the method on sharedStorage."},
mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
{});
permissions_policy_state_ =
mojom::AuctionWorkletPermissionsPolicyState::New(
true,
true);
}
}
TEST_F(SellerWorkletSharedStorageAPIEnabledTest,
SharedStorageWriteInReportResult) {
auction_worklet::TestAuctionSharedStorageHost test_shared_storage_host;
{
mojo::Receiver<auction_worklet::mojom::AuctionSharedStorageHost> receiver(
&test_shared_storage_host);
shared_storage_hosts_[0] = receiver.BindNewPipeAndPassRemote();
RunReportResultCreatedScriptExpectingResult(
R"(5)",
R"(
sharedStorage.set('a', 'b');
sharedStorage.set('a', 'b', {ignoreIfPresent: true});
sharedStorage.append('a', 'b');
sharedStorage.delete('a');
sharedStorage.clear();
sharedStorage.clear({withLock: 'lock1'});
)",
"5",
std::nullopt, {},
{},
{});
task_environment_.RunUntilIdle();
using Request = auction_worklet::TestAuctionSharedStorageHost::Request;
EXPECT_THAT(
test_shared_storage_host.observed_requests(),
testing::ElementsAre(
Request(MojomSetMethod(u"a", u"b",
false),
mojom::AuctionWorkletFunction::kSellerReportResult),
Request(MojomSetMethod(u"a", u"b",
true),
mojom::AuctionWorkletFunction::kSellerReportResult),
Request(MojomAppendMethod(u"a", u"b"),
mojom::AuctionWorkletFunction::kSellerReportResult),
Request(MojomDeleteMethod(u"a"),
mojom::AuctionWorkletFunction::kSellerReportResult),
Request(MojomClearMethod(),
mojom::AuctionWorkletFunction::kSellerReportResult),
Request(MojomClearMethod("lock1"),
mojom::AuctionWorkletFunction::kSellerReportResult)));
}
{
shared_storage_hosts_[0] =
mojo::PendingRemote<mojom::AuctionSharedStorageHost>();
permissions_policy_state_ =
mojom::AuctionWorkletPermissionsPolicyState::New(
true,
false);
RunReportResultCreatedScriptExpectingResult(
R"(5)",
R"(
sharedStorage.clear();
)",
std::nullopt,
std::nullopt, {},
{},
{"https://url.test/:11 Uncaught TypeError: The \"shared-storage\" "
"Permissions Policy denied the method on sharedStorage."});
permissions_policy_state_ =
mojom::AuctionWorkletPermissionsPolicyState::New(
true,
true);
}
}
class SellerWorkletKVv2Test : public SellerWorkletTest,
public mojom::TrustedSignalsCache {
public:
SellerWorkletKVv2Test() {
scoped_feature_list_.InitAndEnableFeature(
blink::features::kFledgeTrustedSignalsKVv2Support);
trusted_scoring_signals_url_ =
GURL("https://url.test/trusted_scoring_signals");
auction_ad_config_non_shared_params_.trusted_scoring_signals_coordinator =
url::Origin::Create(GURL("https://value.that.does.not.matter.test"));
public_key_ = mojom::TrustedSignalsPublicKey::New(
std::string(reinterpret_cast<const char*>(&kTestPublicKey[0]),
sizeof(kTestPublicKey)),
kKeyId);
}
~SellerWorkletKVv2Test() override {
EXPECT_TRUE(pending_cache_clients_.empty());
}
void EnableSignalsCache() {
trusted_signals_kvv2_manager_ = std::make_unique<TrustedSignalsKVv2Manager>(
cache_receiver_.BindNewPipeAndPassRemote(), v8_helper());
trusted_signals_cache_key_ = mojom::TrustedSignalsCacheKey::New(
base::UnguessableToken::Create(), 0);
public_key_.reset();
}
void GetTrustedSignals(
const base::UnguessableToken& compression_group_token,
mojo::PendingRemote<mojom::TrustedSignalsCacheClient> client) override {
bool inserted = pending_cache_clients_
.try_emplace(compression_group_token, std::move(client))
.second;
EXPECT_TRUE(inserted);
if (wait_for_cache_requests_loop_ &&
waiting_for_cache_requests_count_ == pending_cache_clients_.size()) {
wait_for_cache_requests_loop_->Quit();
}
}
void WaitForCacheRequests(size_t count) {
if (pending_cache_clients_.size() < count) {
waiting_for_cache_requests_count_ = count;
wait_for_cache_requests_loop_ = std::make_unique<base::RunLoop>();
wait_for_cache_requests_loop_->Run();
wait_for_cache_requests_loop_.reset();
}
EXPECT_EQ(pending_cache_clients_.size(), count);
}
void WaitForRequestAndSendResponse(const std::string& response_json_content) {
if (trusted_signals_kvv2_manager_) {
WaitForCacheRequests(1);
RespondToCacheRequest(trusted_signals_cache_key_->compression_group_token,
response_json_content);
return;
}
task_environment_.RunUntilIdle();
const network::ResourceRequest* pending_request;
ASSERT_TRUE(url_loader_factory_.IsPending(
trusted_scoring_signals_url_->spec(), &pending_request));
std::string request_body =
std::string(pending_request->request_body->elements()
->at(0)
.As<network::DataElementBytes>()
.AsStringPiece());
std::string response_body =
GenerateResponseBody(request_body, response_json_content);
std::string headers =
base::StringPrintf("%s\nContent-Type: %s", kAllowFledgeHeader,
"message/ad-auction-trusted-signals-request");
AddResponse(&url_loader_factory_, trusted_scoring_signals_url_.value(),
kAdAuctionTrustedSignalsMimeType,
std::nullopt, response_body, headers);
}
void RespondToCacheRequest(base::UnguessableToken compression_group_token,
const std::string& response_json_content) {
auto client_it = pending_cache_clients_.find(compression_group_token);
ASSERT_TRUE(client_it != pending_cache_clients_.end());
mojo::Remote<mojom::TrustedSignalsCacheClient> client(
std::move(client_it->second));
pending_cache_clients_.erase(client_it);
client->OnSuccess(mojom::TrustedSignalsCompressionScheme::kNone,
{test::ToCborVector(response_json_content)});
}
static std::string GenerateResponseBody(
const std::string& request_body,
const std::string& response_json_content) {
auto response_key_config = quiche::ObliviousHttpHeaderKeyConfig::Create(
kKeyId, EVP_HPKE_DHKEM_X25519_HKDF_SHA256, EVP_HPKE_HKDF_SHA256,
EVP_HPKE_AES_256_GCM);
CHECK(response_key_config.ok()) << response_key_config.status();
auto ohttp_gateway =
quiche::ObliviousHttpGateway::Create(
std::string(reinterpret_cast<const char*>(&kTestPrivateKey[0]),
sizeof(kTestPrivateKey)),
response_key_config.value())
.value();
auto received_request = ohttp_gateway.DecryptObliviousHttpRequest(
request_body, kTrustedSignalsKVv2EncryptionRequestMediaType);
CHECK(received_request.ok()) << received_request.status();
cbor::Value::MapValue compression_group;
compression_group.try_emplace(cbor::Value("compressionGroupId"),
cbor::Value(0));
compression_group.try_emplace(cbor::Value("ttlMs"), cbor::Value(100));
compression_group.try_emplace(
cbor::Value("content"),
cbor::Value(test::ToCborVector(response_json_content)));
cbor::Value::ArrayValue compression_groups;
compression_groups.emplace_back(std::move(compression_group));
cbor::Value::MapValue body_map;
body_map.try_emplace(cbor::Value("compressionGroups"),
cbor::Value(std::move(compression_groups)));
cbor::Value body_value(std::move(body_map));
std::optional<std::vector<uint8_t>> maybe_body_bytes =
cbor::Writer::Write(body_value);
CHECK(maybe_body_bytes);
std::string response_body = test::CreateKVv2ResponseBody(
base::as_string_view(maybe_body_bytes.value()));
auto response_context =
std::move(received_request).value().ReleaseContext();
auto maybe_response = ohttp_gateway.CreateObliviousHttpResponse(
response_body, response_context,
kTrustedSignalsKVv2EncryptionResponseMediaType);
CHECK(maybe_response.ok()) << maybe_response.status();
return maybe_response->EncapsulateAndSerialize();
}
protected:
base::test::ScopedFeatureList scoped_feature_list_;
std::map<base::UnguessableToken,
mojo::PendingRemote<mojom::TrustedSignalsCacheClient>>
pending_cache_clients_;
std::unique_ptr<base::RunLoop> wait_for_cache_requests_loop_;
size_t waiting_for_cache_requests_count_ = 0;
mojo::Receiver<mojom::TrustedSignalsCache> cache_receiver_{this};
};
TEST_F(SellerWorkletKVv2Test, SignalsError) {
const char kJson[] = "{}";
const base::TimeDelta kDelay = base::Milliseconds(135);
const std::string kScoreAdScript =
CreateScoreAdScript("null === trustedScoringSignals ? 2 : 0");
for (bool use_signals_cache : {false, true}) {
SCOPED_TRACE(use_signals_cache);
if (use_signals_cache) {
EnableSignalsCache();
}
for (bool signals_first : {false, true}) {
SCOPED_TRACE(signals_first);
url_loader_factory_.ClearResponses();
auto seller_worklet = CreateWorklet();
ASSERT_TRUE(seller_worklet);
base::RunLoop run_loop;
RunScoreAdOnWorkletAsync(
seller_worklet.get(), 2,
{"Content is not type of array."},
mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
{},
GetRealTimeReportingContributionsOnError(
true,
false),
std::nullopt,
false,
kDelay,
std::nullopt, run_loop.QuitClosure());
if (signals_first) {
task_environment_.FastForwardBy(kDelay);
WaitForRequestAndSendResponse(kJson);
task_environment_.RunUntilIdle();
AddJavascriptResponse(&url_loader_factory_, decision_logic_url_,
kScoreAdScript);
} else {
AddJavascriptResponse(&url_loader_factory_, decision_logic_url_,
kScoreAdScript);
task_environment_.FastForwardBy(kDelay);
WaitForRequestAndSendResponse(kJson);
}
run_loop.Run();
}
}
}
TEST_F(SellerWorkletKVv2Test, ScoreAdTrustedScoringSignals) {
const char kJson[] =
R"([{
"id": 0,
"dataVersion": 101,
"keyGroupOutputs": [
{
"tags": [
"renderURLs"
],
"keyValues": {
"https://bar.test/": {
"value": "1"
}
}
},
{
"tags": [
"adComponentRenderURLs"
],
"keyValues": {
"https://barsub.test/": {
"value": "2"
},
"https://foosub.test/": {
"value": "[3]"
}
}
}
]
}])";
const char kValidate[] = R"(
const expected = '{"renderURL":{"https://bar.test/":1},' +
'"renderUrl":{"https://bar.test/":1},"adComponentRenderURLs":' +
'{"https://barsub.test/":2,"https://foosub.test/":[3]},' +
'"adComponentRenderUrls":{"https://barsub.test/":2,' +
'"https://foosub.test/":[3]}}'
const actual = JSON.stringify(trustedScoringSignals);
if (actual === expected)
return 2;
throw actual + "!" + expected;
)";
const base::TimeDelta kDelay = base::Milliseconds(135);
browser_signal_render_url_ = GURL("https://bar.test/");
component_ads_.push_back(
auction_worklet::mojom::CreativeInfoWithoutOwner::New(
blink::AdDescriptor(GURL("https://barsub.test/")),
std::nullopt));
component_ads_.push_back(
auction_worklet::mojom::CreativeInfoWithoutOwner::New(
blink::AdDescriptor(GURL("https://foosub.test/")),
std::nullopt));
for (bool use_signals_cache : {false, true}) {
SCOPED_TRACE(use_signals_cache);
if (use_signals_cache) {
EnableSignalsCache();
}
for (bool signals_first : {false, true}) {
SCOPED_TRACE(signals_first);
url_loader_factory_.ClearResponses();
auto seller_worklet = CreateWorklet();
ASSERT_TRUE(seller_worklet);
base::RunLoop run_loop;
RunScoreAdOnWorkletAsync(
seller_worklet.get(), 2,
{}, mojom::ComponentAuctionModifiedBidParamsPtr(),
101,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
{},
{},
std::nullopt,
false,
kDelay,
std::nullopt, run_loop.QuitClosure());
if (signals_first) {
task_environment_.FastForwardBy(kDelay);
WaitForRequestAndSendResponse(kJson);
task_environment_.RunUntilIdle();
AddJavascriptResponse(
&url_loader_factory_, decision_logic_url_,
CreateScoreAdScript("1", kValidate));
} else {
AddJavascriptResponse(
&url_loader_factory_, decision_logic_url_,
CreateScoreAdScript("1", kValidate));
task_environment_.FastForwardBy(kDelay);
WaitForRequestAndSendResponse(kJson);
}
run_loop.Run();
}
}
}
TEST_F(SellerWorkletKVv2Test,
MultipleRequestsWithDifferentCompressionGroupTokens) {
EnableSignalsCache();
const std::array<const char*, 2> kCompressionGroups{
R"([{
"id": 0,
"dataVersion": 2,
"keyGroupOutputs": []
}])",
R"([{
"id": 1,
"dataVersion": 3,
"keyGroupOutputs": []
}])"};
const std::array<mojom::TrustedSignalsCacheKeyPtr, 2>
trusted_signals_cache_keys = {
mojom::TrustedSignalsCacheKey::New(base::UnguessableToken::Create(),
0),
mojom::TrustedSignalsCacheKey::New(base::UnguessableToken::Create(),
1),
};
AddJavascriptResponse(&url_loader_factory_, decision_logic_url_,
CreateScoreAdScript("1"));
auto seller_worklet = CreateWorklet();
std::array<base::RunLoop, 2> run_loops;
for (size_t i = 0; i < 2; ++i) {
trusted_signals_cache_key_ = trusted_signals_cache_keys[i].Clone();
RunScoreAdOnWorkletAsync(seller_worklet.get(), 1,
{},
mojom::ComponentAuctionModifiedBidParamsPtr(),
2 + i,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
{},
{},
std::nullopt,
false,
std::nullopt,
std::nullopt,
run_loops[i].QuitClosure());
}
WaitForCacheRequests(2);
for (int i = 0; i < 2; ++i) {
RespondToCacheRequest(
trusted_signals_cache_keys[i]->compression_group_token,
kCompressionGroups[i]);
}
run_loops[0].Run();
run_loops[1].Run();
}
TEST_F(SellerWorkletKVv2Test, MultipleScoreAdCallsSingleCacheRequest) {
EnableSignalsCache();
const char kJson[] = {
R"([
{
"id": 0,
"dataVersion": 3,
"keyGroupOutputs": []
},
{
"id": 1,
"dataVersion": 4,
"keyGroupOutputs": []
}
])"};
direct_from_seller_seller_signals_ = GURL("https://url.test/sellersignals");
AddJavascriptResponse(&url_loader_factory_, decision_logic_url_,
CreateScoreAdScript("1"));
auto seller_worklet = CreateWorklet();
base::RunLoop run_loop1;
RunScoreAdOnWorkletAsync(
seller_worklet.get(), 1,
{}, mojom::ComponentAuctionModifiedBidParamsPtr(),
3,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
{},
{},
std::nullopt,
false,
std::nullopt,
std::nullopt, run_loop1.QuitClosure());
WaitForRequestAndSendResponse(kJson);
task_environment_.RunUntilIdle();
EXPECT_FALSE(run_loop1.AnyQuitCalled());
base::WaitableEvent* event_handle = WedgeV8Thread(v8_helper().get());
AddResponse(&url_loader_factory_, *direct_from_seller_seller_signals_,
kJsonMimeType, std::nullopt, "{}",
"Ad-Auction-Allowed: true\nAd-Auction-Only: true");
base::RunLoop().RunUntilIdle();
trusted_signals_cache_key_->partition_id = 1;
base::RunLoop run_loop2;
RunScoreAdOnWorkletAsync(
seller_worklet.get(), 1,
{}, mojom::ComponentAuctionModifiedBidParamsPtr(),
4,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
{},
{},
std::nullopt,
false,
std::nullopt,
std::nullopt, run_loop2.QuitClosure());
seller_worklet.FlushForTesting();
event_handle->Signal();
run_loop1.Run();
run_loop2.Run();
EXPECT_TRUE(pending_cache_clients_.empty());
RunScoreAdOnWorkletExpectingCallbackNeverInvoked(seller_worklet.get());
WaitForCacheRequests(1);
EXPECT_EQ(pending_cache_clients_.count(
trusted_signals_cache_key_->compression_group_token),
1u);
seller_worklets_.Clear();
pending_cache_clients_.clear();
}
TEST_F(SellerWorkletKVv2Test, TrustedScoringSignalsV1WithKVv2Cache) {
EnableSignalsCache();
trusted_signals_cache_key_.reset();
auction_ad_config_non_shared_params_.trusted_scoring_signals_coordinator =
std::nullopt;
AddJsonResponse(
&url_loader_factory_,
GURL("https://url.test/trusted_scoring_signals?hostname=window.test"
"&renderUrls=https%3A%2F%2Frender.url.test%2F"),
R"({"renderUrls": {"https://render.url.test/": 7}})");
RunScoreAdWithReturnValueExpectingResult(
R"({desirability:trustedScoringSignals.renderURL["https://render.url.test/"]})",
7);
}
TEST_F(SellerWorkletKVv2Test,
TrustedScoringSignalsCacheCrossOriginPermissionsDenied) {
trusted_scoring_signals_url_ =
GURL("https://cross-origin-url.test/trusted_scoring_signals");
EnableSignalsCache();
AddJavascriptResponse(
&url_loader_factory_, decision_logic_url_,
CreateScoreAdScript("(trustedScoringSignals === null &&"
" crossOriginTrustedSignals === null) ? 1 : 0"));
auto seller_worklet = CreateWorklet();
ASSERT_FALSE(WaitAndGetTrustedSignalsUrlAllowed());
trusted_signals_cache_key_.reset();
RunScoreAdExpectingResultOnWorklet(
seller_worklet.get(), 1,
{"https://url.test/ disregarding trusted scoring signals since origin "
"'https://cross-origin-url.test' is different from script's origin but "
"not authorized by script's "
"Ad-Auction-Allow-Trusted-Scoring-Signals-From."});
EXPECT_EQ(1u, url_loader_factory_.total_requests());
}
TEST_F(SellerWorkletKVv2Test,
TrustedScoringSignalsCacheCrossOriginPermissionsAllowed) {
trusted_scoring_signals_url_ =
GURL("https://cross-origin-url.test/trusted_scoring_signals");
EnableSignalsCache();
AddJavascriptResponse(
&url_loader_factory_, decision_logic_url_,
CreateScoreAdScript(
"crossOriginTrustedSignals[\"https://cross-origin-url.test\"]"
" .renderURL[\"https://render.url.test/\"]",
"if (trustedScoringSignals !== null)"
" throw \"Unexpected trustedScoringSignals\";"),
std::string("Ad-Auction-Allow-Trusted-Scoring-Signals-From:"
" \"https://cross-origin-url.test/\""));
auto seller_worklet = CreateWorklet();
ASSERT_TRUE(WaitAndGetTrustedSignalsUrlAllowed());
base::RunLoop run_loop;
RunScoreAdOnWorkletAsync(
seller_worklet.get(), 3,
{}, mojom::ComponentAuctionModifiedBidParamsPtr(),
4,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
{},
{},
std::nullopt,
false,
std::nullopt,
std::nullopt, run_loop.QuitClosure());
WaitForRequestAndSendResponse(R"(
[{
"id": 0,
"dataVersion": 4,
"keyGroupOutputs": [
{
"tags": [
"renderURLs"
],
"keyValues": {
"https://render.url.test/": {
"value": "3"
}
}
}
]
}])");
run_loop.Run();
}
class SellerWorkletTwoThreadsSharedStorageAPIEnabledTest
: public SellerWorkletSharedStorageAPIEnabledTest {
public:
size_t NumThreads() override { return 2u; }
};
TEST_F(SellerWorkletTwoThreadsSharedStorageAPIEnabledTest,
SharedStorageWriteInScoreAd) {
auction_worklet::TestAuctionSharedStorageHost test_shared_storage_host0;
auction_worklet::TestAuctionSharedStorageHost test_shared_storage_host1;
mojo::Receiver<auction_worklet::mojom::AuctionSharedStorageHost> receiver0(
&test_shared_storage_host0);
shared_storage_hosts_[0] = receiver0.BindNewPipeAndPassRemote();
mojo::Receiver<auction_worklet::mojom::AuctionSharedStorageHost> receiver1(
&test_shared_storage_host0);
shared_storage_hosts_[1] = receiver1.BindNewPipeAndPassRemote();
AddJavascriptResponse(
&url_loader_factory_, decision_logic_url_,
CreateScoreAdScript("5", R"(
sharedStorage.set('a', 'b');
)"));
auto seller_worklet = CreateWorklet();
RunScoreAdExpectingResultOnWorklet(seller_worklet.get(), 5);
task_environment_.RunUntilIdle();
EXPECT_TRUE(test_shared_storage_host1.observed_requests().empty());
using Request = auction_worklet::TestAuctionSharedStorageHost::Request;
EXPECT_THAT(test_shared_storage_host0.observed_requests(),
testing::ElementsAre(
Request(MojomSetMethod(u"a", u"b",
false),
mojom::AuctionWorkletFunction::kSellerScoreAd)));
}
class SellerWorkletRealTimeTest : public SellerWorkletTest {
public:
SellerWorkletRealTimeTest()
: SellerWorkletTest(
base::test::TaskEnvironment::TimeSource::SYSTEM_TIME) {}
};
TEST_F(SellerWorkletRealTimeTest, ScoreAdDefaultTimeout) {
AddJavascriptResponse(
&url_loader_factory_, decision_logic_url_,
CreateScoreAdScript("", R"(while (1))"));
auto seller_worklet = CreateWorklet();
ASSERT_TRUE(seller_worklet);
base::RunLoop run_loop;
RunScoreAdOnWorkletAsync(
seller_worklet.get(), 0,
{"https://url.test/ execution of `scoreAd` timed out."},
mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
{},
{},
std::nullopt,
true,
std::nullopt,
std::nullopt, run_loop.QuitClosure());
run_loop.Run();
}
TEST_F(SellerWorkletRealTimeTest, ScoreAdTopLevelTimeout) {
AddJavascriptResponse(&url_loader_factory_, decision_logic_url_,
"while (1) {}");
auto seller_worklet = CreateWorklet();
ASSERT_TRUE(seller_worklet);
base::RunLoop run_loop;
RunScoreAdOnWorkletAsync(seller_worklet.get(), 0,
{"https://url.test/ top-level execution timed out."},
mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
{},
{},
std::nullopt,
true,
std::nullopt,
std::nullopt,
run_loop.QuitClosure());
run_loop.Run();
}
TEST_F(SellerWorkletRealTimeTest, ScoreAdZeroTimeout) {
seller_timeout_ = base::TimeDelta();
AddJavascriptResponse(&url_loader_factory_, decision_logic_url_,
CreateScoreAdScript("10"));
auto seller_worklet = CreateWorklet();
ASSERT_TRUE(seller_worklet);
base::RunLoop run_loop;
RunScoreAdOnWorkletAsync(seller_worklet.get(), 0,
{"scoreAd() aborted due to zero timeout."},
mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
{},
{},
std::nullopt,
true,
std::nullopt,
std::nullopt,
run_loop.QuitClosure());
run_loop.Run();
}
TEST_F(SellerWorkletRealTimeTest, ScoreAdSellerTimeoutFromAuctionConfig) {
const base::TimeDelta kScriptTimeout = base::Days(360);
v8_helper()->v8_runner()->PostTask(
FROM_HERE,
base::BindOnce(
[](scoped_refptr<AuctionV8Helper> v8_helper,
const base::TimeDelta script_timeout) {
v8_helper->set_script_timeout_for_testing(script_timeout);
},
v8_helper(), kScriptTimeout));
task_environment_.RunUntilIdle();
seller_timeout_ = base::Milliseconds(20);
AddJavascriptResponse(
&url_loader_factory_, decision_logic_url_,
CreateScoreAdScript("", R"(while (1))"));
auto seller_worklet = CreateWorklet();
ASSERT_TRUE(seller_worklet);
base::RunLoop run_loop;
RunScoreAdOnWorkletAsync(
seller_worklet.get(), 0,
{"https://url.test/ execution of `scoreAd` timed out."},
mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
{},
{},
std::nullopt,
true,
std::nullopt,
std::nullopt, run_loop.QuitClosure());
run_loop.Run();
}
TEST_F(SellerWorkletRealTimeTest, ScoreAdJsonTimeout) {
seller_timeout_ = base::Milliseconds(20);
const char kReturnVal[] = R"({
allowComponentAuction: true,
desirability: 5,
ad: {
get field() { while(true) {} }
}
})";
AddJavascriptResponse(&url_loader_factory_, decision_logic_url_,
CreateScoreAdScript(kReturnVal));
auto seller_worklet = CreateWorklet();
ASSERT_TRUE(seller_worklet);
base::RunLoop run_loop;
browser_signals_other_seller_ =
mojom::ComponentAuctionOtherSeller::NewTopLevelSeller(
url::Origin::Create(GURL("https://top.seller.test")));
RunScoreAdOnWorkletAsync(seller_worklet.get(), 0,
{"https://url.test/ timeout serializing `ad` field "
"of scoreAd() return value."},
mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
{},
{},
std::nullopt,
true,
std::nullopt,
std::nullopt,
run_loop.QuitClosure());
run_loop.Run();
}
TEST_F(SellerWorkletRealTimeTest, ReportResultLatency) {
AddJavascriptResponse(&url_loader_factory_, decision_logic_url_,
CreateReportToScript("1", "while (true) {}"));
RunReportResultExpectingResult(
std::nullopt,
std::nullopt,
{},
{},
true,
{"https://url.test/ execution of `reportResult` timed out."});
}
TEST_F(SellerWorkletRealTimeTest, ReportResultZeroTimeout) {
AddJavascriptResponse(&url_loader_factory_, decision_logic_url_,
CreateReportToScript("1", "throw 'something'"));
auction_ad_config_non_shared_params_.reporting_timeout = base::TimeDelta();
RunReportResultExpectingResult(
std::nullopt,
std::nullopt,
{},
{},
true,
{"reportResult() aborted due to zero timeout."});
}
TEST_F(SellerWorkletRealTimeTest, ReportResultTimeoutFromAuctionConfig) {
const base::TimeDelta kScriptTimeout = base::Days(360);
v8_helper()->v8_runner()->PostTask(
FROM_HERE,
base::BindOnce(
[](scoped_refptr<AuctionV8Helper> v8_helper,
const base::TimeDelta script_timeout) {
v8_helper->set_script_timeout_for_testing(script_timeout);
},
v8_helper(), kScriptTimeout));
task_environment_.RunUntilIdle();
auction_ad_config_non_shared_params_.reporting_timeout =
base::Milliseconds(50);
AddJavascriptResponse(&url_loader_factory_, decision_logic_url_,
CreateReportToScript("1", "while (true) {}"));
RunReportResultExpectingResult(
std::nullopt,
std::nullopt,
{},
{},
true,
{"https://url.test/ execution of `reportResult` timed out."});
}
TEST_F(SellerWorkletRealTimeTest, ReportResultJsonTimeout) {
const char kReturnVal[] = R"({
desirability: 5,
ad: {
get field() { while(true) {} }
}
})";
auction_ad_config_non_shared_params_.reporting_timeout =
base::Milliseconds(30);
AddJavascriptResponse(&url_loader_factory_, decision_logic_url_,
CreateReportToScript(kReturnVal));
RunReportResultExpectingResult(
std::nullopt,
std::nullopt,
{},
{},
true,
{"https://url.test/ timeout serializing reportResult() return value."});
}
TEST_F(SellerWorkletRealTimeTest, ReportResultTopLevelTimeout) {
auction_ad_config_non_shared_params_.reporting_timeout =
base::Milliseconds(30);
AddJavascriptResponse(&url_loader_factory_, decision_logic_url_,
"while (true) {}");
RunReportResultExpectingResult(
std::nullopt,
std::nullopt,
{},
{},
true,
{"https://url.test/ top-level execution timed out."});
}
TEST_F(SellerWorkletRealTimeTest, ForDebuggingOnlyReports) {
RunScoreAdWithJavascriptExpectingResult(
CreateScoreAdScript(
"1",
R"(forDebuggingOnly.reportAdAuctionLoss("https://loss.url");
forDebuggingOnly.reportAdAuctionWin("https://win.url"))"),
1, {}, mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt, GURL("https://loss.url"),
GURL("https://win.url"));
RunScoreAdWithJavascriptExpectingResult(
CreateScoreAdScript(
"-1",
R"(forDebuggingOnly.reportAdAuctionLoss("https://loss.url");
forDebuggingOnly.reportAdAuctionWin("https://win.url"))"),
0, {}, mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt, GURL("https://loss.url"),
GURL("https://win.url"));
RunScoreAdWithJavascriptExpectingResult(
CreateScoreAdScript(
"1", R"(forDebuggingOnly.reportAdAuctionLoss("https://loss.url"))"),
1, {}, mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt, GURL("https://loss.url"));
RunScoreAdWithJavascriptExpectingResult(
CreateScoreAdScript(
"1", R"(forDebuggingOnly.reportAdAuctionWin("https://win.url"))"),
1, {}, mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt, GURL("https://win.url"));
RunScoreAdWithJavascriptExpectingResult(
CreateScoreAdScript("1", R"(forDebuggingOnly.reportAdAuctionLoss(null))"),
0,
{"https://url.test/:4 Uncaught TypeError: "
"reportAdAuctionLoss must be passed a valid HTTPS url."},
mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt);
RunScoreAdWithJavascriptExpectingResult(
CreateScoreAdScript(
"\"invalid_score\"",
R"(forDebuggingOnly.reportAdAuctionLoss("https://loss.url");
forDebuggingOnly.reportAdAuctionWin("https://win.url"))"),
0,
{"https://url.test/ scoreAd() return: Value passed as dictionary is "
"neither object, null, nor undefined."},
mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt);
}
TEST_F(SellerWorkletRealTimeTest,
ForDebuggingOnlyReportsInvalidScoreAdParameter) {
auction_ad_config_non_shared_params_.auction_signals =
blink::AuctionConfig::MaybePromiseJson::FromValue("{invalid json");
RunScoreAdWithJavascriptExpectingResult(
CreateScoreAdScript(
"1",
R"(forDebuggingOnly.reportAdAuctionLoss("https://loss.url");
forDebuggingOnly.reportAdAuctionWin("https://win.url"))"),
0, {}, mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt);
auction_ad_config_non_shared_params_.auction_signals =
blink::AuctionConfig::MaybePromiseJson::FromValue(
R"({"is_auction_signals": true})");
ad_metadata_ = "{invalid_json";
RunScoreAdWithJavascriptExpectingResult(
CreateScoreAdScript(
"1",
R"(forDebuggingOnly.reportAdAuctionLoss("https://loss.url");
forDebuggingOnly.reportAdAuctionWin("https://win.url"))"),
0);
}
TEST_F(SellerWorkletRealTimeTest, ScoreAdHasError) {
RunScoreAdWithJavascriptExpectingResult(
CreateScoreAdScript(
"",
R"(forDebuggingOnly.reportAdAuctionLoss("https://loss.url1");
error;
forDebuggingOnly.reportAdAuctionLoss("https://loss.url2"))"),
0, {"https://url.test/:5 Uncaught ReferenceError: error is not defined."},
mojom::ComponentAuctionModifiedBidParamsPtr(),
{}, GURL("https://loss.url1"));
}
TEST_F(SellerWorkletRealTimeTest, ScoreAdTimedOut) {
AddJavascriptResponse(
&url_loader_factory_, decision_logic_url_,
CreateScoreAdScript(
"",
R"(forDebuggingOnly.reportAdAuctionLoss("https://loss.url1");
while (1);
forDebuggingOnly.reportAdAuctionLoss("https://loss.url2"))"));
auto seller_worklet = CreateWorklet();
ASSERT_TRUE(seller_worklet);
base::RunLoop run_loop;
RunScoreAdOnWorkletAsync(
seller_worklet.get(), 0,
{"https://url.test/ execution of `scoreAd` timed out."},
mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
GURL("https://loss.url1"),
std::nullopt,
mojom::RejectReason::kNotAvailable,
{},
{},
std::nullopt,
true,
std::nullopt,
std::nullopt, run_loop.QuitClosure());
run_loop.Run();
}
TEST_F(SellerWorkletRealTimeTest, ForDebuggingOnlyReportsScriptIsolation) {
AddJavascriptResponse(&url_loader_factory_, decision_logic_url_,
R"(
function scoreAd(adMetadata, bid, auctionConfig, trustedScoringSignals,
browserSignals) {
if (bid === 1) {
forDebuggingOnly.reportAdAuctionLoss("https://loss.url");
forDebuggingOnly.reportAdAuctionWin("https://win.url");
}
return bid;
}
function reportResult() {}
)");
auto seller_worklet = CreateWorklet();
ASSERT_TRUE(seller_worklet);
for (int i = 0; i < 2; ++i) {
base::RunLoop run_loop;
seller_worklet->ScoreAd(
ad_metadata_, i + 1, bid_currency_,
auction_ad_config_non_shared_params_,
auction_worklet::mojom::TrustedSignalsCacheKeyPtr(), MainAd(),
ComponentAds(), direct_from_seller_seller_signals_,
direct_from_seller_seller_signals_header_ad_slot_,
direct_from_seller_auction_signals_,
direct_from_seller_auction_signals_header_ad_slot_,
browser_signals_other_seller_.Clone(), component_expect_bid_currency_,
browser_signal_interest_group_owner_,
browser_signal_selected_buyer_and_seller_reporting_id_,
browser_signal_buyer_and_seller_reporting_id_,
browser_signal_bidding_duration_msecs_,
browser_signal_for_debugging_only_in_cooldown_or_lockout_,
browser_signal_for_debugging_only_sampling_, seller_timeout_,
group_by_origin_id_, allow_group_by_origin_mode_,
1, bidder_joining_origin_,
TestScoreAdClient::Create(base::BindLambdaForTesting(
[&run_loop](double score, mojom::RejectReason reject_reason,
mojom::ComponentAuctionModifiedBidParamsPtr
component_auction_modified_bid_params,
std::optional<double> bid_in_seller_currency,
std::optional<uint32_t> scoring_signals_data_version,
const std::optional<GURL>& debug_loss_report_url,
const std::optional<GURL>& debug_win_report_url,
PrivateAggregationRequests pa_requests,
RealTimeReportingContributions real_time_contributions,
mojom::SellerTimingMetricsPtr score_ad_timing_metrics,
mojom::ScoreAdDependencyLatenciesPtr
score_ad_dependency_latencies,
const std::vector<std::string>& errors) {
if (score == 1) {
EXPECT_TRUE(debug_loss_report_url.has_value());
EXPECT_TRUE(debug_win_report_url.has_value());
EXPECT_EQ(GURL("https://loss.url"),
debug_loss_report_url.value());
EXPECT_EQ(GURL("https://win.url"),
debug_win_report_url.value());
} else {
EXPECT_EQ(std::nullopt, debug_loss_report_url);
EXPECT_EQ(std::nullopt, debug_win_report_url);
}
run_loop.Quit();
})));
run_loop.Run();
}
}
class SellerWorkletSampleDebugReportsDisabledTest : public SellerWorkletTest {
public:
SellerWorkletSampleDebugReportsDisabledTest() {
feature_list_.InitAndDisableFeature(
blink::features::kFledgeSampleDebugReports);
}
private:
base::test::ScopedFeatureList feature_list_;
};
TEST_F(SellerWorkletSampleDebugReportsDisabledTest,
ScoreAdBrowserSignalForDebuggingOnlyInCooldownOrLockout) {
RunScoreAdWithReturnValueExpectingResult(
R"(browserSignals.hasOwnProperty('forDebuggingOnlyInCooldownOrLockout') ?
3 : 0)",
0);
}
class SellerWorkletEnableSampleDebugReportOnCookieSettingTest
: public SellerWorkletTest {
public:
SellerWorkletEnableSampleDebugReportOnCookieSettingTest() {
scoped_feature_list_.InitAndEnableFeature(
blink::features::kFledgeEnableSampleDebugReportOnCookieSetting);
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
TEST_F(SellerWorkletEnableSampleDebugReportOnCookieSettingTest,
ScoreAdBrowserSignalForDebuggingOnlySampling) {
RunScoreAdWithReturnValueExpectingResult(
R"(browserSignals.forDebuggingOnlySampling === false ? 3 : 0)", 3);
browser_signal_for_debugging_only_sampling_ = true;
RunScoreAdWithReturnValueExpectingResult(
R"(browserSignals.forDebuggingOnlySampling === true ? 3 : 0)", 3);
}
class SellerWorkletPrivateAggregationEnabledTest : public SellerWorkletTest {
public:
SellerWorkletPrivateAggregationEnabledTest() {
feature_list_.InitAndEnableFeature(blink::features::kPrivateAggregationApi);
}
private:
base::test::ScopedFeatureList feature_list_;
};
class SellerWorkletPrivateAggregationErrorReportingEnabledTest
: public SellerWorkletPrivateAggregationEnabledTest {
public:
SellerWorkletPrivateAggregationErrorReportingEnabledTest() {
feature_list_.InitAndEnableFeature(
blink::features::kPrivateAggregationApiErrorReporting);
}
private:
base::test::ScopedFeatureList feature_list_;
};
class SellerWorkletPrivateAggregationErrorReportingDisabledTest
: public SellerWorkletPrivateAggregationEnabledTest {
public:
SellerWorkletPrivateAggregationErrorReportingDisabledTest() {
feature_list_.InitAndDisableFeature(
blink::features::kPrivateAggregationApiErrorReporting);
}
private:
base::test::ScopedFeatureList feature_list_;
};
TEST_F(SellerWorkletPrivateAggregationEnabledTest, ScoreAd) {
mojom::PrivateAggregationRequest kExpectedRequest1(
mojom::AggregatableReportContribution::NewHistogramContribution(
blink::mojom::AggregatableReportHistogramContribution::New(
123,
45,
std::nullopt)),
blink::mojom::DebugModeDetails::New());
mojom::PrivateAggregationRequest kExpectedRequest2(
mojom::AggregatableReportContribution::NewHistogramContribution(
blink::mojom::AggregatableReportHistogramContribution::New(
absl::MakeInt128(1, 0),
1,
std::nullopt)),
blink::mojom::DebugModeDetails::New());
mojom::PrivateAggregationRequest kExpectedForEventRequest1(
mojom::AggregatableReportContribution::NewForEventContribution(
mojom::AggregatableReportForEventContribution::New(
mojom::ForEventSignalBucket::NewIdBucket(234),
mojom::ForEventSignalValue::NewIntValue(56),
std::nullopt,
mojom::EventType::NewReservedNonError(
mojom::ReservedNonErrorEventType::kReservedWin))),
blink::mojom::DebugModeDetails::New());
mojom::PrivateAggregationRequest kExpectedForEventRequest2(
mojom::AggregatableReportContribution::NewForEventContribution(
mojom::AggregatableReportForEventContribution::New(
mojom::ForEventSignalBucket::NewIdBucket(
absl::MakeInt128(1,
0)),
mojom::ForEventSignalValue::NewIntValue(2),
std::nullopt,
mojom::EventType::NewReservedNonError(
mojom::ReservedNonErrorEventType::kReservedWin))),
blink::mojom::DebugModeDetails::New());
{
PrivateAggregationRequests expected_pa_requests;
expected_pa_requests.push_back(kExpectedRequest1.Clone());
expected_pa_requests.push_back(kExpectedForEventRequest1.Clone());
RunScoreAdWithJavascriptExpectingResult(
CreateScoreAdScript("5", R"(
privateAggregation.contributeToHistogram({bucket: 123n, value: 45});
privateAggregation.contributeToHistogramOnEvent(
"reserved.win", {bucket: 234n, value: 56});
)"),
5, {},
mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
std::move(expected_pa_requests));
}
{
permissions_policy_state_ =
mojom::AuctionWorkletPermissionsPolicyState::New(
false,
false);
RunScoreAdWithJavascriptExpectingResult(
CreateScoreAdScript("5",
"privateAggregation.contributeToHistogram({bucket: "
"123n, value: 45})"),
0,
{"https://url.test/:4 Uncaught TypeError: The \"private-aggregation\" "
"Permissions Policy denied the method on privateAggregation."},
mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
{});
permissions_policy_state_ =
mojom::AuctionWorkletPermissionsPolicyState::New(
true,
false);
}
{
PrivateAggregationRequests expected_pa_requests;
expected_pa_requests.push_back(kExpectedRequest2.Clone());
expected_pa_requests.push_back(kExpectedForEventRequest2.Clone());
RunScoreAdWithJavascriptExpectingResult(
CreateScoreAdScript("5", R"(
privateAggregation.contributeToHistogram(
{bucket: 18446744073709551616n, value: 1});
privateAggregation.contributeToHistogramOnEvent(
"reserved.win", {bucket: 18446744073709551616n, value: 2});
)"),
5, {},
mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
std::move(expected_pa_requests));
}
{
PrivateAggregationRequests expected_pa_requests;
expected_pa_requests.push_back(kExpectedRequest1.Clone());
expected_pa_requests.push_back(kExpectedRequest2.Clone());
expected_pa_requests.push_back(kExpectedForEventRequest1.Clone());
expected_pa_requests.push_back(kExpectedForEventRequest2.Clone());
RunScoreAdWithJavascriptExpectingResult(
CreateScoreAdScript("5", R"(
privateAggregation.contributeToHistogram({bucket: 123n, value: 45});
privateAggregation.contributeToHistogram(
{bucket: 18446744073709551616n, value: 1});
privateAggregation.contributeToHistogramOnEvent(
"reserved.win", {bucket: 234n, value: 56});
privateAggregation.contributeToHistogramOnEvent(
"reserved.win", {bucket: 18446744073709551616n, value: 2});
)"),
5, {},
mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
std::move(expected_pa_requests));
}
{
PrivateAggregationRequests expected_pa_requests;
expected_pa_requests.push_back(kExpectedRequest1.Clone());
expected_pa_requests.push_back(kExpectedForEventRequest1.Clone());
RunScoreAdWithJavascriptExpectingResult(
CreateScoreAdScript("5", R"(
privateAggregation.contributeToHistogram({bucket: 123n, value: 45});
privateAggregation.contributeToHistogramOnEvent(
"reserved.win", {bucket: 234n, value: 56});
error;
)"),
0,
{"https://url.test/:8 Uncaught ReferenceError: error is not defined."},
mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
std::move(expected_pa_requests));
}
{
PrivateAggregationRequests expected_pa_requests;
expected_pa_requests.push_back(mojom::PrivateAggregationRequest::New(
kExpectedRequest1.contribution->Clone(),
blink::mojom::DebugModeDetails::New(
true, blink::mojom::DebugKey::New(1234u))));
expected_pa_requests.push_back(mojom::PrivateAggregationRequest::New(
kExpectedForEventRequest1.contribution->Clone(),
blink::mojom::DebugModeDetails::New(
true, blink::mojom::DebugKey::New(1234u))));
RunScoreAdWithJavascriptExpectingResult(
CreateScoreAdScript("5",
R"(
privateAggregation.enableDebugMode({debugKey: 1234n});
privateAggregation.contributeToHistogram({bucket: 123n, value: 45});
privateAggregation.contributeToHistogramOnEvent(
"reserved.win", {bucket: 234n, value: 56});
)"),
5, {},
mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
std::move(expected_pa_requests));
}
{
PrivateAggregationRequests expected_pa_requests;
expected_pa_requests.push_back(mojom::PrivateAggregationRequest::New(
kExpectedRequest1.contribution->Clone(),
blink::mojom::DebugModeDetails::New(
true, nullptr)));
expected_pa_requests.push_back(mojom::PrivateAggregationRequest::New(
kExpectedRequest2.contribution->Clone(),
blink::mojom::DebugModeDetails::New(
true, nullptr)));
RunScoreAdWithJavascriptExpectingResult(
CreateScoreAdScript("5",
R"(
privateAggregation.enableDebugMode();
privateAggregation.contributeToHistogram({bucket: 123n, value: 45});
privateAggregation.contributeToHistogram(
{bucket: 18446744073709551616n, value: 1});
)"),
5, {},
mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
std::move(expected_pa_requests));
}
{
PrivateAggregationRequests expected_pa_requests;
expected_pa_requests.push_back(mojom::PrivateAggregationRequest::New(
mojom::AggregatableReportContribution::NewHistogramContribution(
blink::mojom::AggregatableReportHistogramContribution::New(
123,
45,
0)),
blink::mojom::DebugModeDetails::New()));
expected_pa_requests.push_back(mojom::PrivateAggregationRequest::New(
mojom::AggregatableReportContribution::NewForEventContribution(
mojom::AggregatableReportForEventContribution::New(
mojom::ForEventSignalBucket::NewIdBucket(234),
mojom::ForEventSignalValue::NewIntValue(56),
255,
mojom::EventType::NewReservedNonError(
mojom::ReservedNonErrorEventType::kReservedWin))),
blink::mojom::DebugModeDetails::New()));
RunScoreAdWithJavascriptExpectingResult(
CreateScoreAdScript("5", R"(
privateAggregation.contributeToHistogram(
{bucket: 123n, value: 45, filteringId: 0n});
privateAggregation.contributeToHistogramOnEvent(
"reserved.win", {bucket: 234n, value: 56, filteringId: 255n});
)"),
5, {},
mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
std::move(expected_pa_requests));
}
}
TEST_F(SellerWorkletPrivateAggregationErrorReportingEnabledTest, ScoreAd) {
PrivateAggregationRequests expected_pa_requests;
expected_pa_requests.push_back(mojom::PrivateAggregationRequest::New(
mojom::AggregatableReportContribution::NewHistogramContribution(
blink::mojom::AggregatableReportHistogramContribution::New(
123,
45,
0)),
blink::mojom::DebugModeDetails::New()));
expected_pa_requests.push_back(mojom::PrivateAggregationRequest::New(
mojom::AggregatableReportContribution::NewForEventContribution(
mojom::AggregatableReportForEventContribution::New(
mojom::ForEventSignalBucket::NewIdBucket(234),
mojom::ForEventSignalValue::NewIntValue(56),
255,
mojom::EventType::NewReservedError(
mojom::ReservedErrorEventType::kReportSuccess))),
blink::mojom::DebugModeDetails::New()));
RunScoreAdWithJavascriptExpectingResult(
CreateScoreAdScript("5", R"(
privateAggregation.contributeToHistogram(
{bucket: 123n, value: 45, filteringId: 0n});
privateAggregation.contributeToHistogramOnEvent(
"reserved.report-success",
{bucket: 234n, value: 56, filteringId: 255n});
)"),
5, {}, mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
std::move(expected_pa_requests));
}
TEST_F(SellerWorkletPrivateAggregationErrorReportingDisabledTest, ScoreAd) {
PrivateAggregationRequests expected_pa_requests;
expected_pa_requests.push_back(mojom::PrivateAggregationRequest::New(
mojom::AggregatableReportContribution::NewHistogramContribution(
blink::mojom::AggregatableReportHistogramContribution::New(
123,
45,
0)),
blink::mojom::DebugModeDetails::New()));
RunScoreAdWithJavascriptExpectingResult(
CreateScoreAdScript("5", R"(
privateAggregation.contributeToHistogram(
{bucket: 123n, value: 45, filteringId: 0n});
privateAggregation.contributeToHistogramOnEvent(
"reserved.report-success",
{bucket: 234n, value: 56, filteringId: 255n});
)"),
5, {}, mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
std::move(expected_pa_requests));
}
TEST_F(SellerWorkletPrivateAggregationEnabledTest, ReportResult) {
mojom::PrivateAggregationRequest kExpectedRequest1(
mojom::AggregatableReportContribution::NewHistogramContribution(
blink::mojom::AggregatableReportHistogramContribution::New(
123,
45,
std::nullopt)),
blink::mojom::DebugModeDetails::New());
mojom::PrivateAggregationRequest kExpectedRequest2(
mojom::AggregatableReportContribution::NewHistogramContribution(
blink::mojom::AggregatableReportHistogramContribution::New(
absl::MakeInt128(1, 0),
1,
std::nullopt)),
blink::mojom::DebugModeDetails::New());
mojom::PrivateAggregationRequest kExpectedForEventRequest(
mojom::AggregatableReportContribution::NewForEventContribution(
mojom::AggregatableReportForEventContribution::New(
mojom::ForEventSignalBucket::NewIdBucket(234),
mojom::ForEventSignalValue::NewIntValue(56),
std::nullopt,
mojom::EventType::NewReservedNonError(
mojom::ReservedNonErrorEventType::kReservedWin))),
blink::mojom::DebugModeDetails::New());
{
PrivateAggregationRequests expected_pa_requests;
expected_pa_requests.push_back(kExpectedRequest1.Clone());
RunReportResultCreatedScriptExpectingResult(
R"(5)",
R"(
privateAggregation.contributeToHistogram({bucket: 123n, value: 45});
)",
"5",
std::nullopt, {},
std::move(expected_pa_requests),
{});
}
{
PrivateAggregationRequests expected_pa_requests;
expected_pa_requests.push_back(kExpectedForEventRequest.Clone());
RunReportResultCreatedScriptExpectingResult(
"5",
R"(
privateAggregation.contributeToHistogramOnEvent(
"reserved.win", {bucket: 234n, value: 56});
)",
"5",
std::nullopt, {},
std::move(expected_pa_requests),
{});
}
{
PrivateAggregationRequests expected_pa_requests;
expected_pa_requests.push_back(kExpectedRequest1.Clone());
expected_pa_requests.push_back(kExpectedForEventRequest.Clone());
RunReportResultCreatedScriptExpectingResult(
"5",
R"(
privateAggregation.contributeToHistogram({bucket: 123n, value: 45});
privateAggregation.contributeToHistogramOnEvent(
"reserved.win", {bucket: 234n, value: 56});
)",
"5",
std::nullopt, {},
std::move(expected_pa_requests),
{});
}
{
permissions_policy_state_ =
mojom::AuctionWorkletPermissionsPolicyState::New(
false,
false);
RunReportResultCreatedScriptExpectingResult(
R"(5)",
R"(
privateAggregation.contributeToHistogram({bucket: 123n, value: 45});
)",
std::nullopt,
std::nullopt, {},
{},
{"https://url.test/:11 Uncaught TypeError: The \"private-aggregation\" "
"Permissions Policy denied the method on privateAggregation."});
permissions_policy_state_ =
mojom::AuctionWorkletPermissionsPolicyState::New(
true,
false);
}
{
PrivateAggregationRequests expected_pa_requests;
expected_pa_requests.push_back(kExpectedRequest1.Clone());
RunReportResultCreatedScriptExpectingResult(
R"(5)",
R"(
privateAggregation.contributeToHistogram({bucket: 123n, value: 45});
)",
"5",
std::nullopt, {},
std::move(expected_pa_requests),
{});
}
{
PrivateAggregationRequests expected_pa_requests;
expected_pa_requests.push_back(kExpectedRequest2.Clone());
RunReportResultCreatedScriptExpectingResult(
R"(5)",
R"(privateAggregation.contributeToHistogram(
{bucket: 18446744073709551616n, value: 1});)",
"5",
std::nullopt, {},
std::move(expected_pa_requests),
{});
}
{
PrivateAggregationRequests expected_pa_requests;
expected_pa_requests.push_back(kExpectedRequest1.Clone());
expected_pa_requests.push_back(kExpectedRequest2.Clone());
RunReportResultCreatedScriptExpectingResult(
R"(5)",
R"(
privateAggregation.contributeToHistogram({bucket: 123n, value: 45});
privateAggregation.contributeToHistogram(
{bucket: 18446744073709551616n, value: 1});
)",
"5",
std::nullopt, {},
std::move(expected_pa_requests),
{});
}
{
PrivateAggregationRequests expected_pa_requests;
expected_pa_requests.push_back(kExpectedRequest1.Clone());
RunReportResultCreatedScriptExpectingResult(
R"(5)",
R"(
privateAggregation.contributeToHistogram({bucket: 123n, value: 45});
error;
)",
std::nullopt,
std::nullopt, {},
std::move(expected_pa_requests),
{"https://url.test/:12 Uncaught ReferenceError: error is not "
"defined."});
}
{
PrivateAggregationRequests expected_pa_requests;
expected_pa_requests.push_back(mojom::PrivateAggregationRequest::New(
kExpectedRequest1.contribution->Clone(),
blink::mojom::DebugModeDetails::New(
true, blink::mojom::DebugKey::New(1234u))));
RunReportResultCreatedScriptExpectingResult(
"5",
R"(
privateAggregation.enableDebugMode({debugKey: 1234n});
privateAggregation.contributeToHistogram({bucket: 123n, value: 45});
)",
"5",
std::nullopt, {},
std::move(expected_pa_requests),
{});
}
{
PrivateAggregationRequests expected_pa_requests;
expected_pa_requests.push_back(mojom::PrivateAggregationRequest::New(
kExpectedRequest1.contribution->Clone(),
blink::mojom::DebugModeDetails::New(
true, nullptr)));
expected_pa_requests.push_back(mojom::PrivateAggregationRequest::New(
kExpectedRequest2.contribution->Clone(),
blink::mojom::DebugModeDetails::New(
true, nullptr)));
RunReportResultCreatedScriptExpectingResult(
"5",
R"(
privateAggregation.enableDebugMode();
privateAggregation.contributeToHistogram({bucket: 123n, value: 45});
privateAggregation.contributeToHistogram(
{bucket: 18446744073709551616n, value: 1});
)",
"5",
std::nullopt, {},
std::move(expected_pa_requests),
{});
}
{
RunReportResultCreatedScriptExpectingResult(
"5",
R"(
privateAggregation.enableDebugMode();
privateAggregation.enableDebugMode();
)",
std::nullopt,
std::nullopt, {},
{},
{"https://url.test/:12 Uncaught TypeError: enableDebugMode may be "
"called at most once."});
}
{
PrivateAggregationRequests expected_pa_requests;
expected_pa_requests.push_back(mojom::PrivateAggregationRequest::New(
mojom::AggregatableReportContribution::NewHistogramContribution(
blink::mojom::AggregatableReportHistogramContribution::New(
123,
45,
0)),
blink::mojom::DebugModeDetails::New()));
expected_pa_requests.push_back(mojom::PrivateAggregationRequest::New(
mojom::AggregatableReportContribution::NewForEventContribution(
mojom::AggregatableReportForEventContribution::New(
mojom::ForEventSignalBucket::NewIdBucket(234),
mojom::ForEventSignalValue::NewIntValue(56),
255,
mojom::EventType::NewReservedNonError(
mojom::ReservedNonErrorEventType::kReservedWin))),
blink::mojom::DebugModeDetails::New()));
RunReportResultCreatedScriptExpectingResult(
"5",
R"(
privateAggregation.contributeToHistogram(
{bucket: 123n, value: 45, filteringId: 0n});
privateAggregation.contributeToHistogramOnEvent(
"reserved.win", {bucket: 234n, value: 56, filteringId: 255n});
)",
"5",
std::nullopt, {},
std::move(expected_pa_requests),
{});
}
}
TEST_F(SellerWorkletPrivateAggregationErrorReportingEnabledTest, ReportResult) {
PrivateAggregationRequests expected_pa_requests;
expected_pa_requests.push_back(mojom::PrivateAggregationRequest::New(
mojom::AggregatableReportContribution::NewHistogramContribution(
blink::mojom::AggregatableReportHistogramContribution::New(
123,
45,
0)),
blink::mojom::DebugModeDetails::New()));
expected_pa_requests.push_back(mojom::PrivateAggregationRequest::New(
mojom::AggregatableReportContribution::NewForEventContribution(
mojom::AggregatableReportForEventContribution::New(
mojom::ForEventSignalBucket::NewIdBucket(234),
mojom::ForEventSignalValue::NewIntValue(56),
255,
mojom::EventType::NewReservedError(
mojom::ReservedErrorEventType::kReportSuccess))),
blink::mojom::DebugModeDetails::New()));
RunReportResultCreatedScriptExpectingResult(
"5",
R"(
privateAggregation.contributeToHistogram(
{bucket: 123n, value: 45, filteringId: 0n});
privateAggregation.contributeToHistogramOnEvent(
"reserved.report-success",
{bucket: 234n, value: 56, filteringId: 255n});
)",
"5",
std::nullopt, {},
std::move(expected_pa_requests),
{});
}
TEST_F(SellerWorkletPrivateAggregationErrorReportingDisabledTest,
ReportResult) {
PrivateAggregationRequests expected_pa_requests;
expected_pa_requests.push_back(mojom::PrivateAggregationRequest::New(
mojom::AggregatableReportContribution::NewHistogramContribution(
blink::mojom::AggregatableReportHistogramContribution::New(
123,
45,
0)),
blink::mojom::DebugModeDetails::New()));
RunReportResultCreatedScriptExpectingResult(
"5",
R"(
privateAggregation.contributeToHistogram(
{bucket: 123n, value: 45, filteringId: 0n});
privateAggregation.contributeToHistogramOnEvent(
"reserved.report-success",
{bucket: 234n, value: 56, filteringId: 255n});
)",
"5",
std::nullopt, {},
std::move(expected_pa_requests),
{});
}
class SellerWorkletPrivateAggregationDisabledTest : public SellerWorkletTest {
public:
SellerWorkletPrivateAggregationDisabledTest() {
feature_list_.InitAndDisableFeature(
blink::features::kPrivateAggregationApi);
}
private:
base::test::ScopedFeatureList feature_list_;
};
TEST_F(SellerWorkletPrivateAggregationDisabledTest, ScoreAd) {
RunScoreAdWithJavascriptExpectingResult(
CreateScoreAdScript("5",
"privateAggregation.contributeToHistogram({bucket: "
"123n, value: 45})"),
0,
{"https://url.test/:4 Uncaught ReferenceError: privateAggregation is not "
"defined."},
mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
{});
}
TEST_F(SellerWorkletPrivateAggregationDisabledTest, ReportResult) {
RunReportResultCreatedScriptExpectingResult(
R"(5)",
R"(privateAggregation.contributeToHistogram({bucket: 123n, value: 45});)",
std::nullopt,
std::nullopt, {},
{},
{"https://url.test/:10 Uncaught ReferenceError: privateAggregation is "
"not defined."});
}
class SellerWorkletDeprecatedRenderURLReplacementsEnabledTest
: public SellerWorkletTest {
public:
SellerWorkletDeprecatedRenderURLReplacementsEnabledTest() {
feature_list_.InitAndEnableFeature(
blink::features::kFledgeDeprecatedRenderURLReplacements);
}
protected:
base::test::ScopedFeatureList feature_list_;
};
TEST_F(SellerWorkletDeprecatedRenderURLReplacementsEnabledTest,
DeprecatedRenderURLReplacementsArePresentInScoreAdJavascript) {
const std::vector<blink::AuctionConfig::AdKeywordReplacement>
example_replacement = {blink::AuctionConfig::AdKeywordReplacement(
{"${SELLER}", "ExampleSSP"})};
auction_ad_config_non_shared_params_.deprecated_render_url_replacements =
blink::AuctionConfig::MaybePromiseDeprecatedRenderURLReplacements::
FromValue(std::move(example_replacement));
std::string render_url_replacements_validator =
R"(
const replacementsJson =
JSON.stringify(auctionConfig.deprecatedRenderURLReplacements);
if (!(replacementsJson === "{\"${SELLER}\":\"ExampleSSP\"}")) {
throw new Error('deprecatedRenderURLReplacements is incorrect' +
'or missing.');
})";
RunScoreAdWithJavascriptExpectingResult(
CreateScoreAdScript("1", render_url_replacements_validator), 1,
{}, mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt);
}
TEST_F(SellerWorkletTest, SameOrigin) {
trusted_scoring_signals_url_ =
GURL("https://url.test/trusted_scoring_signals");
const GURL kNoComponentSignalsUrl(
"https://url.test/trusted_scoring_signals?hostname=window.test"
"&renderUrls=https%3A%2F%2Frender.url.test%2F");
AddVersionedJsonResponse(&url_loader_factory_, kNoComponentSignalsUrl,
kTrustedScoringSignalsResponse, 5);
RunScoreAdWithReturnValueExpectingResult(
"crossOriginTrustedSignals === null ? 1 : 0", 1,
{}, mojom::ComponentAuctionModifiedBidParamsPtr(),
5);
load_seller_worklet_client_receivers_.FlushForTesting();
EXPECT_TRUE(*trusted_signals_url_allowed_);
RunScoreAdWithReturnValueExpectingResult(
"arguments.length", 7,
{}, mojom::ComponentAuctionModifiedBidParamsPtr(),
5);
RunScoreAdWithReturnValueExpectingResult(
"browserSignals.dataVersion", 5,
{}, mojom::ComponentAuctionModifiedBidParamsPtr(),
5);
RunScoreAdWithReturnValueExpectingResult(
"'crossOriginDataVersion' in browserSignals ? 3 : 2", 2,
{}, mojom::ComponentAuctionModifiedBidParamsPtr(),
5);
RunScoreAdWithReturnValueExpectingResult(
"trustedScoringSignals.renderURL['https://render.url.test/']", 4,
{}, mojom::ComponentAuctionModifiedBidParamsPtr(),
5);
}
TEST_F(SellerWorkletTest, ForbiddenCrossOrigin) {
trusted_scoring_signals_url_ =
GURL("https://other.test/trusted_scoring_signals");
const GURL kNoComponentSignalsUrl(
"https://other.test/trusted_scoring_signals?hostname=window.test"
"&renderUrls=https%3A%2F%2Frender.url.test%2F");
std::vector<std::string> expected_errors = {
"https://url.test/ disregarding trusted scoring signals since origin "
"'https://other.test' is different from script's origin but not "
"authorized by script's Ad-Auction-Allow-Trusted-Scoring-Signals-From."};
AddVersionedJsonResponse(&url_loader_factory_, kNoComponentSignalsUrl,
kTrustedScoringSignalsResponse, 5);
for (bool provide_header : {false, true}) {
SCOPED_TRACE(provide_header);
if (provide_header) {
extra_js_headers_ =
"Ad-Auction-Allow-Trusted-Scoring-Signals-From: "
"\"http://other.test/\", \"https://url.test/\"";
} else {
extra_js_headers_ = std::nullopt;
}
trusted_signals_url_allowed_.reset();
RunScoreAdWithReturnValueExpectingResult(
"crossOriginTrustedSignals === null ? 1 : 0", 1, expected_errors);
load_seller_worklet_client_receivers_.FlushForTesting();
EXPECT_FALSE(*trusted_signals_url_allowed_);
RunScoreAdWithReturnValueExpectingResult("arguments.length", 7,
expected_errors);
RunScoreAdWithReturnValueExpectingResult(
"trustedScoringSignals === null ? 1 : 0", 1, expected_errors);
RunScoreAdWithReturnValueExpectingResult(
"'dataVersion' in browserSignals ? 0 : 1", 1, expected_errors,
mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt);
RunScoreAdWithReturnValueExpectingResult(
"'crossOriginDataVersion' in browserSignals ? 0 : 1", 1,
expected_errors, mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt);
}
}
TEST_F(SellerWorkletTest, ForbiddenCrossOriginNoFetch) {
trusted_scoring_signals_url_ =
GURL("https://other.test/trusted_scoring_signals");
const char kScoreExpr[] = "crossOriginTrustedSignals === null ? 1 : 0";
const char kError[] =
"https://url.test/ disregarding trusted scoring signals since origin "
"'https://other.test' is different from script's origin but not "
"authorized by script's Ad-Auction-Allow-Trusted-Scoring-Signals-From.";
std::vector<GURL> saw_urls;
url_loader_factory_.SetInterceptor(
base::BindLambdaForTesting([&](const network::ResourceRequest& request) {
saw_urls.push_back(request.url);
}));
for (bool provide_header : {false, true}) {
base::HistogramTester histogram_tester;
SCOPED_TRACE(provide_header);
url_loader_factory_.ClearResponses();
saw_urls.clear();
if (provide_header) {
extra_js_headers_ =
"Ad-Auction-Allow-Trusted-Scoring-Signals-From: "
"\"http://other.test/\", \"https://url.test/\"";
} else {
extra_js_headers_ = std::nullopt;
}
base::RunLoop run_loop;
auto seller_worklet = CreateWorklet();
RunScoreAdOnWorkletAsync(seller_worklet.get(),
1.0,
{kError},
mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
{},
{},
std::nullopt,
false,
std::nullopt,
std::nullopt,
run_loop.QuitClosure());
task_environment_.FastForwardBy(
TrustedSignalsRequestManager::kAutoSendDelay);
EXPECT_EQ(1, url_loader_factory_.NumPending());
EXPECT_TRUE(url_loader_factory_.IsPending(decision_logic_url_.spec()));
trusted_signals_url_allowed_.reset();
AddJavascriptResponse(&url_loader_factory_, decision_logic_url_,
CreateScoreAdScript(kScoreExpr), extra_js_headers_);
run_loop.Run();
EXPECT_THAT(saw_urls, testing::ElementsAre(decision_logic_url_));
load_seller_worklet_client_receivers_.FlushForTesting();
EXPECT_FALSE(*trusted_signals_url_allowed_);
histogram_tester.ExpectTotalCount(
"Ads.InterestGroup.Auction."
"TrustedSellerSignalsCrossOriginPermissionWait",
0);
histogram_tester.ExpectUniqueSample(
"Ads.InterestGroup.Auction.TrustedSellerSignalsOriginRelation",
SellerWorklet::SignalsOriginRelation::kForbiddenCrossOriginSignals, 1);
}
}
TEST_F(SellerWorkletTest, AllowedCrossOrigin) {
trusted_scoring_signals_url_ =
GURL("https://other.test/trusted_scoring_signals");
const GURL kNoComponentSignalsUrl(
"https://other.test/trusted_scoring_signals?hostname=window.test"
"&renderUrls=https%3A%2F%2Frender.url.test%2F");
extra_js_headers_ =
"Ad-Auction-Allow-Trusted-Scoring-Signals-From: "
"\"https://more.test\", \"https://other.test/ignored\"";
AddVersionedJsonResponse(&url_loader_factory_, kNoComponentSignalsUrl,
kTrustedScoringSignalsResponse, 5);
RunScoreAdWithReturnValueExpectingResult(
"arguments.length", 7, {},
mojom::ComponentAuctionModifiedBidParamsPtr(),
5);
RunScoreAdWithReturnValueExpectingResult(
"trustedScoringSignals === null ? 1 : 0", 1,
{}, mojom::ComponentAuctionModifiedBidParamsPtr(),
5);
const char kValidate[] = R"(
const expected = '{"https://other.test":{' +
'"renderURL":{"https://render.url.test/":4},' +
'"renderUrl":{"https://render.url.test/":4}}}'
const actual = JSON.stringify(crossOriginTrustedSignals);
if (actual === expected)
return 7;
throw actual + "!" + expected;
)";
trusted_signals_url_allowed_.reset();
RunScoreAdWithJavascriptExpectingResult(
CreateScoreAdScript("3", kValidate), 7,
{}, mojom::ComponentAuctionModifiedBidParamsPtr(),
5);
load_seller_worklet_client_receivers_.FlushForTesting();
EXPECT_TRUE(*trusted_signals_url_allowed_);
RunScoreAdWithReturnValueExpectingResult(
"'dataVersion' in browserSignals ? 0 : 1", 1,
{}, mojom::ComponentAuctionModifiedBidParamsPtr(),
5);
RunScoreAdWithReturnValueExpectingResult(
"browserSignals.crossOriginDataVersion", 5,
{}, mojom::ComponentAuctionModifiedBidParamsPtr(),
5);
}
TEST_F(SellerWorkletTest, AllowedCrossOriginTiming) {
trusted_scoring_signals_url_ =
GURL("https://other.test/trusted_scoring_signals");
const GURL kNoComponentSignalsUrl(
"https://other.test/trusted_scoring_signals?hostname=window.test"
"&renderUrls=https%3A%2F%2Frender.url.test%2F");
extra_js_headers_ =
"Ad-Auction-Allow-Trusted-Scoring-Signals-From: "
"\"https://more.test\", \"https://other.test/ignored\"";
const char kValidate[] = R"(
const expected = '{"https://other.test":{' +
'"renderURL":{"https://render.url.test/":4},' +
'"renderUrl":{"https://render.url.test/":4}}}'
const actual = JSON.stringify(crossOriginTrustedSignals);
if (actual === expected)
return 7;
throw actual + "!" + expected;
)";
for (base::TimeDelta delay :
{base::Seconds(0), TrustedSignalsRequestManager::kAutoSendDelay,
base::Milliseconds(50)}) {
base::HistogramTester histogram_tester;
base::RunLoop run_loop;
auto seller_worklet = CreateWorklet();
RunScoreAdOnWorkletAsync(
seller_worklet.get(),
7,
{}, mojom::ComponentAuctionModifiedBidParamsPtr(),
5,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
{},
{},
std::nullopt,
false,
std::nullopt,
std::nullopt, run_loop.QuitClosure());
if (delay != base::TimeDelta()) {
task_environment_.FastForwardBy(delay);
} else {
task_environment_.RunUntilIdle();
}
EXPECT_EQ(1, url_loader_factory_.NumPending());
EXPECT_TRUE(url_loader_factory_.IsPending(decision_logic_url_.spec()));
AddJavascriptResponse(&url_loader_factory_, decision_logic_url_,
CreateScoreAdScript("3", kValidate),
extra_js_headers_);
task_environment_.RunUntilIdle();
EXPECT_EQ(1, url_loader_factory_.NumPending());
EXPECT_TRUE(url_loader_factory_.IsPending(kNoComponentSignalsUrl.spec()));
task_environment_.AdvanceClock(base::Milliseconds(100));
AddVersionedJsonResponse(&url_loader_factory_, kNoComponentSignalsUrl,
kTrustedScoringSignalsResponse,
5);
run_loop.Run();
url_loader_factory_.ClearResponses();
histogram_tester.ExpectUniqueSample(
"Ads.InterestGroup.Auction."
"TrustedSellerSignalsCrossOriginPermissionWait",
delay.InMilliseconds(), 1);
histogram_tester.ExpectUniqueSample(
"Ads.InterestGroup.Auction.TrustedSellerSignalsOriginRelation",
SellerWorklet::SignalsOriginRelation::kPermittedCrossOriginSignals, 1);
}
}
TEST_F(SellerWorkletTest, ErrorCrossOrigin) {
trusted_scoring_signals_url_ =
GURL("https://other.test/trusted_scoring_signals");
const GURL kNoComponentSignalsUrl(
"https://other.test/trusted_scoring_signals?hostname=window.test"
"&renderUrls=https%3A%2F%2Frender.url.test%2F");
std::vector<std::string> expected_errors = {
"https://other.test/trusted_scoring_signals Unable to parse as a JSON "
"object."};
extra_js_headers_ =
"Ad-Auction-Allow-Trusted-Scoring-Signals-From: "
"\"https://more.test\", \"https://other.test/ignored\"";
AddVersionedJsonResponse(&url_loader_factory_, kNoComponentSignalsUrl, "{",
5);
const char* kTestCases[] = {
"crossOriginTrustedSignals === null ? 1 : 0",
"arguments.length === 7 ? 1 : 0",
"trustedScoringSignals === null ? 1 : 0",
"'dataVersion' in browserSignals ? 0 : 1",
"'crossOriginDataVersion' in browserSignals ? 0 : 1"};
for (const char* test_case : kTestCases) {
SCOPED_TRACE(test_case);
mojom::RealTimeReportingContribution expected_trusted_signal_histogram(
1024 + auction_worklet::RealTimeReportingPlatformError::
kTrustedScoringSignalsFailure,
1,
std::nullopt);
RealTimeReportingContributions expected_real_time_contributions;
expected_real_time_contributions.push_back(
expected_trusted_signal_histogram.Clone());
RunScoreAdWithReturnValueExpectingResult(
test_case, 1, expected_errors,
mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
{},
std::move(expected_real_time_contributions));
}
}
class SellerWorkletRealTimeReportingEnabledTest : public SellerWorkletTest {
public:
SellerWorkletRealTimeReportingEnabledTest()
: SellerWorkletTest(
base::test::TaskEnvironment::TimeSource::SYSTEM_TIME) {
feature_list_.InitAndEnableFeature(
blink::features::kFledgeRealTimeReporting);
}
private:
base::test::ScopedFeatureList feature_list_;
};
TEST_F(SellerWorkletRealTimeReportingEnabledTest, RealTimeReporting) {
mojom::RealTimeReportingContribution expected_histogram(
100, 0.5,
std::nullopt);
constexpr char kExtraCode[] = R"(
realTimeReporting.contributeToHistogram({bucket: 100, priorityWeight: 0.5})
)";
RealTimeReportingContributions expected_real_time_contributions;
expected_real_time_contributions.push_back(expected_histogram.Clone());
RunScoreAdWithJavascriptExpectingResult(
CreateScoreAdScript("5", kExtraCode), 5, {},
mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
{}, std::move(expected_real_time_contributions));
}
TEST_F(SellerWorkletRealTimeReportingEnabledTest, InvalidScore) {
mojom::RealTimeReportingContribution expected_histogram(
100, 0.5,
std::nullopt);
constexpr char kExtraCode[] = R"(
realTimeReporting.contributeToHistogram({bucket: 100, priorityWeight: 0.5})
)";
RealTimeReportingContributions expected_real_time_contributions;
expected_real_time_contributions.push_back(expected_histogram.Clone());
RunScoreAdWithJavascriptExpectingResult(
CreateScoreAdScript("\"invalid_score\"", kExtraCode), 0,
{"https://url.test/ scoreAd() return: Value passed as dictionary is "
"neither object, null, nor undefined."},
mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
{}, std::move(expected_real_time_contributions));
}
TEST_F(SellerWorkletRealTimeReportingEnabledTest, ScriptTimeout) {
seller_timeout_ = base::Milliseconds(3);
mojom::RealTimeReportingContribution expected_histogram(
100, 0.5,
std::nullopt);
mojom::RealTimeReportingContribution expected_latency_histogram(
200, 2, 1);
constexpr char kExtraCode[] = R"(
realTimeReporting.contributeToHistogram({bucket: 100, priorityWeight: 0.5});
realTimeReporting.contributeToHistogram(
{bucket: 200, priorityWeight: 2, latencyThreshold: 1});
while (1);
)";
RealTimeReportingContributions expected_real_time_contributions;
expected_real_time_contributions.push_back(expected_histogram.Clone());
expected_real_time_contributions.push_back(
expected_latency_histogram.Clone());
AddJavascriptResponse(&url_loader_factory_, decision_logic_url_,
CreateScoreAdScript("5", kExtraCode));
auto seller_worklet = CreateWorklet();
ASSERT_TRUE(seller_worklet);
base::RunLoop run_loop;
RunScoreAdOnWorkletAsync(
seller_worklet.get(), 0,
{"https://url.test/ execution of `scoreAd` timed out."},
mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
{},
std::move(expected_real_time_contributions),
std::nullopt,
true,
std::nullopt,
std::nullopt, run_loop.QuitClosure());
run_loop.Run();
}
TEST_F(SellerWorkletRealTimeReportingEnabledTest,
NotExceedingLatencyThreshold) {
mojom::RealTimeReportingContribution expected_histogram(
100, 0.5,
std::nullopt);
constexpr char kExtraCode[] = R"(
realTimeReporting.contributeToHistogram({bucket: 100, priorityWeight: 0.5});
realTimeReporting.contributeToHistogram(
{bucket: 200, priorityWeight: 2, latencyThreshold: 10000000})
)";
RealTimeReportingContributions expected_real_time_contributions;
expected_real_time_contributions.push_back(expected_histogram.Clone());
RunScoreAdWithJavascriptExpectingResult(
CreateScoreAdScript("5", kExtraCode), 5, {},
mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
{}, std::move(expected_real_time_contributions));
}
TEST_F(SellerWorkletRealTimeReportingEnabledTest,
TrustedScoringSignalNetworkError) {
trusted_scoring_signals_url_ =
GURL("https://url.test/trusted_scoring_signals");
const GURL kNoComponentSignalsUrl = GURL(
"https://url.test/trusted_scoring_signals?hostname=window.test"
"&renderUrls=https%3A%2F%2Frender.url.test%2F");
url_loader_factory_.AddResponse(kNoComponentSignalsUrl.spec(),
std::string(),
net::HTTP_NOT_FOUND);
auction_worklet::mojom::RealTimeReportingContribution expected_histogram(
100, 0.5,
std::nullopt);
mojom::RealTimeReportingContribution expected_trusted_signal_histogram(
1024 + auction_worklet::RealTimeReportingPlatformError::
kTrustedScoringSignalsFailure,
1,
std::nullopt);
constexpr char kExtraCode[] = R"(
realTimeReporting.contributeToHistogram({bucket: 100, priorityWeight: 0.5})
)";
RealTimeReportingContributions expected_real_time_contributions;
expected_real_time_contributions.push_back(expected_histogram.Clone());
expected_real_time_contributions.push_back(
expected_trusted_signal_histogram.Clone());
RunScoreAdWithJavascriptExpectingResult(
CreateScoreAdScript("trustedScoringSignals === null ? 1 : 0", kExtraCode),
1,
{base::StringPrintf("Failed to load %s HTTP status = 404 Not Found.",
kNoComponentSignalsUrl.spec().c_str())},
mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
{}, std::move(expected_real_time_contributions));
}
TEST_F(SellerWorkletRealTimeReportingEnabledTest,
TrustedScoringSignalNetworkErrorScoreAdFailed) {
trusted_scoring_signals_url_ =
GURL("https://url.test/trusted_scoring_signals");
const GURL kNoComponentSignalsUrl = GURL(
"https://url.test/trusted_scoring_signals?hostname=window.test"
"&renderUrls=https%3A%2F%2Frender.url.test%2F");
url_loader_factory_.AddResponse(kNoComponentSignalsUrl.spec(),
std::string(),
net::HTTP_NOT_FOUND);
mojom::RealTimeReportingContribution expected_trusted_signal_histogram(
1024 + auction_worklet::RealTimeReportingPlatformError::
kTrustedScoringSignalsFailure,
1,
std::nullopt);
RealTimeReportingContributions expected_real_time_contributions;
expected_real_time_contributions.push_back(
expected_trusted_signal_histogram.Clone());
RunScoreAdWithJavascriptExpectingResult(
CreateScoreAdScript(""),
0,
{base::StringPrintf("Failed to load %s HTTP status = 404 Not Found.",
kNoComponentSignalsUrl.spec().c_str()),
"https://url.test/ scoreAd() return: Required field 'desirability' "
"is undefined."},
mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
{}, std::move(expected_real_time_contributions));
}
TEST_F(SellerWorkletRealTimeReportingEnabledTest,
TrustedScoringSignalSucceedsNoContributionAdded) {
trusted_scoring_signals_url_ =
GURL("https://url.test/trusted_scoring_signals");
const GURL kSignalsUrl = GURL(
"https://url.test/trusted_scoring_signals?hostname=window.test"
"&renderUrls=https%3A%2F%2Frender.url.test%2F");
AddJsonResponse(&url_loader_factory_, kSignalsUrl,
kTrustedScoringSignalsResponse);
auction_worklet::mojom::RealTimeReportingContribution expected_histogram(
100, 0.5,
std::nullopt);
constexpr char kExtraCode[] = R"(
realTimeReporting.contributeToHistogram({bucket: 100, priorityWeight: 0.5})
)";
RealTimeReportingContributions expected_real_time_contributions;
expected_real_time_contributions.push_back(expected_histogram.Clone());
RunScoreAdWithJavascriptExpectingResult(
CreateScoreAdScript("trustedScoringSignals === null ? 1 : 0", kExtraCode),
0,
{}, mojom::ComponentAuctionModifiedBidParamsPtr(),
std::nullopt,
std::nullopt,
std::nullopt,
mojom::RejectReason::kNotAvailable,
{}, std::move(expected_real_time_contributions));
}
class SellerWorkletCreativeScanningDisabledTest : public SellerWorkletTest {
public:
SellerWorkletCreativeScanningDisabledTest() {
feature_list_.InitAndDisableFeature(
blink::features::kFledgeTrustedSignalsKVv1CreativeScanning);
}
protected:
base::test::ScopedFeatureList feature_list_;
};
TEST_F(SellerWorkletCreativeScanningDisabledTest,
ScoreAdCreativeScanningMetadata) {
send_creative_scanning_metadata_ = true;
creative_scanning_metadata_ = std::nullopt;
RunScoreAdWithReturnValueExpectingResult(
R"('creativeScanningMetadata' in browserSignals ? 4 : 3)", 3);
creative_scanning_metadata_ = "hello";
RunScoreAdWithReturnValueExpectingResult(
R"('creativeScanningMetadata' in browserSignals ? 4 : 3)", 3);
}
}
}