diff --git a/content/browser/interest_group/ad_auction_service_impl.cc b/content/browser/interest_group/ad_auction_service_impl.cc index d0fcbdccc205fa..3691071cddc54d 100644 --- a/content/browser/interest_group/ad_auction_service_impl.cc +++ b/content/browser/interest_group/ad_auction_service_impl.cc @@ -460,6 +460,7 @@ void AdAuctionServiceImpl::OnAuctionComplete( std::vector report_urls, std::vector debug_loss_report_urls, std::vector debug_win_report_urls, + ReportingMetadata ad_beacon_map, std::vector errors) { // Delete the AuctionRunner. Since all arguments are passed by value, they're // all safe to used after this has been done. @@ -487,13 +488,11 @@ void AdAuctionServiceImpl::OnAuctionComplete( return; } DCHECK(winning_group_id); // Should always be present with a render_url - render_url = - GetFrame() - ->GetPage() - .fenced_frame_urls_map() - .AddFencedFrameURLWithInterestGroupInfo( - *render_url, {winning_group_id->owner, winning_group_id->name}, - ad_component_urls); + FencedFrameURLMapping& fenced_frame_urls_map = + GetFrame()->GetPage().fenced_frame_urls_map(); + render_url = fenced_frame_urls_map.AddFencedFrameURLWithInterestGroupInfo( + *render_url, {winning_group_id->owner, winning_group_id->name}, + ad_component_urls, ad_beacon_map); DCHECK(render_url->is_valid()); std::move(callback).Run(render_url); diff --git a/content/browser/interest_group/ad_auction_service_impl.h b/content/browser/interest_group/ad_auction_service_impl.h index 8769d8d321746b..0a32bed30b9492 100644 --- a/content/browser/interest_group/ad_auction_service_impl.h +++ b/content/browser/interest_group/ad_auction_service_impl.h @@ -9,6 +9,7 @@ #include #include "base/containers/unique_ptr_adapters.h" +#include "content/browser/fenced_frame/fenced_frame_url_mapping.h" #include "content/browser/interest_group/auction_runner.h" #include "content/browser/interest_group/auction_worklet_manager.h" #include "content/common/content_export.h" @@ -99,6 +100,7 @@ class CONTENT_EXPORT AdAuctionServiceImpl final std::vector report_urls, std::vector debug_loss_report_urls, std::vector debug_win_report_urls, + ReportingMetadata ad_beacon_map, std::vector errors); InterestGroupManagerImpl& GetInterestGroupManager() const; diff --git a/content/browser/interest_group/auction_runner.cc b/content/browser/interest_group/auction_runner.cc index 5baa03834a346a..b2ee174fd5f2bd 100644 --- a/content/browser/interest_group/auction_runner.cc +++ b/content/browser/interest_group/auction_runner.cc @@ -44,6 +44,7 @@ namespace content { namespace { +using blink::mojom::ReportingDestination; constexpr base::TimeDelta kMaxTimeout = base::Milliseconds(500); // For group freshness metrics. @@ -1065,6 +1066,7 @@ void AuctionRunner::Auction::ReportSellerResult( void AuctionRunner::Auction::OnReportSellerResultComplete( const absl::optional& signals_for_winner, const absl::optional& seller_report_url, + const base::flat_map& seller_ad_beacon_map, const std::vector& errors) { // There should be no other report URLs at this point. DCHECK(report_urls_.empty()); @@ -1075,6 +1077,10 @@ void AuctionRunner::Auction::OnReportSellerResultComplete( // an error if it crashes at this point, failing the auction unnecessarily. seller_worklet_handle_.reset(); + if (!seller_ad_beacon_map.empty()) + ad_beacon_map_.metadata[ReportingDestination::kSeller] = + seller_ad_beacon_map; + if (seller_report_url) { if (!IsUrlValid(*seller_report_url)) { mojo::ReportBadMessage("Invalid seller report URL"); @@ -1140,6 +1146,7 @@ void AuctionRunner::Auction::ReportBidWin( void AuctionRunner::Auction::OnReportBidWinComplete( const absl::optional& bidder_report_url, + const base::flat_map& bidder_ad_beacon_map, const std::vector& errors) { // There should be at most one other report URL at this point. DCHECK_LE(report_urls_.size(), 1u); @@ -1148,6 +1155,10 @@ void AuctionRunner::Auction::OnReportBidWinComplete( // fatal error notification. top_bid_->bid->bid_state->worklet_handle.reset(); + if (!bidder_ad_beacon_map.empty()) + ad_beacon_map_.metadata[ReportingDestination::kBuyer] = + bidder_ad_beacon_map; + if (bidder_report_url) { if (!IsUrlValid(*bidder_report_url)) { mojo::ReportBadMessage("Invalid bidder report URL"); @@ -1178,7 +1189,8 @@ void AuctionRunner::Auction::OnWinningComponentSellerWorkletFatalError( // An error while reloading the worklet to call ReportResult() does not // currently fail the auction. OnReportSellerResultComplete(/*signals_for_winner=*/absl::nullopt, - /*seller_report_url=*/absl::nullopt, errors); + /*seller_report_url=*/absl::nullopt, + /*seller_ad_beacon_map=*/{}, errors); } } @@ -1197,12 +1209,28 @@ void AuctionRunner::Auction::OnWinningBidderWorkletFatalError( } else { // An error while reloading the worklet to call ReportWin() does not // currently fail the auction. - OnReportBidWinComplete(/*bidder_report_url=*/absl::nullopt, errors); + OnReportBidWinComplete(/*bidder_report_url=*/absl::nullopt, + /*bidder_ad_beacon_map=*/{}, errors); } } void AuctionRunner::Auction::OnComponentAuctionReportingPhaseComplete( bool success) { + // Copy ad beacon registry. + DCHECK(top_bid_->bid->auction); + if (top_bid_->bid->auction->ad_beacon_map_.metadata.count( + ReportingDestination::kSeller) > 0) { + ad_beacon_map_.metadata[ReportingDestination::kComponentSeller] = + top_bid_->bid->auction->ad_beacon_map_ + .metadata[ReportingDestination::kSeller]; + } + if (top_bid_->bid->auction->ad_beacon_map_.metadata.count( + ReportingDestination::kBuyer) > 0) { + ad_beacon_map_.metadata[ReportingDestination::kBuyer] = + top_bid_->bid->auction->ad_beacon_map_ + .metadata[ReportingDestination::kBuyer]; + } + // Inherit the success or error from the nested auction. OnReportingPhaseComplete(*top_bid_->bid->auction->final_auction_result_); } @@ -1290,12 +1318,13 @@ void AuctionRunner::FailAuction() { UpdateInterestGroupsPostAuction(); - std::move(callback_).Run( - this, /*render_url=*/absl::nullopt, - /*winning_group_key=*/absl::nullopt, - /*ad_component_urls=*/{}, - /*report_urls=*/{}, std::move(debug_loss_report_urls), - std::move(debug_win_report_urls), auction_.TakeErrors()); + std::move(callback_).Run(this, /*render_url=*/absl::nullopt, + /*winning_group_key=*/absl::nullopt, + /*ad_component_urls=*/{}, + /*report_urls=*/{}, + std::move(debug_loss_report_urls), + std::move(debug_win_report_urls), + /*ad_beacon_map=*/{}, auction_.TakeErrors()); } AuctionRunner::AuctionRunner( @@ -1463,7 +1492,7 @@ void AuctionRunner::OnReportingPhaseComplete(bool success) { this, std::move(winning_group_key), auction_.top_bid()->bid->render_url, auction_.top_bid()->bid->ad_components, auction_.TakeReportUrls(), std::move(debug_loss_report_urls), std::move(debug_win_report_urls), - auction_.TakeErrors()); + auction_.TakeAdBeaconMap(), auction_.TakeErrors()); } void AuctionRunner::UpdateInterestGroupsPostAuction() { diff --git a/content/browser/interest_group/auction_runner.h b/content/browser/interest_group/auction_runner.h index c0f29a059cf00b..d30d51228db43b 100644 --- a/content/browser/interest_group/auction_runner.h +++ b/content/browser/interest_group/auction_runner.h @@ -12,10 +12,12 @@ #include #include "base/callback.h" +#include "base/containers/flat_map.h" #include "base/logging.h" #include "base/memory/raw_ptr.h" #include "base/memory/weak_ptr.h" #include "base/time/time.h" +#include "content/browser/fenced_frame/fenced_frame_url_mapping.h" #include "content/browser/interest_group/auction_worklet_manager.h" #include "content/browser/interest_group/interest_group_storage.h" #include "content/common/content_export.h" @@ -51,6 +53,7 @@ class CONTENT_EXPORT AuctionRunner { url::Origin owner; std::string name; }; + // Invoked when a FLEDGE auction is complete. // // `winning_group_id` owner and name of the winning interest group (if any). @@ -80,6 +83,7 @@ class CONTENT_EXPORT AuctionRunner { std::vector report_urls, std::vector debug_loss_report_urls, std::vector debug_win_report_urls, + ReportingMetadata ad_beacon_map, std::vector errors)>; // Returns true if `origin` is allowed to use the interest group API. Will be @@ -404,6 +408,10 @@ class CONTENT_EXPORT AuctionRunner { void TakeDebugReportUrls(std::vector& debug_win_report_urls, std::vector& debug_loss_report_urls); + // Retrieves the ad beacon map. May only be called once, since it takes + // ownership of the stored ad beacon map. + ReportingMetadata TakeAdBeaconMap() { return std::move(ad_beacon_map_); } + // Retrieves any reporting URLs returned by ReportWin() and ReportResult() // methods. May only be called after an auction has completed successfully. // May only be called once, since it takes ownership of stored reporting @@ -580,11 +588,14 @@ class CONTENT_EXPORT AuctionRunner { void OnReportSellerResultComplete( const absl::optional& signals_for_winner, const absl::optional& seller_report_url, + const base::flat_map& seller_ad_beacon_map, const std::vector& error_msgs); void LoadBidderWorkletToReportBidWin(const std::string& signals_for_winner); void ReportBidWin(const std::string& signals_for_winner); - void OnReportBidWinComplete(const absl::optional& bidder_report_url, - const std::vector& error_msgs); + void OnReportBidWinComplete( + const absl::optional& bidder_report_url, + const base::flat_map& bidder_ad_beacon_map, + const std::vector& error_msgs); // Called when the component SellerWorklet with the bidder that won an // auction has an out-of-band fatal error during the ReportResult() call. @@ -733,6 +744,12 @@ class CONTENT_EXPORT AuctionRunner { // All errors reported by worklets thus far. std::vector errors_; + // Ad Beacon URL mapping generated from reportResult() or reportWin() from + // this auction and its components. Destination is relative to this auction. + // Returned to the caller for it to deal with, so the Auction itself can be + // deleted at the end of the auction. + ReportingMetadata ad_beacon_map_; + // This is set to true if the scoring phase ran and was able to score all // bids that were made (of which there may have been none). This is used to // gate accessors that should return nothing if the entire auction failed diff --git a/content/browser/interest_group/auction_runner_unittest.cc b/content/browser/interest_group/auction_runner_unittest.cc index 23c05986164613..650bf14a13f9f9 100644 --- a/content/browser/interest_group/auction_runner_unittest.cc +++ b/content/browser/interest_group/auction_runner_unittest.cc @@ -49,6 +49,7 @@ #include "third_party/blink/public/common/features.h" #include "third_party/blink/public/common/interest_group/ad_auction_constants.h" #include "third_party/blink/public/common/interest_group/interest_group.h" +#include "third_party/blink/public/mojom/fenced_frame/fenced_frame.mojom-shared.h" using auction_worklet::TestDevToolsAgentClient; @@ -56,6 +57,7 @@ namespace content { namespace { using InterestGroupKey = AuctionRunner::InterestGroupKey; +using blink::mojom::ReportingDestination; const std::string kBidder1Name{"Ad Platform"}; const char kBidder1DebugLossReportUrl[] = @@ -267,6 +269,9 @@ std::string MakeBidScript(const url::Origin& seller, throw new Error(`wrong dataVersion (${browserSignals.dataVersion})`); sendReportTo("https://buyer-reporting.example.com/" + bid); + registerAdBeacon({ + "click": "https://buyer-reporting.example.com/" + 2*bid, + }); } )"; return base::StringPrintf( @@ -455,8 +460,12 @@ std::string MakeDecisionScript( throw new Error("modifiedBid unexpectedly in browserSignals"); if (browserSignals.dataVersion !== undefined) throw new Error(`wrong dataVersion (${browserSignals.dataVersion})`); - if (sendReportUrl) + if (sendReportUrl) { sendReportTo(sendReportUrl + browserSignals.bid); + registerAdBeacon({ + "click": sendReportUrl + 2*browserSignals.bid, + }); + } return browserSignals; } @@ -513,6 +522,9 @@ const char kAuctionScriptRejects2[] = R"( const char kBasicReportResult[] = R"( function reportResult(auctionConfig, browserSignals) { sendReportTo("https://reporting.example.com/" + browserSignals.bid); + registerAdBeacon({ + "click": "https://reporting.example.com/" + 2*browserSignals.bid, + }); return browserSignals; } )"; @@ -711,7 +723,8 @@ class MockBidderWorklet : public auction_worklet::mojom::BidderWorklet { absl::optional report_url = absl::nullopt) { DCHECK(report_win_callback_); std::move(report_win_callback_) - .Run(report_url, /*errors=*/std::vector()); + .Run(report_url, /*ad_beacon_map=*/{}, + /*errors=*/std::vector()); } // Flush the receiver pipe and return whether or not its closed. @@ -871,7 +884,7 @@ class MockSellerWorklet : public auction_worklet::mojom::SellerWorklet { DCHECK(report_result_callback_); std::move(report_result_callback_) .Run(/*signals_for_winner=*/absl::nullopt, std::move(report_url), - errors); + /*ad_beacon_map=*/{}, errors); } void Flush() { receiver_.FlushForTesting(); } @@ -1150,6 +1163,8 @@ class AuctionRunnerTest : public testing::Test, std::vector report_urls; std::vector debug_loss_report_urls; std::vector debug_win_report_urls; + ReportingMetadata ad_beacon_map; + std::vector errors; // Metadata about `bidder1` and `bidder2`, pulled from the @@ -1324,6 +1339,7 @@ class AuctionRunnerTest : public testing::Test, std::vector report_urls, std::vector debug_loss_report_urls, std::vector debug_win_report_urls, + ReportingMetadata ad_beacon_map, std::vector errors) { DCHECK(auction_run_loop_); DCHECK(!auction_complete_); @@ -1344,6 +1360,7 @@ class AuctionRunnerTest : public testing::Test, result_.bidder2_prev_wins.clear(); result_.debug_loss_report_urls = std::move(debug_loss_report_urls); result_.debug_win_report_urls = std::move(debug_win_report_urls); + result_.ad_beacon_map = std::move(ad_beacon_map); // Retrieve bid count and previous wins for kBidder1 (and subsequently // kBidder2). @@ -1997,6 +2014,16 @@ TEST_F(AuctionRunnerTest, Basic) { testing::UnorderedElementsAre( GURL("https://reporting.example.com/2"), GURL("https://buyer-reporting.example.com/2"))); + EXPECT_THAT( + res.ad_beacon_map.metadata, + testing::UnorderedElementsAre( + testing::Pair(ReportingDestination::kSeller, + testing::ElementsAre(testing::Pair( + "click", GURL("https://reporting.example.com/4")))), + testing::Pair( + ReportingDestination::kBuyer, + testing::ElementsAre(testing::Pair( + "click", GURL("https://buyer-reporting.example.com/4")))))); EXPECT_EQ(6, res.bidder1_bid_count); EXPECT_EQ(3u, res.bidder1_prev_wins.size()); EXPECT_EQ(6, res.bidder2_bid_count); @@ -2156,6 +2183,17 @@ TEST_F(AuctionRunnerTest, BasicDebug) { testing::UnorderedElementsAre( GURL("https://reporting.example.com/2"), GURL("https://buyer-reporting.example.com/2"))); + EXPECT_THAT( + result_.ad_beacon_map.metadata, + testing::UnorderedElementsAre( + testing::Pair( + ReportingDestination::kSeller, + testing::ElementsAre(testing::Pair( + "click", GURL("https://reporting.example.com/4")))), + testing::Pair( + ReportingDestination::kBuyer, + testing::ElementsAre(testing::Pair( + "click", GURL("https://buyer-reporting.example.com/4")))))); } } @@ -2211,6 +2249,16 @@ TEST_F(AuctionRunnerTest, PauseBidder) { testing::UnorderedElementsAre( GURL("https://reporting.example.com/2"), GURL("https://buyer-reporting.example.com/2"))); + EXPECT_THAT( + result_.ad_beacon_map.metadata, + testing::UnorderedElementsAre( + testing::Pair(ReportingDestination::kSeller, + testing::ElementsAre(testing::Pair( + "click", GURL("https://reporting.example.com/4")))), + testing::Pair( + ReportingDestination::kBuyer, + testing::ElementsAre(testing::Pair( + "click", GURL("https://buyer-reporting.example.com/4")))))); EXPECT_THAT(result_.errors, testing::ElementsAre()); } @@ -2262,6 +2310,16 @@ TEST_F(AuctionRunnerTest, PauseSeller) { testing::UnorderedElementsAre( GURL("https://reporting.example.com/2"), GURL("https://buyer-reporting.example.com/2"))); + EXPECT_THAT( + result_.ad_beacon_map.metadata, + testing::UnorderedElementsAre( + testing::Pair(ReportingDestination::kSeller, + testing::ElementsAre(testing::Pair( + "click", GURL("https://reporting.example.com/4")))), + testing::Pair( + ReportingDestination::kBuyer, + testing::ElementsAre(testing::Pair( + "click", GURL("https://buyer-reporting.example.com/4")))))); EXPECT_THAT(result_.errors, testing::ElementsAre()); } @@ -2280,6 +2338,20 @@ TEST_F(AuctionRunnerTest, ComponentAuction) { GURL("https://reporting.example.com/2"), GURL("https://component2-report.test/2"), GURL("https://buyer-reporting.example.com/2"))); + EXPECT_THAT( + result_.ad_beacon_map.metadata, + testing::UnorderedElementsAre( + testing::Pair(ReportingDestination::kSeller, + testing::ElementsAre(testing::Pair( + "click", GURL("https://reporting.example.com/4")))), + testing::Pair( + ReportingDestination::kComponentSeller, + testing::ElementsAre(testing::Pair( + "click", GURL("https://component2-report.test/4")))), + testing::Pair( + ReportingDestination::kBuyer, + testing::ElementsAre(testing::Pair( + "click", GURL("https://buyer-reporting.example.com/4")))))); EXPECT_EQ(6, result_.bidder1_bid_count); EXPECT_EQ(3u, result_.bidder1_prev_wins.size()); EXPECT_EQ(6, result_.bidder2_bid_count); @@ -2306,6 +2378,16 @@ TEST_F(AuctionRunnerTest, ComponentAuctionComponentSellersHaveNoBuyers) { testing::UnorderedElementsAre( GURL("https://reporting.example.com/2"), GURL("https://buyer-reporting.example.com/2"))); + EXPECT_THAT( + result_.ad_beacon_map.metadata, + testing::UnorderedElementsAre( + testing::Pair(ReportingDestination::kSeller, + testing::ElementsAre(testing::Pair( + "click", GURL("https://reporting.example.com/4")))), + testing::Pair( + ReportingDestination::kBuyer, + testing::ElementsAre(testing::Pair( + "click", GURL("https://buyer-reporting.example.com/4")))))); EXPECT_EQ(6, result_.bidder1_bid_count); EXPECT_EQ(3u, result_.bidder1_prev_wins.size()); EXPECT_EQ(6, result_.bidder2_bid_count); @@ -2332,6 +2414,16 @@ TEST_F(AuctionRunnerTest, ComponentAuctionTopLeverSellerBidWins) { testing::UnorderedElementsAre( GURL("https://reporting.example.com/2"), GURL("https://buyer-reporting.example.com/2"))); + EXPECT_THAT( + result_.ad_beacon_map.metadata, + testing::UnorderedElementsAre( + testing::Pair(ReportingDestination::kSeller, + testing::ElementsAre(testing::Pair( + "click", GURL("https://reporting.example.com/4")))), + testing::Pair( + ReportingDestination::kBuyer, + testing::ElementsAre(testing::Pair( + "click", GURL("https://buyer-reporting.example.com/4")))))); EXPECT_EQ(6, result_.bidder1_bid_count); EXPECT_EQ(3u, result_.bidder1_prev_wins.size()); EXPECT_EQ(6, result_.bidder2_bid_count); @@ -2359,6 +2451,20 @@ TEST_F(AuctionRunnerTest, ComponentAuctionComponentSellerBidWins) { GURL("https://reporting.example.com/2"), GURL("https://component1-report.test/2"), GURL("https://buyer-reporting.example.com/2"))); + EXPECT_THAT( + result_.ad_beacon_map.metadata, + testing::UnorderedElementsAre( + testing::Pair(ReportingDestination::kSeller, + testing::ElementsAre(testing::Pair( + "click", GURL("https://reporting.example.com/4")))), + testing::Pair( + ReportingDestination::kComponentSeller, + testing::ElementsAre(testing::Pair( + "click", GURL("https://component1-report.test/4")))), + testing::Pair( + ReportingDestination::kBuyer, + testing::ElementsAre(testing::Pair( + "click", GURL("https://buyer-reporting.example.com/4")))))); EXPECT_EQ(6, result_.bidder1_bid_count); EXPECT_EQ(3u, result_.bidder1_prev_wins.size()); EXPECT_EQ(6, result_.bidder2_bid_count); @@ -2403,6 +2509,9 @@ TEST_F(AuctionRunnerTest, ComponentAuctionSharedBuyer) { function reportWin(auctionSignals, perBuyerSignals, sellerSignals, browserSignals) { sendReportTo("https://buyer-reporting.example.com/" + browserSignals.bid); + registerAdBeacon({ + "click": "https://buyer-reporting.example.com/" + 2*browserSignals.bid, + }); } )"; @@ -2418,6 +2527,9 @@ TEST_F(AuctionRunnerTest, ComponentAuctionSharedBuyer) { function reportResult(auctionConfig, browserSignals) { sendReportTo(auctionConfig.seller + "/" + browserSignals.desirability); + registerAdBeacon({ + "click": auctionConfig.seller + "/" + 2*browserSignals.desirability, + }); } )"; @@ -2456,6 +2568,17 @@ TEST_F(AuctionRunnerTest, ComponentAuctionSharedBuyer) { testing::UnorderedElementsAre( GURL("https://adstuff.publisher1.com/22"), GURL("https://buyer-reporting.example.com/2"))); + EXPECT_THAT( + result_.ad_beacon_map.metadata, + testing::UnorderedElementsAre( + testing::Pair( + ReportingDestination::kSeller, + testing::ElementsAre(testing::Pair( + "click", GURL("https://adstuff.publisher1.com/44")))), + testing::Pair( + ReportingDestination::kBuyer, + testing::ElementsAre(testing::Pair( + "click", GURL("https://buyer-reporting.example.com/4")))))); // Bid count should only be incremented by 1. EXPECT_EQ(6, result_.bidder1_bid_count); ASSERT_EQ(4u, result_.bidder1_prev_wins.size()); @@ -2498,6 +2621,21 @@ TEST_F(AuctionRunnerTest, ComponentAuctionSharedBuyer) { GURL("https://adstuff.publisher1.com/23"), GURL("https://component.seller2.test/13"), GURL("https://buyer-reporting.example.com/3"))); + EXPECT_THAT( + result_.ad_beacon_map.metadata, + testing::UnorderedElementsAre( + testing::Pair( + ReportingDestination::kSeller, + testing::ElementsAre(testing::Pair( + "click", GURL("https://adstuff.publisher1.com/46")))), + testing::Pair( + ReportingDestination::kComponentSeller, + testing::ElementsAre(testing::Pair( + "click", GURL("https://component.seller2.test/26")))), + testing::Pair( + ReportingDestination::kBuyer, + testing::ElementsAre(testing::Pair( + "click", GURL("https://buyer-reporting.example.com/6")))))); // Bid count should only be incremented by 1. EXPECT_EQ(6, result_.bidder1_bid_count); ASSERT_EQ(4u, result_.bidder1_prev_wins.size()); @@ -2528,6 +2666,20 @@ TEST_F(AuctionRunnerTest, ComponentAuctionOneComponentTwoBidders) { GURL("https://reporting.example.com/1"), GURL("https://component1-report.test/1"), GURL("https://buyer-reporting.example.com/1"))); + EXPECT_THAT( + result_.ad_beacon_map.metadata, + testing::UnorderedElementsAre( + testing::Pair(ReportingDestination::kSeller, + testing::ElementsAre(testing::Pair( + "click", GURL("https://reporting.example.com/2")))), + testing::Pair( + ReportingDestination::kComponentSeller, + testing::ElementsAre(testing::Pair( + "click", GURL("https://component1-report.test/2")))), + testing::Pair( + ReportingDestination::kBuyer, + testing::ElementsAre(testing::Pair( + "click", GURL("https://buyer-reporting.example.com/2")))))); EXPECT_EQ(6, result_.bidder1_bid_count); ASSERT_EQ(4u, result_.bidder1_prev_wins.size()); EXPECT_EQ(R"({"render_url":"https://ad1.com/","metadata":{"ads": true}})", @@ -2554,6 +2706,9 @@ TEST_F(AuctionRunnerTest, ComponentAuctionNoTopLevelReportResultSignals) { function reportWin(auctionSignals, perBuyerSignals, sellerSignals, browserSignals) { sendReportTo("https://buyer-reporting.example.com/" + browserSignals.bid); + registerAdBeacon({ + "click": "https://buyer-reporting.example.com/" + 2*browserSignals.bid, + }); } )"; @@ -2567,6 +2722,10 @@ TEST_F(AuctionRunnerTest, ComponentAuctionNoTopLevelReportResultSignals) { function reportResult(auctionConfig, browserSignals) { sendReportTo(auctionConfig.seller + "/" + (browserSignals.topLevelSellerSignals === null)); + registerAdBeacon({ + "click": auctionConfig.seller + "/" + + (browserSignals.topLevelSellerSignals === null), + }); } )"; @@ -2579,6 +2738,9 @@ TEST_F(AuctionRunnerTest, ComponentAuctionNoTopLevelReportResultSignals) { function reportResult(auctionConfig, browserSignals) { sendReportTo(auctionConfig.seller + "/" + browserSignals.bid); + registerAdBeacon({ + "click": auctionConfig.seller + "/" + 2 * browserSignals.bid, + }); // Note that there's no return value here. } )"; @@ -2609,6 +2771,21 @@ TEST_F(AuctionRunnerTest, ComponentAuctionNoTopLevelReportResultSignals) { GURL("https://buyer-reporting.example.com/2"), GURL("https://component.seller1.test/true"), GURL("https://adstuff.publisher1.com/2"))); + EXPECT_THAT( + result_.ad_beacon_map.metadata, + testing::UnorderedElementsAre( + testing::Pair( + ReportingDestination::kSeller, + testing::ElementsAre(testing::Pair( + "click", GURL("https://adstuff.publisher1.com/4")))), + testing::Pair( + ReportingDestination::kComponentSeller, + testing::ElementsAre(testing::Pair( + "click", GURL("https://component.seller1.test/true")))), + testing::Pair( + ReportingDestination::kBuyer, + testing::ElementsAre(testing::Pair( + "click", GURL("https://buyer-reporting.example.com/4")))))); CheckHistograms(AuctionRunner::AuctionResult::kSuccess, /*expected_interest_groups=*/1, /*expected_owners=*/1); } @@ -2625,6 +2802,9 @@ TEST_F(AuctionRunnerTest, ComponentAuctionModifiesBid) { function reportWin(auctionSignals, perBuyerSignals, sellerSignals, browserSignals) { sendReportTo("https://buyer-reporting.example.com/" + browserSignals.bid); + registerAdBeacon({ + "click": "https://buyer-reporting.example.com/" + 2 * browserSignals.bid, + }); } )"; @@ -2637,6 +2817,10 @@ TEST_F(AuctionRunnerTest, ComponentAuctionModifiesBid) { function reportResult(auctionConfig, browserSignals) { sendReportTo(auctionConfig.seller + "/" + browserSignals.bid + "_" + browserSignals.modifiedBid); + registerAdBeacon({ + "click": auctionConfig.seller + "/" + 2 * browserSignals.bid + + "_" + browserSignals.modifiedBid, + }); } )"; @@ -2650,6 +2834,9 @@ TEST_F(AuctionRunnerTest, ComponentAuctionModifiesBid) { function reportResult(auctionConfig, browserSignals) { sendReportTo(auctionConfig.seller + "/" + browserSignals.bid); + registerAdBeacon({ + "click": auctionConfig.seller + "/" + 2 * browserSignals.bid, + }); } )"; @@ -2682,6 +2869,21 @@ TEST_F(AuctionRunnerTest, ComponentAuctionModifiesBid) { GURL("https://buyer-reporting.example.com/2"), GURL("https://component.seller1.test/2_3"), GURL("https://adstuff.publisher1.com/3"))); + EXPECT_THAT( + result_.ad_beacon_map.metadata, + testing::UnorderedElementsAre( + testing::Pair( + ReportingDestination::kSeller, + testing::ElementsAre(testing::Pair( + "click", GURL("https://adstuff.publisher1.com/6")))), + testing::Pair( + ReportingDestination::kComponentSeller, + testing::ElementsAre(testing::Pair( + "click", GURL("https://component.seller1.test/4_3")))), + testing::Pair( + ReportingDestination::kBuyer, + testing::ElementsAre(testing::Pair( + "click", GURL("https://buyer-reporting.example.com/4")))))); CheckHistograms(AuctionRunner::AuctionResult::kSuccess, /*expected_interest_groups=*/1, /*expected_owners=*/1); } @@ -2699,6 +2901,7 @@ TEST_F(AuctionRunnerTest, DisallowedSeller) { EXPECT_FALSE(result_.ad_url); EXPECT_TRUE(result_.ad_component_urls.empty()); EXPECT_THAT(result_.report_urls, testing::UnorderedElementsAre()); + EXPECT_TRUE(result_.ad_beacon_map.metadata.empty()); EXPECT_EQ(5, result_.bidder1_bid_count); EXPECT_EQ(3u, result_.bidder1_prev_wins.size()); EXPECT_EQ(5, result_.bidder2_bid_count); @@ -2730,6 +2933,7 @@ TEST_F(AuctionRunnerTest, DisallowedComponentAuctionSeller) { EXPECT_FALSE(result_.ad_url); EXPECT_TRUE(result_.ad_component_urls.empty()); EXPECT_THAT(result_.report_urls, testing::UnorderedElementsAre()); + EXPECT_TRUE(result_.ad_beacon_map.metadata.empty()); EXPECT_EQ(5, result_.bidder1_bid_count); EXPECT_EQ(3u, result_.bidder1_prev_wins.size()); EXPECT_EQ(5, result_.bidder2_bid_count); @@ -2769,6 +2973,20 @@ TEST_F(AuctionRunnerTest, DisallowedComponentAuctionOneSeller) { GURL("https://reporting.example.com/1"), GURL("https://component1-report.test/1"), GURL("https://buyer-reporting.example.com/1"))); + EXPECT_THAT( + result_.ad_beacon_map.metadata, + testing::UnorderedElementsAre( + testing::Pair(ReportingDestination::kSeller, + testing::ElementsAre(testing::Pair( + "click", GURL("https://reporting.example.com/2")))), + testing::Pair( + ReportingDestination::kComponentSeller, + testing::ElementsAre(testing::Pair( + "click", GURL("https://component1-report.test/2")))), + testing::Pair( + ReportingDestination::kBuyer, + testing::ElementsAre(testing::Pair( + "click", GURL("https://buyer-reporting.example.com/2")))))); EXPECT_EQ(6, result_.bidder1_bid_count); ASSERT_EQ(4u, result_.bidder1_prev_wins.size()); EXPECT_EQ(R"({"render_url":"https://ad1.com/","metadata":{"ads": true}})", @@ -2794,6 +3012,7 @@ TEST_F(AuctionRunnerTest, DisallowedBuyers) { EXPECT_FALSE(result_.ad_url); EXPECT_TRUE(result_.ad_component_urls.empty()); EXPECT_THAT(result_.report_urls, testing::UnorderedElementsAre()); + EXPECT_TRUE(result_.ad_beacon_map.metadata.empty()); EXPECT_EQ(5, result_.bidder1_bid_count); EXPECT_EQ(3u, result_.bidder1_prev_wins.size()); EXPECT_EQ(5, result_.bidder2_bid_count); @@ -2835,6 +3054,16 @@ TEST_F(AuctionRunnerTest, DisallowedSingleBuyer) { testing::UnorderedElementsAre( GURL("https://reporting.example.com/1"), GURL("https://buyer-reporting.example.com/1"))); + EXPECT_THAT( + result_.ad_beacon_map.metadata, + testing::UnorderedElementsAre( + testing::Pair(ReportingDestination::kSeller, + testing::ElementsAre(testing::Pair( + "click", GURL("https://reporting.example.com/2")))), + testing::Pair( + ReportingDestination::kBuyer, + testing::ElementsAre(testing::Pair( + "click", GURL("https://buyer-reporting.example.com/2")))))); EXPECT_EQ(6, result_.bidder1_bid_count); ASSERT_EQ(4u, result_.bidder1_prev_wins.size()); EXPECT_EQ(R"({"render_url":"https://ad1.com/","metadata":{"ads": true}})", @@ -2869,6 +3098,7 @@ TEST_F(AuctionRunnerTest, DisallowedComponentAuctionBuyers) { EXPECT_FALSE(result_.ad_url); EXPECT_TRUE(result_.ad_component_urls.empty()); EXPECT_THAT(result_.report_urls, testing::UnorderedElementsAre()); + EXPECT_TRUE(result_.ad_beacon_map.metadata.empty()); EXPECT_EQ(5, result_.bidder1_bid_count); EXPECT_EQ(3u, result_.bidder1_prev_wins.size()); EXPECT_EQ(5, result_.bidder2_bid_count); @@ -2903,6 +3133,20 @@ TEST_F(AuctionRunnerTest, DisallowedComponentAuctionSingleBuyer) { GURL("https://reporting.example.com/1"), GURL("https://component1-report.test/1"), GURL("https://buyer-reporting.example.com/1"))); + EXPECT_THAT( + result_.ad_beacon_map.metadata, + testing::UnorderedElementsAre( + testing::Pair(ReportingDestination::kSeller, + testing::ElementsAre(testing::Pair( + "click", GURL("https://reporting.example.com/2")))), + testing::Pair( + ReportingDestination::kComponentSeller, + testing::ElementsAre(testing::Pair( + "click", GURL("https://component1-report.test/2")))), + testing::Pair( + ReportingDestination::kBuyer, + testing::ElementsAre(testing::Pair( + "click", GURL("https://buyer-reporting.example.com/2")))))); EXPECT_EQ(6, result_.bidder1_bid_count); ASSERT_EQ(4u, result_.bidder1_prev_wins.size()); EXPECT_EQ(R"({"render_url":"https://ad1.com/","metadata":{"ads": true}})", @@ -2978,6 +3222,16 @@ TEST_F(AuctionRunnerTest, OneBidOne404) { testing::UnorderedElementsAre( GURL("https://reporting.example.com/1"), GURL("https://buyer-reporting.example.com/1"))); + EXPECT_THAT( + result_.ad_beacon_map.metadata, + testing::UnorderedElementsAre( + testing::Pair(ReportingDestination::kSeller, + testing::ElementsAre(testing::Pair( + "click", GURL("https://reporting.example.com/2")))), + testing::Pair( + ReportingDestination::kBuyer, + testing::ElementsAre(testing::Pair( + "click", GURL("https://buyer-reporting.example.com/2")))))); EXPECT_EQ(6, res.bidder1_bid_count); ASSERT_EQ(4u, res.bidder1_prev_wins.size()); EXPECT_EQ(R"({"render_url":"https://ad1.com/","metadata":{"ads": true}})", @@ -3021,6 +3275,20 @@ TEST_F(AuctionRunnerTest, ComponentAuctionOneSeller404) { GURL("https://reporting.example.com/1"), GURL("https://component1-report.test/1"), GURL("https://buyer-reporting.example.com/1"))); + EXPECT_THAT( + result_.ad_beacon_map.metadata, + testing::UnorderedElementsAre( + testing::Pair(ReportingDestination::kSeller, + testing::ElementsAre(testing::Pair( + "click", GURL("https://reporting.example.com/2")))), + testing::Pair( + ReportingDestination::kComponentSeller, + testing::ElementsAre(testing::Pair( + "click", GURL("https://component1-report.test/2")))), + testing::Pair( + ReportingDestination::kBuyer, + testing::ElementsAre(testing::Pair( + "click", GURL("https://buyer-reporting.example.com/2")))))); EXPECT_EQ(6, result_.bidder1_bid_count); // The bid send to the failing component seller worklet isn't counted, // regardless of whether the bid completed before the worklet failed to load. @@ -3065,6 +3333,16 @@ TEST_F(AuctionRunnerTest, OneBidOneNotMade) { testing::UnorderedElementsAre( GURL("https://reporting.example.com/1"), GURL("https://buyer-reporting.example.com/1"))); + EXPECT_THAT( + result_.ad_beacon_map.metadata, + testing::UnorderedElementsAre( + testing::Pair(ReportingDestination::kSeller, + testing::ElementsAre(testing::Pair( + "click", GURL("https://reporting.example.com/2")))), + testing::Pair( + ReportingDestination::kBuyer, + testing::ElementsAre(testing::Pair( + "click", GURL("https://buyer-reporting.example.com/2")))))); EXPECT_EQ(6, res.bidder1_bid_count); ASSERT_EQ(4u, res.bidder1_prev_wins.size()); EXPECT_EQ(R"({"render_url":"https://ad1.com/","metadata":{"ads": true}})", @@ -3098,6 +3376,7 @@ TEST_F(AuctionRunnerTest, NoBids) { EXPECT_FALSE(res.ad_url); EXPECT_TRUE(res.ad_component_urls.empty()); EXPECT_THAT(res.report_urls, testing::UnorderedElementsAre()); + EXPECT_TRUE(res.ad_beacon_map.metadata.empty()); EXPECT_EQ(5, res.bidder1_bid_count); EXPECT_EQ(3u, res.bidder1_prev_wins.size()); EXPECT_EQ(5, res.bidder2_bid_count); @@ -3135,6 +3414,7 @@ TEST_F(AuctionRunnerTest, NoBidMadeByScript) { EXPECT_FALSE(res.ad_url); EXPECT_TRUE(res.ad_component_urls.empty()); EXPECT_THAT(res.report_urls, testing::UnorderedElementsAre()); + EXPECT_TRUE(res.ad_beacon_map.metadata.empty()); EXPECT_EQ(5, res.bidder1_bid_count); EXPECT_EQ(3u, res.bidder1_prev_wins.size()); EXPECT_EQ(5, res.bidder2_bid_count); @@ -3180,6 +3460,7 @@ TEST_F(AuctionRunnerTest, SellerRejectsAll) { EXPECT_FALSE(res.ad_url); EXPECT_TRUE(res.ad_component_urls.empty()); EXPECT_THAT(res.report_urls, testing::UnorderedElementsAre()); + EXPECT_TRUE(res.ad_beacon_map.metadata.empty()); EXPECT_EQ(6, res.bidder1_bid_count); EXPECT_EQ(3u, res.bidder1_prev_wins.size()); EXPECT_EQ(6, res.bidder2_bid_count); @@ -3225,6 +3506,16 @@ TEST_F(AuctionRunnerTest, SellerRejectsOne) { testing::UnorderedElementsAre( GURL("https://reporting.example.com/1"), GURL("https://buyer-reporting.example.com/1"))); + EXPECT_THAT( + result_.ad_beacon_map.metadata, + testing::UnorderedElementsAre( + testing::Pair(ReportingDestination::kSeller, + testing::ElementsAre(testing::Pair( + "click", GURL("https://reporting.example.com/2")))), + testing::Pair( + ReportingDestination::kBuyer, + testing::ElementsAre(testing::Pair( + "click", GURL("https://buyer-reporting.example.com/2")))))); EXPECT_EQ(6, res.bidder1_bid_count); ASSERT_EQ(4u, res.bidder1_prev_wins.size()); EXPECT_EQ(R"({"render_url":"https://ad1.com/","metadata":{"ads": true}})", @@ -3246,6 +3537,7 @@ TEST_F(AuctionRunnerTest, NoSellerScript) { EXPECT_FALSE(res.ad_url); EXPECT_TRUE(res.ad_component_urls.empty()); EXPECT_THAT(res.report_urls, testing::UnorderedElementsAre()); + EXPECT_TRUE(res.ad_beacon_map.metadata.empty()); EXPECT_EQ(0, url_loader_factory_.NumPending()); EXPECT_EQ(5, res.bidder1_bid_count); @@ -3292,6 +3584,16 @@ TEST_F(AuctionRunnerTest, NoTrustedBiddingSignals) { testing::UnorderedElementsAre( GURL("https://reporting.example.com/2"), GURL("https://buyer-reporting.example.com/2"))); + EXPECT_THAT( + result_.ad_beacon_map.metadata, + testing::UnorderedElementsAre( + testing::Pair(ReportingDestination::kSeller, + testing::ElementsAre(testing::Pair( + "click", GURL("https://reporting.example.com/4")))), + testing::Pair( + ReportingDestination::kBuyer, + testing::ElementsAre(testing::Pair( + "click", GURL("https://buyer-reporting.example.com/4")))))); EXPECT_EQ(6, res.bidder1_bid_count); EXPECT_EQ(3u, res.bidder1_prev_wins.size()); EXPECT_EQ(6, res.bidder2_bid_count); @@ -3333,6 +3635,16 @@ TEST_F(AuctionRunnerTest, TrustedBiddingSignals404) { testing::UnorderedElementsAre( GURL("https://reporting.example.com/2"), GURL("https://buyer-reporting.example.com/2"))); + EXPECT_THAT( + result_.ad_beacon_map.metadata, + testing::UnorderedElementsAre( + testing::Pair(ReportingDestination::kSeller, + testing::ElementsAre(testing::Pair( + "click", GURL("https://reporting.example.com/4")))), + testing::Pair( + ReportingDestination::kBuyer, + testing::ElementsAre(testing::Pair( + "click", GURL("https://buyer-reporting.example.com/4")))))); EXPECT_EQ(6, res.bidder1_bid_count); EXPECT_EQ(3u, res.bidder1_prev_wins.size()); EXPECT_EQ(6, res.bidder2_bid_count); @@ -3382,6 +3694,12 @@ TEST_F(AuctionRunnerTest, NoReportResultUrl) { res.ad_component_urls); EXPECT_THAT(res.report_urls, testing::UnorderedElementsAre(GURL( "https://buyer-reporting.example.com/2"))); + EXPECT_THAT( + result_.ad_beacon_map.metadata, + testing::UnorderedElementsAre(testing::Pair( + ReportingDestination::kBuyer, + testing::ElementsAre(testing::Pair( + "click", GURL("https://buyer-reporting.example.com/4")))))); EXPECT_EQ(6, res.bidder1_bid_count); EXPECT_EQ(3u, res.bidder1_prev_wins.size()); EXPECT_EQ(6, res.bidder2_bid_count); @@ -3425,6 +3743,11 @@ TEST_F(AuctionRunnerTest, NoReportWinUrl) { res.ad_component_urls); EXPECT_THAT(res.report_urls, testing::UnorderedElementsAre( GURL("https://reporting.example.com/2"))); + EXPECT_THAT(result_.ad_beacon_map.metadata, + testing::UnorderedElementsAre(testing::Pair( + ReportingDestination::kSeller, + testing::ElementsAre(testing::Pair( + "click", GURL("https://reporting.example.com/4")))))); EXPECT_EQ(6, res.bidder1_bid_count); EXPECT_EQ(3u, res.bidder1_prev_wins.size()); EXPECT_EQ(6, res.bidder2_bid_count); @@ -3467,6 +3790,7 @@ TEST_F(AuctionRunnerTest, NeitherReportUrl) { EXPECT_EQ(std::vector{GURL("https://ad2.com-component1.com")}, res.ad_component_urls); EXPECT_THAT(res.report_urls, testing::UnorderedElementsAre()); + EXPECT_TRUE(res.ad_beacon_map.metadata.empty()); EXPECT_EQ(6, res.bidder1_bid_count); EXPECT_EQ(3u, res.bidder1_prev_wins.size()); EXPECT_EQ(6, res.bidder2_bid_count); @@ -3517,6 +3841,7 @@ function scoreAd(adMetadata, bid, auctionConfig, trustedScoringSignals, res.ad_component_urls); EXPECT_THAT(res.report_urls, testing::UnorderedElementsAre(GURL( "https://seller.signals.were.null.test/"))); + EXPECT_TRUE(res.ad_beacon_map.metadata.empty()); EXPECT_EQ(6, res.bidder1_bid_count); EXPECT_EQ(3u, res.bidder1_prev_wins.size()); EXPECT_EQ(6, res.bidder2_bid_count); @@ -3574,6 +3899,9 @@ function scoreAd(adMetadata, bid, auctionConfig, trustedScoringSignals, function reportResult(auctionConfig, browserSignals) { sendReportTo("https://reporting.example.com/" + browserSignals.bid); + registerAdBeacon({ + "click": "https://reporting.example.com/" + 2 * browserSignals.bid, + }); if (browserSignals.dataVersion !== 2) { throw new Error(`wrong dataVersion (${browserSignals.dataVersion})`); } @@ -3604,6 +3932,16 @@ function reportResult(auctionConfig, browserSignals) { testing::UnorderedElementsAre( GURL("https://reporting.example.com/1"), GURL("https://buyer-reporting.example.com/1"))); + EXPECT_THAT( + result_.ad_beacon_map.metadata, + testing::UnorderedElementsAre( + testing::Pair(ReportingDestination::kSeller, + testing::ElementsAre(testing::Pair( + "click", GURL("https://reporting.example.com/2")))), + testing::Pair( + ReportingDestination::kBuyer, + testing::ElementsAre(testing::Pair( + "click", GURL("https://buyer-reporting.example.com/2")))))); EXPECT_EQ(6, result_.bidder1_bid_count); EXPECT_EQ(4u, result_.bidder1_prev_wins.size()); EXPECT_EQ(R"({"render_url":"https://ad1.com/","metadata":{"ads": true}})", @@ -3746,6 +4084,18 @@ TEST_F(AuctionRunnerTest, ProcessManagerBlocksWorkletCreation) { testing::UnorderedElementsAre( GURL("https://reporting.example.com/2"), GURL("https://buyer-reporting.example.com/2"))); + EXPECT_THAT( + result_.ad_beacon_map.metadata, + testing::UnorderedElementsAre( + testing::Pair( + ReportingDestination::kSeller, + testing::ElementsAre(testing::Pair( + "click", GURL("https://reporting.example.com/4")))), + testing::Pair( + ReportingDestination::kBuyer, + testing::ElementsAre(testing::Pair( + "click", + GURL("https://buyer-reporting.example.com/4")))))); EXPECT_EQ(6, result_.bidder1_bid_count); EXPECT_EQ(3u, result_.bidder1_prev_wins.size()); EXPECT_EQ(6, result_.bidder2_bid_count); @@ -3906,6 +4256,22 @@ TEST_F(AuctionRunnerTest, ComponentAuctionProcessManagerBlocksWorkletCreation) { GURL("https://reporting.example.com/2"), GURL("https://component2-report.test/2"), GURL("https://buyer-reporting.example.com/2"))); + EXPECT_THAT( + result_.ad_beacon_map.metadata, + testing::UnorderedElementsAre( + testing::Pair( + ReportingDestination::kSeller, + testing::ElementsAre(testing::Pair( + "click", GURL("https://reporting.example.com/4")))), + testing::Pair( + ReportingDestination::kComponentSeller, + testing::ElementsAre(testing::Pair( + "click", GURL("https://component2-report.test/4")))), + testing::Pair( + ReportingDestination::kBuyer, + testing::ElementsAre(testing::Pair( + "click", + GURL("https://buyer-reporting.example.com/4")))))); EXPECT_THAT(result_.errors, testing::ElementsAre()); CheckHistograms(AuctionRunner::AuctionResult::kSuccess, /*expected_interest_groups=*/2, /*expected_owners=*/2); @@ -4037,6 +4403,20 @@ TEST_F(AuctionRunnerTest, GURL("https://reporting.example.com/2"), GURL("https://component2-report.test/2"), GURL("https://buyer-reporting.example.com/2"))); + EXPECT_THAT( + result_.ad_beacon_map.metadata, + testing::UnorderedElementsAre( + testing::Pair(ReportingDestination::kSeller, + testing::ElementsAre(testing::Pair( + "click", GURL("https://reporting.example.com/4")))), + testing::Pair( + ReportingDestination::kComponentSeller, + testing::ElementsAre(testing::Pair( + "click", GURL("https://component2-report.test/4")))), + testing::Pair( + ReportingDestination::kBuyer, + testing::ElementsAre(testing::Pair( + "click", GURL("https://buyer-reporting.example.com/4")))))); EXPECT_THAT(result_.errors, testing::UnorderedElementsAre( "Failed to load https://component.seller1.test/foo.js HTTP " @@ -4124,6 +4504,7 @@ TEST_F(AuctionRunnerTest, ReusedBidderWorkletBatchesSignalsRequests) { EXPECT_EQ(GURL("https://ad1.com/"), result_.ad_url); EXPECT_TRUE(result_.ad_component_urls.empty()); EXPECT_THAT(result_.report_urls, testing::UnorderedElementsAre()); + EXPECT_TRUE(result_.ad_beacon_map.metadata.empty()); EXPECT_THAT(result_.errors, testing::ElementsAre()); } @@ -4174,6 +4555,7 @@ TEST_F(AuctionRunnerTest, AllBiddersCrashBeforeBidding) { EXPECT_FALSE(result_.ad_url); EXPECT_TRUE(result_.ad_component_urls.empty()); EXPECT_THAT(result_.report_urls, testing::UnorderedElementsAre()); + EXPECT_TRUE(result_.ad_beacon_map.metadata.empty()); EXPECT_EQ(5, result_.bidder1_bid_count); EXPECT_EQ(3u, result_.bidder1_prev_wins.size()); EXPECT_EQ(5, result_.bidder2_bid_count); @@ -4276,6 +4658,7 @@ TEST_F(AuctionRunnerTest, BidderCrashBeforeBidding) { EXPECT_EQ(GURL("https://ad2.com/"), result_.ad_url); EXPECT_TRUE(result_.ad_component_urls.empty()); EXPECT_THAT(result_.report_urls, testing::UnorderedElementsAre()); + EXPECT_TRUE(result_.ad_beacon_map.metadata.empty()); EXPECT_EQ(5, result_.bidder1_bid_count); EXPECT_EQ(3u, result_.bidder1_prev_wins.size()); EXPECT_EQ(6, result_.bidder2_bid_count); @@ -4353,6 +4736,7 @@ TEST_F(AuctionRunnerTest, WinningBidderCrashWhileReporting) { EXPECT_FALSE(result_.ad_url); EXPECT_TRUE(result_.ad_component_urls.empty()); EXPECT_THAT(result_.report_urls, testing::UnorderedElementsAre()); + EXPECT_TRUE(result_.ad_beacon_map.metadata.empty()); EXPECT_EQ(6, result_.bidder1_bid_count); EXPECT_EQ(3u, result_.bidder1_prev_wins.size()); EXPECT_EQ(6, result_.bidder2_bid_count); @@ -4478,6 +4862,7 @@ TEST_F(AuctionRunnerTest, SellerCrash) { EXPECT_FALSE(result_.ad_url); EXPECT_TRUE(result_.ad_component_urls.empty()); EXPECT_THAT(result_.report_urls, testing::UnorderedElementsAre()); + EXPECT_TRUE(result_.ad_beacon_map.metadata.empty()); if (crash_phase != CrashPhase::kReportResult) { EXPECT_EQ(5, result_.bidder1_bid_count); EXPECT_EQ(3u, result_.bidder1_prev_wins.size()); @@ -5031,6 +5416,7 @@ TEST_F(AuctionRunnerTest, NullAdComponents) { EXPECT_EQ(GURL("https://ad1.com/"), result_.ad_url); EXPECT_TRUE(result_.ad_component_urls.empty()); EXPECT_THAT(result_.report_urls, testing::UnorderedElementsAre()); + EXPECT_TRUE(result_.ad_beacon_map.metadata.empty()); EXPECT_EQ(6, result_.bidder1_bid_count); ASSERT_EQ(4u, result_.bidder1_prev_wins.size()); EXPECT_EQ(R"({"render_url":"https://ad1.com/","metadata":{"ads": true}})", @@ -5051,6 +5437,7 @@ TEST_F(AuctionRunnerTest, NullAdComponents) { EXPECT_FALSE(result_.ad_url); EXPECT_TRUE(result_.ad_component_urls.empty()); EXPECT_THAT(result_.report_urls, testing::UnorderedElementsAre()); + EXPECT_TRUE(result_.ad_beacon_map.metadata.empty()); EXPECT_EQ(5, result_.bidder1_bid_count); EXPECT_EQ(3u, result_.bidder1_prev_wins.size()); EXPECT_THAT(result_.errors, testing::ElementsAre()); @@ -5119,6 +5506,7 @@ TEST_F(AuctionRunnerTest, AdComponentsLimit) { EXPECT_EQ(GURL("https://ad1.com/"), result_.ad_url); EXPECT_EQ(ad_component_urls, result_.ad_component_urls); EXPECT_THAT(result_.report_urls, testing::UnorderedElementsAre()); + EXPECT_TRUE(result_.ad_beacon_map.metadata.empty()); EXPECT_EQ(6, result_.bidder1_bid_count); ASSERT_EQ(4u, result_.bidder1_prev_wins.size()); EXPECT_EQ(R"({"render_url":"https://ad1.com/","metadata":{"ads": true}})", @@ -5139,6 +5527,7 @@ TEST_F(AuctionRunnerTest, AdComponentsLimit) { EXPECT_FALSE(result_.ad_url); EXPECT_TRUE(result_.ad_component_urls.empty()); EXPECT_THAT(result_.report_urls, testing::UnorderedElementsAre()); + EXPECT_TRUE(result_.ad_beacon_map.metadata.empty()); EXPECT_EQ(5, result_.bidder1_bid_count); EXPECT_EQ(3u, result_.bidder1_prev_wins.size()); EXPECT_THAT(result_.errors, testing::ElementsAre()); @@ -5288,6 +5677,7 @@ TEST_F(AuctionRunnerTest, BadBid) { EXPECT_FALSE(result_.ad_url); EXPECT_TRUE(result_.ad_component_urls.empty()); EXPECT_THAT(result_.report_urls, testing::UnorderedElementsAre()); + EXPECT_TRUE(result_.ad_beacon_map.metadata.empty()); EXPECT_EQ(5, result_.bidder1_bid_count); EXPECT_EQ(3u, result_.bidder1_prev_wins.size()); EXPECT_EQ(5, result_.bidder2_bid_count); @@ -5341,6 +5731,7 @@ TEST_F(AuctionRunnerTest, BadSellerReportUrl) { EXPECT_FALSE(result_.ad_url); EXPECT_TRUE(result_.ad_component_urls.empty()); EXPECT_THAT(result_.report_urls, testing::UnorderedElementsAre()); + EXPECT_TRUE(result_.ad_beacon_map.metadata.empty()); EXPECT_EQ(6, result_.bidder1_bid_count); EXPECT_EQ(3u, result_.bidder1_prev_wins.size()); EXPECT_EQ(5, result_.bidder2_bid_count); @@ -5425,6 +5816,7 @@ TEST_F(AuctionRunnerTest, BadComponentSellerReportUrl) { EXPECT_FALSE(result_.ad_url); EXPECT_TRUE(result_.ad_component_urls.empty()); EXPECT_THAT(result_.report_urls, testing::UnorderedElementsAre()); + EXPECT_TRUE(result_.ad_beacon_map.metadata.empty()); EXPECT_EQ(6, result_.bidder1_bid_count); EXPECT_EQ(3u, result_.bidder1_prev_wins.size()); EXPECT_EQ(5, result_.bidder2_bid_count); @@ -5481,6 +5873,7 @@ TEST_F(AuctionRunnerTest, BadBidderReportUrl) { EXPECT_FALSE(result_.ad_url); EXPECT_TRUE(result_.ad_component_urls.empty()); EXPECT_THAT(result_.report_urls, testing::UnorderedElementsAre()); + EXPECT_TRUE(result_.ad_beacon_map.metadata.empty()); EXPECT_EQ(6, result_.bidder1_bid_count); EXPECT_EQ(3u, result_.bidder1_prev_wins.size()); EXPECT_EQ(5, result_.bidder2_bid_count); @@ -5538,6 +5931,7 @@ TEST_F(AuctionRunnerTest, DestroyBidderWorkletWithoutBid) { EXPECT_EQ(GURL("https://ad2.com/"), result_.ad_url); EXPECT_TRUE(result_.ad_component_urls.empty()); EXPECT_THAT(result_.report_urls, testing::UnorderedElementsAre()); + EXPECT_TRUE(result_.ad_beacon_map.metadata.empty()); EXPECT_EQ(5, result_.bidder1_bid_count); EXPECT_EQ(3u, result_.bidder1_prev_wins.size()); EXPECT_EQ(6, result_.bidder2_bid_count); @@ -5642,6 +6036,7 @@ TEST_F(AuctionRunnerTest, Tie) { } EXPECT_THAT(result_.report_urls, testing::UnorderedElementsAre()); + EXPECT_TRUE(result_.ad_beacon_map.metadata.empty()); EXPECT_EQ(6, result_.bidder1_bid_count); EXPECT_EQ(6, result_.bidder2_bid_count); EXPECT_THAT(result_.errors, testing::ElementsAre()); diff --git a/content/services/auction_worklet/BUILD.gn b/content/services/auction_worklet/BUILD.gn index 96f033f300964e..6bd8872e778069 100644 --- a/content/services/auction_worklet/BUILD.gn +++ b/content/services/auction_worklet/BUILD.gn @@ -50,6 +50,8 @@ source_set("auction_worklet") { "debug_command_queue.h", "for_debugging_only_bindings.cc", "for_debugging_only_bindings.h", + "register_ad_beacon_bindings.cc", + "register_ad_beacon_bindings.h", "report_bindings.cc", "report_bindings.h", "seller_worklet.cc", diff --git a/content/services/auction_worklet/bidder_worklet.cc b/content/services/auction_worklet/bidder_worklet.cc index 92f078df89e76c..13a129722c5cbf 100644 --- a/content/services/auction_worklet/bidder_worklet.cc +++ b/content/services/auction_worklet/bidder_worklet.cc @@ -22,6 +22,7 @@ #include "content/services/auction_worklet/auction_v8_helper.h" #include "content/services/auction_worklet/for_debugging_only_bindings.h" #include "content/services/auction_worklet/public/mojom/auction_worklet_service.mojom.h" +#include "content/services/auction_worklet/register_ad_beacon_bindings.h" #include "content/services/auction_worklet/report_bindings.h" #include "content/services/auction_worklet/set_bid_bindings.h" #include "content/services/auction_worklet/trusted_signals.h" @@ -326,6 +327,8 @@ void BidderWorklet::V8State::ReportWin( v8::Local global_template = v8::ObjectTemplate::New(isolate); ReportBindings report_bindings(v8_helper_.get(), global_template); + RegisterAdBeaconBindings register_ad_beacon_bindings(v8_helper_.get(), + global_template); // Short lived context, to avoid leaking data at global scope between either // repeated calls to this worklet, or to calls to any other worklet. @@ -338,9 +341,9 @@ void BidderWorklet::V8State::ReportWin( !AppendJsonValueOrNull(v8_helper_.get(), context, per_buyer_signals_json, &args) || !v8_helper_->AppendJsonValue(context, seller_signals_json, &args)) { - PostReportWinCallbackToUserThread(std::move(callback), - absl::nullopt /* report_url */, - std::vector() /* errors */); + PostReportWinCallbackToUserThread( + std::move(callback), absl::nullopt /* report_url */, + /*ad_beacon_map=*/{}, std::vector() /* errors */); return; } @@ -364,9 +367,9 @@ void BidderWorklet::V8State::ReportWin( (bidding_signals_data_version.has_value() && !browser_signals_dict.Set("dataVersion", bidding_signals_data_version.value()))) { - PostReportWinCallbackToUserThread(std::move(callback), - absl::nullopt /* report_url */, - std::vector() /* errors */); + PostReportWinCallbackToUserThread( + std::move(callback), absl::nullopt /* report_url */, + /*ad_beacon_map=*/{}, std::vector() /* errors */); return; } args.push_back(browser_signals); @@ -381,16 +384,17 @@ void BidderWorklet::V8State::ReportWin( "reportWin", args, /*script_timeout=*/absl::nullopt, errors_out) .IsEmpty()) { - PostReportWinCallbackToUserThread(std::move(callback), - absl::nullopt /* report_url */, - std::move(errors_out)); + PostReportWinCallbackToUserThread( + std::move(callback), absl::nullopt /* report_url */, + /*ad_beacon_map=*/{}, std::move(errors_out)); return; } // This covers both the case where a report URL was provided, and the case one // was not. PostReportWinCallbackToUserThread( - std::move(callback), report_bindings.report_url(), std::move(errors_out)); + std::move(callback), report_bindings.report_url(), + register_ad_beacon_bindings.TakeAdBeaconMap(), std::move(errors_out)); } void BidderWorklet::V8State::GenerateBid( @@ -640,11 +644,12 @@ void BidderWorklet::V8State::PostResumeToUserThread( void BidderWorklet::V8State::PostReportWinCallbackToUserThread( ReportWinCallbackInternal callback, const absl::optional& report_url, + base::flat_map ad_beacon_map, std::vector errors) { DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_); user_thread_->PostTask( FROM_HERE, base::BindOnce(std::move(callback), std::move(report_url), - std::move(errors))); + std::move(ad_beacon_map), std::move(errors))); } void BidderWorklet::V8State::PostErrorBidCallbackToUserThread( @@ -853,11 +858,13 @@ void BidderWorklet::DeliverBidCallbackOnUserThread( void BidderWorklet::DeliverReportWinOnUserThread( ReportWinTaskList::iterator task, absl::optional report_url, + base::flat_map ad_beacon_map, std::vector errors) { DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_); errors.insert(errors.end(), load_code_error_msgs_.begin(), load_code_error_msgs_.end()); - std::move(task->callback).Run(std::move(report_url), errors); + std::move(task->callback) + .Run(std::move(report_url), std::move(ad_beacon_map), errors); report_win_tasks_.erase(task); } diff --git a/content/services/auction_worklet/bidder_worklet.h b/content/services/auction_worklet/bidder_worklet.h index 81f3ade4d418ba..70c5a204693d0e 100644 --- a/content/services/auction_worklet/bidder_worklet.h +++ b/content/services/auction_worklet/bidder_worklet.h @@ -12,6 +12,7 @@ #include #include "base/callback.h" +#include "base/containers/flat_map.h" #include "base/containers/unique_ptr_adapters.h" #include "base/memory/scoped_refptr.h" #include "base/time/time.h" @@ -192,6 +193,7 @@ class BidderWorklet : public mojom::BidderWorklet { std::vector error_msgs)>; using ReportWinCallbackInternal = base::OnceCallback report_url, + base::flat_map ad_beacon_map, std::vector errors)>; void ReportWin(const std::string& interest_group_name, @@ -231,6 +233,7 @@ class BidderWorklet : public mojom::BidderWorklet { void PostReportWinCallbackToUserThread( ReportWinCallbackInternal callback, const absl::optional& report_url, + base::flat_map ad_beacon_map, std::vector errors); void PostErrorBidCallbackToUserThread( @@ -300,9 +303,11 @@ class BidderWorklet : public mojom::BidderWorklet { // Invokes the `callback` of `task` with the provided values, and removes // `task` from `report_win_tasks_`. - void DeliverReportWinOnUserThread(ReportWinTaskList::iterator task, - absl::optional report_url, - std::vector errors); + void DeliverReportWinOnUserThread( + ReportWinTaskList::iterator task, + absl::optional report_url, + base::flat_map ad_beacon_map, + std::vector errors); // Returns true if unpaused and the script and WASM helper (if needed) have // loaded. diff --git a/content/services/auction_worklet/bidder_worklet_unittest.cc b/content/services/auction_worklet/bidder_worklet_unittest.cc index 285e299e2781c1..a67832688edf8e 100644 --- a/content/services/auction_worklet/bidder_worklet_unittest.cc +++ b/content/services/auction_worklet/bidder_worklet_unittest.cc @@ -239,11 +239,13 @@ class BidderWorkletTest : public testing::Test { void RunReportWinWithFunctionBodyExpectingResult( const std::string& function_body, const absl::optional& expected_report_url, + const base::flat_map& expected_ad_beacon_map = + base::flat_map(), const std::vector& expected_errors = std::vector()) { RunReportWinWithJavascriptExpectingResult( CreateReportWinScript(function_body), expected_report_url, - expected_errors); + expected_ad_beacon_map, expected_errors); } // Configures `url_loader_factory_` to return a reportWin() script with the @@ -251,12 +253,15 @@ class BidderWorkletTest : public testing::Test { void RunReportWinWithJavascriptExpectingResult( const std::string& javascript, const absl::optional& expected_report_url, + const base::flat_map& expected_ad_beacon_map = + base::flat_map(), const std::vector& expected_errors = std::vector()) { SCOPED_TRACE(javascript); AddJavascriptResponse(&url_loader_factory_, interest_group_bidding_url_, javascript); - RunReportWinExpectingResult(expected_report_url, expected_errors); + RunReportWinExpectingResult(expected_report_url, expected_ad_beacon_map, + expected_errors); } // Runs reportWin() on an already loaded worklet, verifies the return @@ -265,6 +270,7 @@ class BidderWorkletTest : public testing::Test { void RunReportWinExpectingResultAsync( mojom::BidderWorklet* bidder_worklet, const absl::optional& expected_report_url, + const base::flat_map& expected_ad_beacon_map, const std::vector& expected_errors, base::OnceClosure done_closure) { bidder_worklet->ReportWin( @@ -274,21 +280,27 @@ class BidderWorkletTest : public testing::Test { data_version_.value_or(0), data_version_.has_value(), base::BindOnce( [](const absl::optional& expected_report_url, + const base::flat_map& expected_ad_beacon_map, const std::vector& expected_errors, base::OnceClosure done_closure, const absl::optional& report_url, + const base::flat_map& ad_beacon_map, const std::vector& errors) { EXPECT_EQ(expected_report_url, report_url); EXPECT_EQ(expected_errors, errors); + EXPECT_EQ(expected_ad_beacon_map, ad_beacon_map); std::move(done_closure).Run(); }, - expected_report_url, expected_errors, std::move(done_closure))); + expected_report_url, expected_ad_beacon_map, expected_errors, + std::move(done_closure))); } // Loads and runs a reportWin() with the provided return line, expecting the // supplied result. void RunReportWinExpectingResult( const absl::optional& expected_report_url, + const base::flat_map& expected_ad_beacon_map = + base::flat_map(), const std::vector& expected_errors = std::vector()) { auto bidder_worklet = CreateWorklet(); @@ -296,7 +308,8 @@ class BidderWorkletTest : public testing::Test { base::RunLoop run_loop; RunReportWinExpectingResultAsync(bidder_worklet.get(), expected_report_url, - expected_errors, run_loop.QuitClosure()); + expected_ad_beacon_map, expected_errors, + run_loop.QuitClosure()); run_loop.Run(); } @@ -2301,6 +2314,7 @@ TEST_F(BidderWorkletTest, WasmReportWin) { data_version_.value_or(0), data_version_.has_value(), base::BindLambdaForTesting( [&run_loop](const absl::optional& report_url, + const base::flat_map& ad_beacon_map, const std::vector& errors) { run_loop.Quit(); })); @@ -2680,22 +2694,25 @@ TEST_F(BidderWorkletTest, ReportWin) { RunReportWinWithFunctionBodyExpectingResult( R"(sendReportTo("http://http.not.allowed.test"))", /*expected_report_url =*/absl::nullopt, + /*expected_ad_beacon_map=*/{}, {"https://url.test/:10 Uncaught TypeError: sendReportTo must be passed a " "valid HTTPS url."}); RunReportWinWithFunctionBodyExpectingResult( R"(sendReportTo("file:///file.not.allowed.test"))", /*expected_report_url =*/absl::nullopt, + /*expected_ad_beacon_map=*/{}, {"https://url.test/:10 Uncaught TypeError: sendReportTo must be passed a " "valid HTTPS url."}); RunReportWinWithFunctionBodyExpectingResult( R"(sendReportTo(""))", /*expected_report_url =*/absl::nullopt, + /*expected_ad_beacon_map=*/{}, {"https://url.test/:10 Uncaught TypeError: sendReportTo must be passed a " "valid HTTPS url."}); RunReportWinWithFunctionBodyExpectingResult( R"(sendReportTo("https://foo.test");sendReportTo("https://foo.test"))", - /*expected_report_url =*/absl::nullopt, + /*expected_report_url =*/absl::nullopt, /*expected_ad_beacon_map=*/{}, {"https://url.test/:10 Uncaught TypeError: sendReportTo may be called at " "most once."}); } @@ -2733,6 +2750,7 @@ TEST_F(BidderWorkletTest, DeleteBeforeReportWinCallback) { browser_signal_seller_origin_, browser_signal_top_level_seller_origin_, data_version_.value_or(0), data_version_.has_value(), base::BindOnce([](const absl::optional& report_url, + const base::flat_map& ad_beacon_map, const std::vector& errors) { ADD_FAILURE() << "Callback should not be invoked since worklet deleted"; })); @@ -2774,6 +2792,7 @@ TEST_F(BidderWorkletTest, ReportWinParallel) { base::BindLambdaForTesting( [&run_loop, &num_report_win_calls, i]( const absl::optional& report_url, + const base::flat_map& ad_beacon_map, const std::vector& errors) { EXPECT_EQ(GURL(base::StringPrintf("https://foo.test/%zu", i)), report_url); @@ -2810,10 +2829,12 @@ TEST_F(BidderWorkletTest, ReportWinParallelLoadFails) { seller_signals_, browser_signal_render_url_, browser_signal_bid_, browser_signal_seller_origin_, browser_signal_top_level_seller_origin_, data_version_.value_or(0), data_version_.has_value(), - base::BindOnce([](const absl::optional& report_url, - const std::vector& errors) { - ADD_FAILURE() << "Callback should not be invoked."; - })); + base::BindOnce( + [](const absl::optional& report_url, + const base::flat_map& ad_beacon_map, + const std::vector& errors) { + ADD_FAILURE() << "Callback should not be invoked."; + })); } url_loader_factory_.AddResponse(interest_group_bidding_url_.spec(), @@ -2829,6 +2850,7 @@ TEST_F(BidderWorkletTest, ReportWinDateNotAvailable) { RunReportWinWithFunctionBodyExpectingResult( R"(sendReportTo("https://foo.test/" + Date().toString()))", /*expected_report_url =*/absl::nullopt, + /*expected_ad_beacon_map=*/{}, {"https://url.test/:10 Uncaught ReferenceError: Date is not defined."}); } @@ -3002,6 +3024,7 @@ TEST_F(BidderWorkletTest, ScriptIsolation) { data_version_.value_or(0), data_version_.has_value(), base::BindLambdaForTesting( [&run_loop](const absl::optional& report_url, + const base::flat_map& ad_beacon_map, const std::vector& errors) { EXPECT_EQ(GURL("https://23.test/"), report_url); EXPECT_TRUE(errors.empty()); @@ -3427,7 +3450,7 @@ TEST_F(BidderWorkletTest, InstrumentationBreakpoints) { // Now ask for reporting. This should hit the other breakpoint. base::RunLoop run_loop; RunReportWinExpectingResultAsync(worklet.get(), GURL("https://foo.test/"), {}, - run_loop.QuitClosure()); + {}, run_loop.QuitClosure()); TestDevToolsAgentClient::Event breakpoint_hit2 = debug.WaitForMethodNotification("Debugger.paused"); @@ -3664,5 +3687,107 @@ TEST_F(BidderWorkletBiddingAndScoringDebugReportingAPIEnabledTest, GURL("https://loss.url1")); } +TEST_F(BidderWorkletTest, ReportWinRegisterAdBeacon) { + base::flat_map expected_ad_beacon_map = { + {"click", GURL("https://click.example.com/")}, + {"view", GURL("https://view.example.com/")}, + }; + RunReportWinWithFunctionBodyExpectingResult( + R"(registerAdBeacon({ + 'click': "https://click.example.com/", + 'view': "https://view.example.com/", + }))", + /*expected_report_url =*/absl::nullopt, expected_ad_beacon_map); + + // Don't call twice. + RunReportWinWithFunctionBodyExpectingResult( + R"(registerAdBeacon({ + 'click': "https://click.example.com/", + 'view': "https://view.example.com/", + }); + registerAdBeacon())", + /*expected_report_url =*/absl::nullopt, + /*expected_ad_beacon_map=*/{}, + {"https://url.test/:14 Uncaught TypeError: registerAdBeacon may be " + "called at most once."}); + + // If called twice and the error is caught, use the first result. + RunReportWinWithFunctionBodyExpectingResult( + R"(registerAdBeacon({ + 'click': "https://click.example.com/", + 'view': "https://view.example.com/", + }); + try { registerAdBeacon() } + catch (e) {})", + /*expected_report_url =*/absl::nullopt, expected_ad_beacon_map); + + // If error on first call, can be called again. + RunReportWinWithFunctionBodyExpectingResult( + R"(try { registerAdBeacon() } + catch (e) {} + registerAdBeacon({ + 'click': "https://click.example.com/", + 'view': "https://view.example.com/", + }))", + /*expected_report_url =*/absl::nullopt, expected_ad_beacon_map); + + // Error if no parameters + RunReportWinWithFunctionBodyExpectingResult( + R"(registerAdBeacon())", + /*expected_report_url =*/absl::nullopt, + /*expected_ad_beacon_map=*/{}, + {"https://url.test/:10 Uncaught TypeError: registerAdBeacon requires 1 " + "object parameter."}); + + // Error if parameter is not an object + RunReportWinWithFunctionBodyExpectingResult( + R"(registerAdBeacon("foo"))", + /*expected_report_url =*/absl::nullopt, + /*expected_ad_beacon_map=*/{}, + {"https://url.test/:10 Uncaught TypeError: registerAdBeacon requires 1 " + "object parameter."}); + + // Error if parameter is not an object + RunReportWinWithFunctionBodyExpectingResult( + R"(registerAdBeacon("foo"))", + /*expected_report_url =*/absl::nullopt, + /*expected_ad_beacon_map=*/{}, + {"https://url.test/:10 Uncaught TypeError: registerAdBeacon requires 1 " + "object parameter."}); + + // Error if parameter attributes are not strings + RunReportWinWithFunctionBodyExpectingResult( + R"(registerAdBeacon({ + 'click': "https://click.example.com/", + 1: "https://view.example.com/", + }))", + /*expected_report_url =*/absl::nullopt, + /*expected_ad_beacon_map=*/{}, + {"https://url.test/:10 Uncaught TypeError: registerAdBeacon object " + "attributes must be strings."}); + + // Error if invalid reporting URL + RunReportWinWithFunctionBodyExpectingResult( + R"(registerAdBeacon({ + 'click': "https://click.example.com/", + 'view': "gopher://view.example.com/", + }))", + /*expected_report_url =*/absl::nullopt, + /*expected_ad_beacon_map=*/{}, + {"https://url.test/:10 Uncaught TypeError: registerAdBeacon invalid " + "reporting url for key 'view': 'gopher://view.example.com/'."}); + + // Error if not trustworthy reporting URL + RunReportWinWithFunctionBodyExpectingResult( + R"(registerAdBeacon({ + 'click': "https://127.0.0.1/", + 'view': "http://view.example.com/", + }))", + /*expected_report_url =*/absl::nullopt, + /*expected_ad_beacon_map=*/{}, + {"https://url.test/:10 Uncaught TypeError: registerAdBeacon invalid " + "reporting url for key 'view': 'http://view.example.com/'."}); +} + } // namespace } // namespace auction_worklet diff --git a/content/services/auction_worklet/public/mojom/bidder_worklet.mojom b/content/services/auction_worklet/public/mojom/bidder_worklet.mojom index 9699e6695a3fd1..7e56f726a402f9 100644 --- a/content/services/auction_worklet/public/mojom/bidder_worklet.mojom +++ b/content/services/auction_worklet/public/mojom/bidder_worklet.mojom @@ -224,6 +224,9 @@ interface BidderWorklet { // to the bidder. It will be null if no reports are requested, or the // report script fails to run. // + // `ad_beacon_map` The map of ad reporting events to URLs for fenced frame + // reporting. + // // `errors` is an array of any errors that occurred while attempting // to run the worklet's reportWin() method. These are too sensitive for // the renderer to see. There may be errors even when a `report_url` is @@ -240,6 +243,7 @@ interface BidderWorklet { uint32 bidding_signals_data_version, bool has_bidding_signals_data_version) => ( url.mojom.Url? report_url, + map ad_beacon_map, array errors); // Establishes a debugger connection to the worklet. diff --git a/content/services/auction_worklet/public/mojom/seller_worklet.mojom b/content/services/auction_worklet/public/mojom/seller_worklet.mojom index dd2049d0197b06..539d1c8a7101cd 100644 --- a/content/services/auction_worklet/public/mojom/seller_worklet.mojom +++ b/content/services/auction_worklet/public/mojom/seller_worklet.mojom @@ -209,6 +209,9 @@ interface SellerWorklet { // `report_url` The URL to request to report the result of the auction to the // seller, if any. // + // `ad_beacon_map` The map of ad reporting events to URLs for fenced frame + // reporting. + // // `errors` are various error messages to be used for debugging. These are too // sensitive for the renderers to see. `errors` should not be assumed to be // empty if the other values are populated, nor should it be assumed to be @@ -227,6 +230,7 @@ interface SellerWorklet { bool has_scoring_signals_data_version) => (string? signals_for_winner, url.mojom.Url? report_url, + map ad_beacon_map, array error_msgs); // Establishes a debugger connection to the worklet. diff --git a/content/services/auction_worklet/register_ad_beacon_bindings.cc b/content/services/auction_worklet/register_ad_beacon_bindings.cc new file mode 100644 index 00000000000000..bcf9fce5d6b7ca --- /dev/null +++ b/content/services/auction_worklet/register_ad_beacon_bindings.cc @@ -0,0 +1,109 @@ +// Copyright 2022 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/services/auction_worklet/register_ad_beacon_bindings.h" + +#include +#include +#include + +#include "base/bind.h" +#include "base/callback.h" +#include "base/logging.h" +#include "base/strings/strcat.h" +#include "content/services/auction_worklet/auction_v8_helper.h" +#include "gin/converter.h" +#include "services/network/public/cpp/is_potentially_trustworthy.h" +#include "url/gurl.h" +#include "url/url_constants.h" +#include "v8-context.h" +#include "v8-object.h" +#include "v8/include/v8-exception.h" +#include "v8/include/v8-external.h" +#include "v8/include/v8-function-callback.h" +#include "v8/include/v8-template.h" + +namespace auction_worklet { + +RegisterAdBeaconBindings::RegisterAdBeaconBindings( + AuctionV8Helper* v8_helper, + v8::Local global_template) + : v8_helper_(v8_helper) { + v8::Local v8_this = + v8::External::New(v8_helper_->isolate(), this); + v8::Local v8_template = v8::FunctionTemplate::New( + v8_helper_->isolate(), &RegisterAdBeaconBindings::RegisterAdBeacon, + v8_this); + v8_template->RemovePrototype(); + global_template->Set(v8_helper_->CreateStringFromLiteral("registerAdBeacon"), + v8_template); +} + +RegisterAdBeaconBindings::~RegisterAdBeaconBindings() = default; + +void RegisterAdBeaconBindings::RegisterAdBeacon( + const v8::FunctionCallbackInfo& args) { + RegisterAdBeaconBindings* bindings = static_cast( + v8::External::Cast(*args.Data())->Value()); + v8::Isolate* isolate = args.GetIsolate(); + v8::Local context = isolate->GetCurrentContext(); + AuctionV8Helper* v8_helper = bindings->v8_helper_; + + if (!bindings->first_call_) { + isolate->ThrowException( + v8::Exception::TypeError(v8_helper->CreateStringFromLiteral( + "registerAdBeacon may be called at most once"))); + return; + } + bindings->ad_beacon_map_.clear(); + + if (args.Length() != 1 || args[0].IsEmpty() || !args[0]->IsObject()) { + isolate->ThrowException( + v8::Exception::TypeError(v8_helper->CreateStringFromLiteral( + "registerAdBeacon requires 1 object parameter"))); + return; + } + + v8::Local obj = args[0].As(); + + v8::MaybeLocal maybe_fields = obj->GetOwnPropertyNames(context); + v8::Local fields; + if (!maybe_fields.ToLocal(&fields)) { + // TODO: This might not be able to happen. + isolate->ThrowException( + v8::Exception::TypeError(v8_helper->CreateStringFromLiteral( + "registerAdBeacon could not get object attributes"))); + return; + } + std::vector> ad_beacon_list; + for (size_t idx = 0; idx < fields->Length(); idx++) { + v8::Local key = fields->Get(context, idx).ToLocalChecked(); + if (!key->IsString()) { + isolate->ThrowException( + v8::Exception::TypeError(v8_helper->CreateStringFromLiteral( + "registerAdBeacon object attributes must be strings"))); + return; + } + std::string key_string = gin::V8ToString(isolate, key); + std::string url_string = + gin::V8ToString(isolate, obj->Get(context, key).ToLocalChecked()); + GURL url(url_string); + if (!url.is_valid() || !network::IsUrlPotentiallyTrustworthy(url)) { + isolate->ThrowException( + v8::Exception::TypeError(v8_helper->CreateStringFromLiteral( + base::StrCat({"registerAdBeacon invalid reporting url for key '", + key_string, "': '", url_string, "'"}) + .c_str()))); + return; + } + ad_beacon_list.emplace_back(key_string, url); + } + base::flat_map ad_beacon_map(std::move(ad_beacon_list)); + DCHECK_EQ(fields->Length(), ad_beacon_map.size()); + + bindings->first_call_ = false; + bindings->ad_beacon_map_ = std::move(ad_beacon_map); +} + +} // namespace auction_worklet diff --git a/content/services/auction_worklet/register_ad_beacon_bindings.h b/content/services/auction_worklet/register_ad_beacon_bindings.h new file mode 100644 index 00000000000000..6fc3bd7e5e8b90 --- /dev/null +++ b/content/services/auction_worklet/register_ad_beacon_bindings.h @@ -0,0 +1,50 @@ +// Copyright 2022 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_SERVICES_AUCTION_WORKLET_REGISTER_AD_BEACON_BINDINGS_H_ +#define CONTENT_SERVICES_AUCTION_WORKLET_REGISTER_AD_BEACON_BINDINGS_H_ + +#include "base/callback.h" +#include "base/containers/flat_map.h" +#include "base/memory/raw_ptr.h" +#include "content/services/auction_worklet/auction_v8_helper.h" +#include "third_party/abseil-cpp/absl/types/optional.h" +#include "url/gurl.h" +#include "v8/include/v8-forward.h" + +namespace auction_worklet { + +// Class to manage bindings for setting ad beacon URLs. Expected to be +// used for a short-lived v8::Context. Allows only a single call for the ad +// beacon reporting map. On any subequent calls registerAdBeacon throws an +// exception and keeps the previous ad_beacon_map_ state. registerAdBeacon also +// throws on invalid URLs or non-HTTPS URLs in the map. +class RegisterAdBeaconBindings { + public: + // Add registerAdBeaconBindings object to `global_template`. The + // RegisterAdBeaconBindings must outlive the template. + RegisterAdBeaconBindings(AuctionV8Helper* v8_helper, + v8::Local global_template); + RegisterAdBeaconBindings(const RegisterAdBeaconBindings&) = delete; + RegisterAdBeaconBindings& operator=(const RegisterAdBeaconBindings&) = delete; + ~RegisterAdBeaconBindings(); + + base::flat_map TakeAdBeaconMap() { + return std::move(ad_beacon_map_); + } + + private: + static void RegisterAdBeacon(const v8::FunctionCallbackInfo& args); + + const raw_ptr v8_helper_; + + // This is a map from the event type to the reporting url. + base::flat_map ad_beacon_map_; + + bool first_call_ = true; +}; + +} // namespace auction_worklet + +#endif // CONTENT_SERVICES_AUCTION_WORKLET_REGISTER_AD_BEACON_BINDINGS_H_ diff --git a/content/services/auction_worklet/seller_worklet.cc b/content/services/auction_worklet/seller_worklet.cc index a989ece9534018..b5a9ebeced4794 100644 --- a/content/services/auction_worklet/seller_worklet.cc +++ b/content/services/auction_worklet/seller_worklet.cc @@ -21,6 +21,7 @@ #include "content/services/auction_worklet/for_debugging_only_bindings.h" #include "content/services/auction_worklet/public/mojom/auction_worklet_service.mojom.h" #include "content/services/auction_worklet/public/mojom/seller_worklet.mojom.h" +#include "content/services/auction_worklet/register_ad_beacon_bindings.h" #include "content/services/auction_worklet/report_bindings.h" #include "content/services/auction_worklet/trusted_signals.h" #include "content/services/auction_worklet/worklet_loader.h" @@ -669,6 +670,8 @@ void SellerWorklet::V8State::ReportResult( v8::Local global_template = v8::ObjectTemplate::New(isolate); ReportBindings report_bindings(v8_helper_.get(), global_template); + RegisterAdBeaconBindings register_ad_beacon_bindings(v8_helper_.get(), + global_template); // Short lived context, to avoid leaking data at global scope between either // repeated calls to this worklet, or to calls to any other worklet. @@ -682,6 +685,7 @@ void SellerWorklet::V8State::ReportResult( PostReportResultCallbackToUserThread(std::move(callback), /*signals_for_winner=*/absl::nullopt, /*report_url=*/absl::nullopt, + /*ad_beacon_map=*/{}, /*errors=*/std::vector()); return; } @@ -705,6 +709,7 @@ void SellerWorklet::V8State::ReportResult( PostReportResultCallbackToUserThread(std::move(callback), /*signals_for_winner=*/absl::nullopt, /*report_url=*/absl::nullopt, + /*ad_beacon_map=*/{}, /*errors=*/std::vector()); return; } @@ -724,6 +729,7 @@ void SellerWorklet::V8State::ReportResult( std::move(callback), /*signals_for_winner=*/absl::nullopt, /*report_url=*/absl::nullopt, + /*ad_beacon_map=*/{}, /*errors=*/std::vector()); return; } @@ -741,7 +747,8 @@ void SellerWorklet::V8State::ReportResult( .ToLocal(&signals_for_winner_value)) { PostReportResultCallbackToUserThread( std::move(callback), /*signals_for_winner=*/absl::nullopt, - /*report_url=*/absl::nullopt, std::move(errors_out)); + /*report_url=*/absl::nullopt, /*ad_beacon_map=*/{}, + std::move(errors_out)); return; } @@ -755,7 +762,8 @@ void SellerWorklet::V8State::ReportResult( PostReportResultCallbackToUserThread( std::move(callback), std::move(signals_for_winner), - report_bindings.report_url(), std::move(errors_out)); + report_bindings.report_url(), + register_ad_beacon_bindings.TakeAdBeaconMap(), std::move(errors_out)); } void SellerWorklet::V8State::ConnectDevToolsAgent( @@ -808,12 +816,14 @@ void SellerWorklet::V8State::PostReportResultCallbackToUserThread( ReportResultCallbackInternal callback, absl::optional signals_for_winner, absl::optional report_url, + base::flat_map ad_beacon_map, std::vector errors) { DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_); user_thread_->PostTask( FROM_HERE, base::BindOnce(std::move(callback), std::move(signals_for_winner), - std::move(report_url), std::move(errors))); + std::move(report_url), std::move(ad_beacon_map), + std::move(errors))); } void SellerWorklet::ResumeIfPaused() { @@ -961,13 +971,15 @@ void SellerWorklet::DeliverReportResultCallbackOnUserThread( ReportResultTaskList::iterator task, const absl::optional signals_for_winner, const absl::optional report_url, + base::flat_map ad_beacon_map, std::vector errors) { DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_); if (load_script_error_msg_) errors.insert(errors.begin(), load_script_error_msg_.value()); - std::move(task->callback).Run(signals_for_winner, report_url, errors); + std::move(task->callback) + .Run(signals_for_winner, report_url, ad_beacon_map, errors); report_result_tasks_.erase(task); } diff --git a/content/services/auction_worklet/seller_worklet.h b/content/services/auction_worklet/seller_worklet.h index 3c104e4b606ec6..f03aa7ad9d2240 100644 --- a/content/services/auction_worklet/seller_worklet.h +++ b/content/services/auction_worklet/seller_worklet.h @@ -13,6 +13,7 @@ #include #include "base/callback.h" +#include "base/containers/flat_map.h" #include "base/memory/scoped_refptr.h" #include "base/sequence_checker.h" #include "base/time/time.h" @@ -186,6 +187,7 @@ class SellerWorklet : public mojom::SellerWorklet { using ReportResultCallbackInternal = base::OnceCallback signals_for_winner, absl::optional report_url, + base::flat_map ad_beacon_map, std::vector errors)>; V8State(scoped_refptr v8_helper, @@ -247,6 +249,7 @@ class SellerWorklet : public mojom::SellerWorklet { ReportResultCallbackInternal callback, absl::optional signals_for_winner, absl::optional report_url, + base::flat_map ad_beacon_map, std::vector errors); static void PostResumeToUserThread( @@ -306,6 +309,7 @@ class SellerWorklet : public mojom::SellerWorklet { ReportResultTaskList::iterator task, absl::optional signals_for_winner, absl::optional report_url, + base::flat_map ad_beacon_map, std::vector errors); // Returns true if unpaused and the script has loaded. diff --git a/content/services/auction_worklet/seller_worklet_unittest.cc b/content/services/auction_worklet/seller_worklet_unittest.cc index e75266d6a09a21..a783a1105fed24 100644 --- a/content/services/auction_worklet/seller_worklet_unittest.cc +++ b/content/services/auction_worklet/seller_worklet_unittest.cc @@ -370,11 +370,14 @@ class SellerWorkletTest : public testing::Test { const std::string& extra_code, const absl::optional& expected_signals_for_winner, const absl::optional& expected_report_url, + const base::flat_map& expected_ad_beacon_map = + base::flat_map(), const std::vector& expected_errors = std::vector()) { RunReportResultWithJavascriptExpectingResult( CreateReportToScript(raw_return_value, extra_code), - expected_signals_for_winner, expected_report_url, expected_errors); + expected_signals_for_winner, expected_report_url, + expected_ad_beacon_map, expected_errors); } // Configures `url_loader_factory_` to return the provided script, and then @@ -384,13 +387,16 @@ class SellerWorkletTest : public testing::Test { const std::string& javascript, const absl::optional& expected_signals_for_winner, const absl::optional& expected_report_url, + const base::flat_map& expected_ad_beacon_map = + base::flat_map(), const std::vector& expected_errors = std::vector()) { SCOPED_TRACE(javascript); AddJavascriptResponse(&url_loader_factory_, decision_logic_url_, javascript); RunReportResultExpectingResult(expected_signals_for_winner, - expected_report_url, expected_errors); + expected_report_url, expected_ad_beacon_map, + expected_errors); } // Loads and runs a report_result() script, expecting the supplied result. @@ -400,6 +406,7 @@ class SellerWorkletTest : public testing::Test { mojom::SellerWorklet* seller_worklet, const absl::optional& expected_signals_for_winner, const absl::optional& expected_report_url, + const base::flat_map& expected_ad_beacon_map, const std::vector& expected_errors, base::OnceClosure done_closure) { seller_worklet->ReportResult( @@ -413,18 +420,21 @@ class SellerWorkletTest : public testing::Test { base::BindOnce( [](const absl::optional& expected_signals_for_winner, const absl::optional& expected_report_url, + const base::flat_map& expected_ad_beacon_map, const std::vector& expected_errors, base::OnceClosure done_closure, const absl::optional& signals_for_winner, const absl::optional& report_url, + const base::flat_map& ad_beacon_map, const std::vector& errors) { 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_errors, errors); std::move(done_closure).Run(); }, - expected_signals_for_winner, expected_report_url, expected_errors, - std::move(done_closure))); + expected_signals_for_winner, expected_report_url, + expected_ad_beacon_map, expected_errors, std::move(done_closure))); } void RunReportResultExpectingCallbackNeverInvoked( @@ -437,17 +447,21 @@ class SellerWorkletTest : public testing::Test { browser_signals_component_auction_report_result_params_.Clone(), browser_signal_data_version_.value_or(0), browser_signal_data_version_.has_value(), - base::BindOnce([](const absl::optional& signals_for_winner, - const absl::optional& report_url, - const std::vector& errors) { - ADD_FAILURE() << "This should not be invoked"; - })); + base::BindOnce( + [](const absl::optional& signals_for_winner, + const absl::optional& report_url, + const base::flat_map& ad_beacon_map, + const std::vector& errors) { + ADD_FAILURE() << "This should not be invoked"; + })); } // Loads and runs a report_result() script, expecting the supplied result. void RunReportResultExpectingResult( const absl::optional& expected_signals_for_winner, const absl::optional& expected_report_url, + const base::flat_map& expected_ad_beacon_map = + base::flat_map(), const std::vector& expected_errors = std::vector()) { auto seller_worklet = CreateWorklet(); @@ -456,7 +470,7 @@ class SellerWorkletTest : public testing::Test { base::RunLoop run_loop; RunReportResultExpectingResultAsync( seller_worklet.get(), expected_signals_for_winner, expected_report_url, - expected_errors, run_loop.QuitClosure()); + expected_ad_beacon_map, expected_errors, run_loop.QuitClosure()); run_loop.Run(); } @@ -1504,6 +1518,7 @@ TEST_F(SellerWorkletTest, ReportResultParallel) { /*expected_signals_for_winner=*/base::NumberToString(bid_), /*expected_report_url=*/ GURL("https://" + base::NumberToString(bid_)), + /*expected_ad_beacon_map=*/{}, /*expected_errors=*/{}, base::BindLambdaForTesting([&run_loop, &num_report_result_calls]() { ++num_report_result_calls; @@ -1570,6 +1585,7 @@ TEST_F(SellerWorkletTest, ReportResult) { "shrimp", std::string() /* extra_code */, absl::nullopt /* expected_signals_for_winner */, absl::nullopt /* expected_render_url */, + /*expected_ad_beacon_map=*/{}, {"https://url.test/:10 Uncaught ReferenceError: " "shrimp is not defined."}); } @@ -1588,12 +1604,14 @@ TEST_F(SellerWorkletTest, ReportResultSendReportTo) { "1", R"(sendReportTo("http://foo.test/"))", absl::nullopt /* expected_signals_for_winner */, absl::nullopt /* expected_render_url */, + /*expected_ad_beacon_map=*/{}, {"https://url.test/:9 Uncaught TypeError: " "sendReportTo must be passed a valid HTTPS url."}); RunReportResultCreatedScriptExpectingResult( "1", R"(sendReportTo("file:///foo/"))", absl::nullopt /* expected_signals_for_winner */, absl::nullopt /* expected_render_url */, + /*expected_ad_beacon_map=*/{}, {"https://url.test/:9 Uncaught TypeError: " "sendReportTo must be passed a valid HTTPS url."}); @@ -1603,6 +1621,7 @@ TEST_F(SellerWorkletTest, ReportResultSendReportTo) { R"(sendReportTo("https://foo.test/"); sendReportTo("https://foo.test/"))", absl::nullopt /* expected_signals_for_winner */, absl::nullopt /* expected_render_url */, + /*expected_ad_beacon_map=*/{}, {"https://url.test/:9 Uncaught TypeError: " "sendReportTo may be called at most once."}); @@ -1619,18 +1638,21 @@ TEST_F(SellerWorkletTest, ReportResultSendReportTo) { "1", R"(sendReportTo("France"))", absl::nullopt /* expected_signals_for_winner */, absl::nullopt /* expected_render_url */, + /*expected_ad_beacon_map=*/{}, {"https://url.test/:9 Uncaught TypeError: " "sendReportTo must be passed a valid HTTPS url."}); RunReportResultCreatedScriptExpectingResult( "1", R"(sendReportTo(null))", absl::nullopt /* expected_signals_for_winner */, absl::nullopt /* expected_render_url */, + /*expected_ad_beacon_map=*/{}, {"https://url.test/:9 Uncaught TypeError: " "sendReportTo requires 1 string parameter."}); RunReportResultCreatedScriptExpectingResult( "1", R"(sendReportTo([5]))", absl::nullopt /* expected_signals_for_winner */, absl::nullopt /* expected_render_url */, + /*expected_ad_beacon_map=*/{}, {"https://url.test/:9 Uncaught TypeError: " "sendReportTo requires 1 string parameter."}); } @@ -1640,6 +1662,7 @@ TEST_F(SellerWorkletTest, ReportResultDateNotAvailable) { "1", R"(sendReportTo("https://foo.test/" + Date().toString()))", absl::nullopt /* expected_signals_for_winner */, absl::nullopt /* expected_render_url */, + /*expected_ad_beacon_map=*/{}, {"https://url.test/:9 Uncaught ReferenceError: Date is not defined."}); } @@ -1823,6 +1846,149 @@ TEST_F(SellerWorkletTest, ReportResultRenderUrl) { R"("https://foo/")", browser_signal_render_url_); } +TEST_F(SellerWorkletTest, ReportResultRegisterAdBeacon) { + bid_ = 5; + base::flat_map expected_ad_beacon_map = { + {"click", GURL("https://click.example.com/")}, + {"view", GURL("https://view.example.com/")}, + }; + RunReportResultCreatedScriptExpectingResult( + R"(5)", + R"(registerAdBeacon({ + 'click': "https://click.example.com/", + 'view': "https://view.example.com/", + }))", + /*expected_signals_for_winner=*/"5", + /*expected_report_url =*/absl::nullopt, expected_ad_beacon_map); + + browser_signal_render_url_ = GURL("https://foo/"); + RunReportResultCreatedScriptExpectingResult( + R"(5)", + R"(registerAdBeacon({ + 'click': "https://click.example.com/", + 'view': "https://view.example.com/", + }); + sendReportTo(browserSignals.renderUrl))", + /*expected_signals_for_winner=*/"5", + /*expected_report_url =*/browser_signal_render_url_, + expected_ad_beacon_map); + + RunReportResultCreatedScriptExpectingResult( + R"(5)", + R"(sendReportTo(browserSignals.renderUrl); + registerAdBeacon({ + 'click': "https://click.example.com/", + 'view': "https://view.example.com/", + }))", + /*expected_signals_for_winner=*/"5", + /*expected_report_url =*/browser_signal_render_url_, + expected_ad_beacon_map); + + // Don't call twice. + RunReportResultCreatedScriptExpectingResult( + R"(5)", + R"(registerAdBeacon({ + 'click': "https://click.example.com/", + 'view': "https://view.example.com/", + }); + registerAdBeacon())", + /*expected_signals_for_winner=*/{}, + /*expected_report_url =*/absl::nullopt, + /*expected_ad_beacon_map=*/{}, + {"https://url.test/:13 Uncaught TypeError: registerAdBeacon may be " + "called at most once."}); + + // If called twice and the error is caught, use the first result. + RunReportResultCreatedScriptExpectingResult( + R"(5)", + R"(registerAdBeacon({ + 'click': "https://click.example.com/", + 'view': "https://view.example.com/", + }); + try { registerAdBeacon() } + catch (e) {})", + /*expected_signals_for_winner=*/"5", + /*expected_report_url =*/absl::nullopt, expected_ad_beacon_map); + + // If error on first call, can be called again. + RunReportResultCreatedScriptExpectingResult( + R"(5)", + R"(try { registerAdBeacon() } + catch (e) {} + registerAdBeacon({ + 'click': "https://click.example.com/", + 'view': "https://view.example.com/", + }))", + /*expected_signals_for_winner=*/"5", + /*expected_report_url =*/absl::nullopt, expected_ad_beacon_map); + + // Error if no parameters + RunReportResultCreatedScriptExpectingResult( + R"(5)", R"(registerAdBeacon())", + /*expected_signals_for_winner=*/{}, + /*expected_report_url =*/absl::nullopt, + /*expected_ad_beacon_map=*/{}, + {"https://url.test/:9 Uncaught TypeError: registerAdBeacon requires 1 " + "object parameter."}); + + // Error if parameter is not an object + RunReportResultCreatedScriptExpectingResult( + R"(5)", R"(registerAdBeacon("foo"))", + /*expected_signals_for_winner=*/{}, + /*expected_report_url =*/absl::nullopt, + /*expected_ad_beacon_map=*/{}, + {"https://url.test/:9 Uncaught TypeError: registerAdBeacon requires 1 " + "object parameter."}); + + // Error if parameter is not an object + RunReportResultCreatedScriptExpectingResult( + R"(5)", R"(registerAdBeacon("foo"))", + /*expected_signals_for_winner=*/{}, + /*expected_report_url =*/absl::nullopt, + /*expected_ad_beacon_map=*/{}, + {"https://url.test/:9 Uncaught TypeError: registerAdBeacon requires 1 " + "object parameter."}); + + // Error if parameter attributes are not strings + RunReportResultCreatedScriptExpectingResult( + R"(5)", + R"(registerAdBeacon({ + 'click': "https://click.example.com/", + 1: "https://view.example.com/", + }))", + /*expected_signals_for_winner=*/{}, + /*expected_report_url =*/absl::nullopt, + /*expected_ad_beacon_map=*/{}, + {"https://url.test/:9 Uncaught TypeError: registerAdBeacon object " + "attributes must be strings."}); + + // Error if invalid reporting URL + RunReportResultCreatedScriptExpectingResult( + R"(5)", + R"(registerAdBeacon({ + 'click': "https://click.example.com/", + 'view': "gopher://view.example.com/", + }))", + /*expected_signals_for_winner=*/{}, + /*expected_report_url =*/absl::nullopt, + /*expected_ad_beacon_map=*/{}, + {"https://url.test/:9 Uncaught TypeError: registerAdBeacon invalid " + "reporting url for key 'view': 'gopher://view.example.com/'."}); + + // Error if not trustworthy reporting URL + RunReportResultCreatedScriptExpectingResult( + R"(5)", + R"(registerAdBeacon({ + 'click': "https://127.0.0.1/", + 'view': "http://view.example.com/", + }))", + /*expected_signals_for_winner=*/{}, + /*expected_report_url =*/absl::nullopt, + /*expected_ad_beacon_map=*/{}, + {"https://url.test/:9 Uncaught TypeError: registerAdBeacon invalid " + "reporting url for key 'view': 'http://view.example.com/'."}); +} + TEST_F(SellerWorkletTest, ReportResultBid) { bid_ = 5; RunReportResultCreatedScriptExpectingResult( @@ -2033,9 +2199,11 @@ TEST_F(SellerWorkletTest, ScriptIsolation) { browser_signal_data_version_.value_or(0), browser_signal_data_version_.has_value(), base::BindLambdaForTesting( - [&run_loop](const absl::optional& signals_for_winner, - const absl::optional& report_url, - const std::vector& errors) { + [&run_loop]( + const absl::optional& signals_for_winner, + const absl::optional& report_url, + const base::flat_map& ad_beacon_map, + const std::vector& errors) { EXPECT_EQ("2", signals_for_winner); EXPECT_TRUE(errors.empty()); run_loop.Quit(); @@ -2093,6 +2261,7 @@ TEST_F(SellerWorkletTest, DeleteBeforeReportResultCallback) { browser_signal_data_version_.has_value(), base::BindOnce([](const absl::optional& signals_for_winner, const absl::optional& report_url, + const base::flat_map& ad_beacon_map, const std::vector& errors) { ADD_FAILURE() << "Callback should not be invoked since worklet deleted"; })); @@ -2528,9 +2697,10 @@ TEST_F(SellerWorkletTest, InstrumentationBreakpoints) { // Now try reporting, should hit the other breakpoint. base::RunLoop run_loop2; - RunReportResultExpectingResultAsync(worklet.get(), "1", - GURL("https://foo.test/"), {}, - run_loop2.QuitClosure()); + RunReportResultExpectingResultAsync( + worklet.get(), "1", GURL("https://foo.test/"), + /*expected_ad_beacon_map=*/{}, /*expected_errors=*/{}, + run_loop2.QuitClosure()); TestDevToolsAgentClient::Event breakpoint_hit2 = debug.WaitForMethodNotification("Debugger.paused"); const std::string* breakpoint2 =