| // Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/browser/interest_group/auction_runner.h" |
| |
| #include <stdint.h> |
| |
| #include <optional> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/feature_list.h" |
| #include "base/functional/callback.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/time/time.h" |
| #include "base/types/optional_ref.h" |
| #include "content/browser/interest_group/ad_auction_page_data.h" |
| #include "content/browser/interest_group/auction_metrics_recorder.h" |
| #include "content/browser/interest_group/auction_nonce_manager.h" |
| #include "content/browser/interest_group/interest_group_auction.h" |
| #include "content/browser/interest_group/interest_group_auction_reporter.h" |
| #include "content/browser/interest_group/interest_group_manager_impl.h" |
| #include "content/browser/interest_group/interest_group_real_time_report_util.h" |
| #include "content/common/features.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/content_browser_client.h" |
| #include "content/public/browser/frame_tree_node_id.h" |
| #include "content/public/common/content_features.h" |
| #include "content/services/auction_worklet/public/mojom/private_aggregation_request.mojom.h" |
| #include "services/metrics/public/cpp/ukm_source_id.h" |
| #include "services/network/public/mojom/client_security_state.mojom.h" |
| #include "third_party/blink/public/common/features.h" |
| #include "third_party/blink/public/common/interest_group/auction_config.h" |
| #include "third_party/blink/public/common/interest_group/interest_group.h" |
| #include "third_party/blink/public/mojom/interest_group/interest_group_types.mojom.h" |
| #include "url/gurl.h" |
| #include "url/origin.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| auction_worklet::mojom::KAnonymityBidMode DetermineKAnonMode() { |
| // K-anonymity enforcement is always disabled for the testing population. |
| if (base::FeatureList::IsEnabled( |
| features::kCookieDeprecationFacilitatedTesting)) { |
| return auction_worklet::mojom::KAnonymityBidMode::kNone; |
| } |
| if (base::FeatureList::IsEnabled( |
| blink::features::kFledgeConsiderKAnonymity)) { |
| if (base::FeatureList::IsEnabled( |
| blink::features::kFledgeEnforceKAnonymity)) { |
| return auction_worklet::mojom::KAnonymityBidMode::kEnforce; |
| } else { |
| return auction_worklet::mojom::KAnonymityBidMode::kSimulate; |
| } |
| } else { |
| return auction_worklet::mojom::KAnonymityBidMode::kNone; |
| } |
| } |
| |
| // Returns null if failed to verify. |
| blink::AuctionConfig* LookupAuction( |
| blink::AuctionConfig& config, |
| const blink::mojom::AuctionAdConfigAuctionId& auction) { |
| if (auction.is_main_auction()) { |
| return &config; |
| } |
| uint32_t pos = auction.get_component_auction(); |
| if (pos < config.non_shared_params.component_auctions.size()) { |
| return &config.non_shared_params.component_auctions[pos]; |
| } |
| return nullptr; |
| } |
| |
| } // namespace |
| |
| std::unique_ptr<AuctionRunner> AuctionRunner::CreateAndStart( |
| AuctionMetricsRecorder* auction_metrics_recorder, |
| DwaAuctionMetricsManager* dwa_auction_metrics_manager, |
| AuctionWorkletManager* auction_worklet_manager, |
| AuctionNonceManager* auction_nonce_manager, |
| InterestGroupManagerImpl* interest_group_manager, |
| BrowserContext* browser_context, |
| PrivateAggregationManager* private_aggregation_manager, |
| AdAuctionPageDataCallback ad_auction_page_data_callback, |
| InterestGroupAuctionReporter::LogPrivateAggregationRequestsCallback |
| log_private_aggregation_requests_callback, |
| const blink::AuctionConfig& auction_config, |
| const url::Origin& main_frame_origin, |
| const url::Origin& frame_origin, |
| std::optional<std::string> user_agent_override, |
| network::mojom::ClientSecurityStatePtr client_security_state, |
| scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory, |
| IsInterestGroupApiAllowedCallback is_interest_group_api_allowed_callback, |
| AreReportingOriginsAttestedCallback attestation_callback, |
| mojo::PendingReceiver<AbortableAdAuction> abort_receiver, |
| RunAuctionCallback callback) { |
| std::unique_ptr<AuctionRunner> instance(new AuctionRunner( |
| auction_metrics_recorder, dwa_auction_metrics_manager, |
| auction_worklet_manager, auction_nonce_manager, interest_group_manager, |
| browser_context, private_aggregation_manager, |
| std::move(ad_auction_page_data_callback), |
| std::move(log_private_aggregation_requests_callback), |
| DetermineKAnonMode(), std::move(auction_config), main_frame_origin, |
| frame_origin, std::move(user_agent_override), |
| std::move(client_security_state), std::move(url_loader_factory), |
| std::move(is_interest_group_api_allowed_callback), |
| std::move(attestation_callback), std::move(abort_receiver), |
| std::move(callback))); |
| instance->StartAuction(); |
| return instance; |
| } |
| |
| AuctionRunner::~AuctionRunner() = default; |
| |
| void AuctionRunner::ResolvedPromiseParam( |
| blink::mojom::AuctionAdConfigAuctionIdPtr auction_id, |
| blink::mojom::AuctionAdConfigField field, |
| const std::optional<std::string>& json_value) { |
| if (state_ == State::kFailed) { |
| return; |
| } |
| |
| blink::AuctionConfig* config = |
| LookupAuction(*owned_auction_config_, *auction_id); |
| if (!config) { |
| // TODO(morlovich): Abort on these. |
| mojo::ReportBadMessage("Invalid auction ID in ResolvedPromiseParam"); |
| return; |
| } |
| |
| blink::AuctionConfig::MaybePromiseJson new_val = |
| blink::AuctionConfig::MaybePromiseJson::FromValue(json_value); |
| |
| switch (field) { |
| case blink::mojom::AuctionAdConfigField::kAuctionSignals: |
| if (!config->non_shared_params.auction_signals.is_promise()) { |
| mojo::ReportBadMessage("ResolvedPromiseParam updating non-promise"); |
| return; |
| } |
| config->non_shared_params.auction_signals = std::move(new_val); |
| break; |
| |
| case blink::mojom::AuctionAdConfigField::kSellerSignals: |
| if (!config->non_shared_params.seller_signals.is_promise()) { |
| mojo::ReportBadMessage("ResolvedPromiseParam updating non-promise"); |
| return; |
| } |
| config->non_shared_params.seller_signals = std::move(new_val); |
| break; |
| |
| case blink::mojom::AuctionAdConfigField::kSellerTKVSignals: |
| if (!config->non_shared_params.seller_tkv_signals.is_promise()) { |
| mojo::ReportBadMessage("ResolvedPromiseParam updating non-promise"); |
| return; |
| } |
| config->non_shared_params.seller_tkv_signals = std::move(new_val); |
| break; |
| } |
| |
| NotifyPromiseResolved(*auction_id, *config); |
| } |
| |
| void AuctionRunner::ResolvedPerBuyerSignalsPromise( |
| blink::mojom::AuctionAdConfigAuctionIdPtr auction_id, |
| const std::optional<base::flat_map<url::Origin, std::string>>& |
| per_buyer_signals) { |
| if (state_ == State::kFailed) { |
| return; |
| } |
| |
| blink::AuctionConfig* config = |
| LookupAuction(*owned_auction_config_, *auction_id); |
| if (!config) { |
| mojo::ReportBadMessage( |
| "Invalid auction ID in ResolvedPerBuyerSignalsPromise"); |
| return; |
| } |
| |
| if (!config->non_shared_params.per_buyer_signals.is_promise()) { |
| mojo::ReportBadMessage( |
| "ResolvedPerBuyerSignalsPromise updating non-promise"); |
| return; |
| } |
| |
| config->non_shared_params.per_buyer_signals = |
| blink::AuctionConfig::MaybePromisePerBuyerSignals::FromValue( |
| per_buyer_signals); |
| NotifyPromiseResolved(*auction_id, *config); |
| } |
| |
| void AuctionRunner::ResolvedBuyerTkvSignalsPromise( |
| blink::mojom::AuctionAdConfigAuctionIdPtr auction_id, |
| const url::Origin& buyer, |
| const std::optional<std::string>& buyer_tkv_signals) { |
| if (state_ == State::kFailed) { |
| return; |
| } |
| |
| blink::AuctionConfig* config = |
| LookupAuction(*owned_auction_config_, *auction_id); |
| if (!config) { |
| mojo::ReportBadMessage( |
| "Invalid auction ID in ResolvedPerBuyerSignalsPromise"); |
| return; |
| } |
| |
| auto tvk_signals_it = |
| config->non_shared_params.per_buyer_tkv_signals.find(buyer); |
| if (tvk_signals_it == config->non_shared_params.per_buyer_tkv_signals.end() || |
| !tvk_signals_it->second.is_promise()) { |
| mojo::ReportBadMessage( |
| "ResolvedBuyerTkvSignalsPromise may only update a promise"); |
| return; |
| } |
| |
| tvk_signals_it->second = |
| blink::AuctionConfig::MaybePromiseJson::FromValue(buyer_tkv_signals); |
| |
| // The order these two notifications are send in should not matter. |
| auction_.NotifyBuyerTkvSignalsPromiseResolved( |
| buyer, auction_id->is_main_auction() |
| ? std::optional<uint32_t>() |
| : auction_id->get_component_auction()); |
| NotifyPromiseResolved(*auction_id, *config); |
| } |
| |
| void AuctionRunner::ResolvedBuyerTimeoutsPromise( |
| blink::mojom::AuctionAdConfigAuctionIdPtr auction_id, |
| blink::mojom::AuctionAdConfigBuyerTimeoutField field, |
| const blink::AuctionConfig::BuyerTimeouts& buyer_timeouts) { |
| if (state_ == State::kFailed) { |
| return; |
| } |
| |
| blink::AuctionConfig* config = |
| LookupAuction(*owned_auction_config_, *auction_id); |
| if (!config) { |
| mojo::ReportBadMessage( |
| "Invalid auction ID in ResolvedBuyerTimeoutsPromise"); |
| return; |
| } |
| |
| blink::AuctionConfig::MaybePromiseBuyerTimeouts* field_ptr = nullptr; |
| switch (field) { |
| case blink::mojom::AuctionAdConfigBuyerTimeoutField::kPerBuyerTimeouts: |
| field_ptr = &config->non_shared_params.buyer_timeouts; |
| break; |
| case blink::mojom::AuctionAdConfigBuyerTimeoutField:: |
| kPerBuyerCumulativeTimeouts: |
| field_ptr = &config->non_shared_params.buyer_cumulative_timeouts; |
| break; |
| } |
| |
| if (!field_ptr->is_promise()) { |
| mojo::ReportBadMessage("ResolvedBuyerTimeoutsPromise updating non-promise"); |
| return; |
| } |
| |
| *field_ptr = blink::AuctionConfig::MaybePromiseBuyerTimeouts::FromValue( |
| buyer_timeouts); |
| NotifyPromiseResolved(*auction_id, *config); |
| } |
| |
| void AuctionRunner::ResolvedDeprecatedRenderURLReplacementsPromise( |
| blink::mojom::AuctionAdConfigAuctionIdPtr auction_id, |
| const std::vector<::blink::AuctionConfig::AdKeywordReplacement>& |
| deprecated_render_url_replacements) { |
| if (state_ == State::kFailed) { |
| return; |
| } |
| blink::AuctionConfig* config = |
| LookupAuction(*owned_auction_config_, *auction_id); |
| if (!config) { |
| mojo::ReportBadMessage( |
| "Invalid auction ID in ResolvedDeprecatedRenderURLReplacementsPromise"); |
| return; |
| } |
| if (!config->non_shared_params.deprecated_render_url_replacements |
| .is_promise()) { |
| mojo::ReportBadMessage( |
| "ResolvedDeprecatedRenderURLReplacementsPromise updating non-promise"); |
| return; |
| } |
| |
| config->non_shared_params.deprecated_render_url_replacements = |
| blink::AuctionConfig::MaybePromiseDeprecatedRenderURLReplacements:: |
| FromValue(deprecated_render_url_replacements); |
| NotifyPromiseResolved(*auction_id, *config); |
| } |
| |
| void AuctionRunner::ResolvedBuyerCurrenciesPromise( |
| blink::mojom::AuctionAdConfigAuctionIdPtr auction_id, |
| const blink::AuctionConfig::BuyerCurrencies& buyer_currencies) { |
| if (state_ == State::kFailed) { |
| return; |
| } |
| |
| blink::AuctionConfig* config = |
| LookupAuction(*owned_auction_config_, *auction_id); |
| if (!config) { |
| mojo::ReportBadMessage( |
| "Invalid auction ID in ResolvedBuyerCurrenciesPromise"); |
| return; |
| } |
| |
| if (!config->non_shared_params.buyer_currencies.is_promise()) { |
| mojo::ReportBadMessage( |
| "ResolvedBuyerCurrenciesPromise updating non-promise"); |
| return; |
| } |
| |
| config->non_shared_params.buyer_currencies = |
| blink::AuctionConfig::MaybePromiseBuyerCurrencies::FromValue( |
| buyer_currencies); |
| NotifyPromiseResolved(*auction_id, *config); |
| } |
| |
| void AuctionRunner::ResolvedDirectFromSellerSignalsPromise( |
| blink::mojom::AuctionAdConfigAuctionIdPtr auction_id, |
| const std::optional<blink::DirectFromSellerSignals>& |
| direct_from_seller_signals) { |
| if (state_ == State::kFailed) { |
| return; |
| } |
| |
| blink::AuctionConfig* config = |
| LookupAuction(*owned_auction_config_, *auction_id); |
| if (!config) { |
| mojo::ReportBadMessage( |
| "Invalid auction ID in ResolvedDirectFromSellerSignalsPromise"); |
| return; |
| } |
| |
| if (!config->direct_from_seller_signals.is_promise()) { |
| mojo::ReportBadMessage( |
| "ResolvedDirectFromSellerSignalsPromise updating non-promise"); |
| return; |
| } |
| |
| if (!config->IsDirectFromSellerSignalsValid(direct_from_seller_signals)) { |
| mojo::ReportBadMessage( |
| "ResolvedDirectFromSellerSignalsPromise with invalid signals"); |
| return; |
| } |
| |
| config->direct_from_seller_signals = |
| blink::AuctionConfig::MaybePromiseDirectFromSellerSignals::FromValue( |
| direct_from_seller_signals); |
| NotifyPromiseResolved(*auction_id, *config); |
| } |
| |
| void AuctionRunner::ResolvedDirectFromSellerSignalsHeaderAdSlotPromise( |
| blink::mojom::AuctionAdConfigAuctionIdPtr auction_id, |
| const std::optional<std::string>& |
| direct_from_seller_signals_header_ad_slot) { |
| if (!base::FeatureList::IsEnabled( |
| blink::features::kFledgeDirectFromSellerSignalsHeaderAdSlot)) { |
| mojo::ReportBadMessage( |
| "ResolvedDirectFromSellerSignalsHeaderAdSlot with " |
| "FledgeDirectFromSellerSignalsHeaderAdSlot off"); |
| return; |
| } |
| |
| if (state_ == State::kFailed) { |
| return; |
| } |
| |
| blink::AuctionConfig* config = |
| LookupAuction(*owned_auction_config_, *auction_id); |
| if (!config) { |
| mojo::ReportBadMessage( |
| "Invalid auction ID in ResolvedDirectFromSellerSignalsHeaderAdSlot"); |
| return; |
| } |
| |
| if (!config->expects_direct_from_seller_signals_header_ad_slot) { |
| mojo::ReportBadMessage( |
| "ResolvedDirectFromSellerSignalsHeaderAdSlot updating non-promise"); |
| return; |
| } |
| |
| AdAuctionPageData* page_data = ad_auction_page_data_callback_.Run(); |
| if (!page_data) { |
| // Page is in process of being torn down, so just abort. |
| FailAuction(false); |
| return; |
| } |
| |
| if (auction_id->is_main_auction()) { |
| auction_.NotifyDirectFromSellerSignalsHeaderAdSlotConfig( |
| *page_data, std::move(direct_from_seller_signals_header_ad_slot)); |
| } else { |
| auction_.NotifyComponentDirectFromSellerSignalsHeaderAdSlotConfig( |
| auction_id->get_component_auction(), *page_data, |
| std::move(direct_from_seller_signals_header_ad_slot)); |
| } |
| |
| config->expects_direct_from_seller_signals_header_ad_slot = false; |
| NotifyPromiseResolved(*auction_id, *config); |
| } |
| |
| void AuctionRunner::ResolvedAuctionAdResponsePromise( |
| blink::mojom::AuctionAdConfigAuctionIdPtr auction_id, |
| mojo_base::BigBuffer response) { |
| if (state_ == State::kFailed) { |
| return; |
| } |
| blink::AuctionConfig* config = |
| LookupAuction(*owned_auction_config_, *auction_id); |
| if (!config) { |
| mojo::ReportBadMessage( |
| "Invalid auction ID in ResolvedAuctionAdResponsePromise"); |
| return; |
| } |
| // If we aren't in B&A mode or we already have the response it's an error. |
| if (!config->server_response || |
| (config->server_response && config->server_response->got_response)) { |
| mojo::ReportBadMessage( |
| "ResolvedAuctionAdResponsePromise updating non-promise"); |
| return; |
| } |
| config->server_response->got_response = true; |
| |
| if (auction_id->is_main_auction()) { |
| auction_.HandleServerResponse(std::move(response), |
| ad_auction_page_data_callback_); |
| } else { |
| auction_.HandleComponentServerResponse(auction_id->get_component_auction(), |
| std::move(response), |
| ad_auction_page_data_callback_); |
| } |
| } |
| |
| void AuctionRunner::ResolvedAdditionalBids( |
| blink::mojom::AuctionAdConfigAuctionIdPtr auction_id) { |
| if (state_ == State::kFailed) { |
| return; |
| } |
| |
| blink::AuctionConfig* config = |
| LookupAuction(*owned_auction_config_, *auction_id); |
| if (!config) { |
| mojo::ReportBadMessage("Invalid auction ID in ResolvedAdditionalBids"); |
| return; |
| } |
| |
| if (!config->expects_additional_bids) { |
| mojo::ReportBadMessage("ResolvedAdditionalBids updating non-promise"); |
| return; |
| } |
| |
| config->expects_additional_bids = false; |
| |
| AdAuctionPageData* page_data = ad_auction_page_data_callback_.Run(); |
| if (!page_data) { |
| // Page is in process of being torn down, so just abort. |
| FailAuction(false); |
| return; |
| } |
| |
| if (auction_id->is_main_auction()) { |
| auction_.NotifyAdditionalBidsConfig(*page_data); |
| } else { |
| auction_.NotifyComponentAdditionalBidsConfig( |
| auction_id->get_component_auction(), *page_data); |
| } |
| |
| NotifyPromiseResolved(*auction_id, *config); |
| } |
| |
| void AuctionRunner::Abort() { |
| // Don't abort if the auction already finished (either as success or failure; |
| // this includes the case of multiple promise arguments rejecting). |
| if (state_ == State::kFailed || state_ == State::kSucceeded) { |
| return; |
| } |
| std::string uma_prefix = owned_auction_config_->server_response.has_value() |
| ? "Ads.InterestGroup.ServerAuction." |
| : "Ads.InterestGroup.Auction."; |
| base::UmaHistogramEnumeration(base::StrCat({uma_prefix, "StateAtAbortTime"}), |
| state_); |
| base::UmaHistogramMediumTimes( |
| uma_prefix + "SignaledAbortTime", |
| base::TimeTicks::Now() - auction_.creation_time()); |
| auction_.SetReceivedAbortSignal(); |
| FailAuction(/*aborted_by_script=*/true); |
| } |
| |
| void AuctionRunner::NormalizeReportingTimeouts() { |
| if (owned_auction_config_->non_shared_params.reporting_timeout.has_value()) { |
| owned_auction_config_->non_shared_params.reporting_timeout = |
| std::min(*owned_auction_config_->non_shared_params.reporting_timeout, |
| kMaxReportingTimeout); |
| } |
| for (auto& component : |
| owned_auction_config_->non_shared_params.component_auctions) { |
| if (component.non_shared_params.reporting_timeout.has_value()) { |
| component.non_shared_params.reporting_timeout = std::min( |
| *component.non_shared_params.reporting_timeout, kMaxReportingTimeout); |
| } |
| } |
| } |
| |
| void AuctionRunner::FailAuction( |
| bool aborted_by_script, |
| blink::InterestGroupSet interest_groups_that_bid) { |
| DCHECK(callback_); |
| State state_at_auction_fail = state_; |
| state_ = State::kFailed; |
| |
| // Can have loss report URLs if the auction failed because the seller |
| // rejected all bids. |
| auction_.CollectBiddingAndScoringPhaseReports(); |
| // Shouldn't have any win report URLs if nothing won the auction. |
| CHECK(auction_.TakeDebugWinReportUrls().empty()); |
| |
| if (!aborted_by_script) { |
| // A different metric is recorded for script-triggered abort in |
| // AuctionRunner::Abort. |
| std::string uma_prefix = owned_auction_config_->server_response.has_value() |
| ? "Ads.InterestGroup.ServerAuction." |
| : "Ads.InterestGroup.Auction."; |
| base::UmaHistogramEnumeration(base::StrCat({uma_prefix, "StateAtFailTime"}), |
| state_at_auction_fail); |
| |
| interest_group_manager_->RegisterAdKeysAsJoined( |
| auction_.GetKAnonKeysToJoin()); |
| interest_group_manager_->EnqueueReports( |
| InterestGroupManagerImpl::ReportType::kDebugLoss, |
| auction_.TakeDebugLossReportUrls(), FrameTreeNodeId(), frame_origin_, |
| *client_security_state_, url_loader_factory_); |
| |
| interest_group_manager_->EnqueueRealTimeReports( |
| auction_.TakeRealTimeReportingContributions(), |
| ad_auction_page_data_callback_, FrameTreeNodeId(), frame_origin_, |
| *client_security_state_, url_loader_factory_); |
| |
| InterestGroupAuctionReporter::OnFledgePrivateAggregationRequests( |
| private_aggregation_manager_, main_frame_origin_, |
| auction_.TakeReservedPrivateAggregationRequests()); |
| } |
| |
| interest_group_manager_->RecordInterestGroupBids(interest_groups_that_bid); |
| |
| UpdateInterestGroupsPostAuction(); |
| |
| auto [contained_server_auction, contained_on_device_auction] = |
| IncludesServerAndOnDeviceAuctions(); |
| |
| AuctionResult auction_result = auction_.final_auction_result().value_or( |
| aborted_by_script ? AuctionResult::kAbortSignal |
| : AuctionResult::kDocumentDestruction); |
| // When the auction fails, private aggregation requests of non-reserved event |
| // types cannot be triggered anyway, so no need to pass it along. |
| std::move(callback_).Run(this, aborted_by_script, |
| /*winning_group_key=*/std::nullopt, |
| /*requested_ad_size=*/std::nullopt, |
| /*ad_descriptor=*/std::nullopt, |
| /*ad_component_descriptors=*/{}, |
| auction_.TakeErrors(), |
| /*reporter=*/nullptr, contained_server_auction, |
| contained_on_device_auction, auction_result); |
| } |
| |
| AuctionRunner::AuctionRunner( |
| AuctionMetricsRecorder* auction_metrics_recorder, |
| DwaAuctionMetricsManager* dwa_auction_metrics_manager, |
| AuctionWorkletManager* auction_worklet_manager, |
| AuctionNonceManager* auction_nonce_manager, |
| InterestGroupManagerImpl* interest_group_manager, |
| BrowserContext* browser_context, |
| PrivateAggregationManager* private_aggregation_manager, |
| AdAuctionPageDataCallback ad_auction_page_data_callback, |
| InterestGroupAuctionReporter::LogPrivateAggregationRequestsCallback |
| log_private_aggregation_requests_callback, |
| auction_worklet::mojom::KAnonymityBidMode kanon_mode, |
| const blink::AuctionConfig& auction_config, |
| const url::Origin& main_frame_origin, |
| const url::Origin& frame_origin, |
| std::optional<std::string> user_agent_override, |
| network::mojom::ClientSecurityStatePtr client_security_state, |
| scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory, |
| IsInterestGroupApiAllowedCallback is_interest_group_api_allowed_callback, |
| AreReportingOriginsAttestedCallback attestation_callback, |
| mojo::PendingReceiver<AbortableAdAuction> abort_receiver, |
| RunAuctionCallback callback) |
| : interest_group_manager_(interest_group_manager), |
| browser_context_(browser_context), |
| private_aggregation_manager_(private_aggregation_manager), |
| main_frame_origin_(main_frame_origin), |
| frame_origin_(frame_origin), |
| client_security_state_(std::move(client_security_state)), |
| user_agent_override_(std::move(user_agent_override)), |
| url_loader_factory_(std::move(url_loader_factory)), |
| is_interest_group_api_allowed_callback_( |
| std::move(is_interest_group_api_allowed_callback)), |
| ad_auction_page_data_callback_(std::move(ad_auction_page_data_callback)), |
| attestation_callback_(attestation_callback), |
| abort_receiver_(this, std::move(abort_receiver)), |
| kanon_mode_(kanon_mode), |
| owned_auction_config_( |
| std::make_unique<blink::AuctionConfig>(auction_config)), |
| callback_(std::move(callback)), |
| promise_fields_in_auction_config_(owned_auction_config_->NumPromises()), |
| auction_(kanon_mode_, |
| url_loader_factory_, |
| main_frame_origin, |
| client_security_state_->ip_address_space, |
| owned_auction_config_.get(), |
| /*parent=*/nullptr, |
| auction_metrics_recorder, |
| dwa_auction_metrics_manager, |
| auction_worklet_manager, |
| auction_nonce_manager, |
| interest_group_manager, |
| base::BindRepeating(&AuctionRunner::GetDataDecoder, |
| // `this` owns `auction_`. |
| base::Unretained(this)), |
| /*auction_start_time=*/base::Time::Now(), |
| is_interest_group_api_allowed_callback_, |
| std::move(log_private_aggregation_requests_callback)) {} |
| |
| void AuctionRunner::StartAuction() { |
| NormalizeReportingTimeouts(); |
| |
| if (owned_auction_config_->server_response) { |
| // Entire auction is running server-side, so skip interest group loading. |
| state_ = State::kBiddingAndScoringPhase; |
| auction_.StartBiddingAndScoringPhase( |
| /*debug_report_lockout_and_cooldowns=*/std::nullopt, |
| /*on_seller_receiver_callback=*/base::OnceClosure(), |
| base::BindOnce(&AuctionRunner::OnBidsGeneratedAndScored, |
| base::Unretained(this), base::TimeTicks::Now())); |
| return; |
| } |
| state_ = State::kLoadingGroupsPhase; |
| auction_.StartLoadInterestGroupsPhase(base::BindOnce( |
| &AuctionRunner::OnLoadInterestGroupsComplete, base::Unretained(this))); |
| } |
| |
| void AuctionRunner::OnLoadInterestGroupsComplete(bool success) { |
| if (state_ == State::kFailed) { |
| return; |
| } |
| if (!success) { |
| FailAuction(/*aborted_by_script=*/false); |
| return; |
| } |
| |
| if (base::FeatureList::IsEnabled( |
| blink::features::kFledgeSampleDebugReports)) { |
| // All sellers and buyers in the auction. |
| base::flat_set<url::Origin> origins = auction_.GetSellersAndBuyers(); |
| // Use a weak pointer here so that |
| // &AuctionRunner::OnLoadDebugReportLockoutAndCooldownsComplete is cancelled |
| // when |weak_ptr_factory_| is destroyed. |
| interest_group_manager_->GetDebugReportLockoutAndCooldowns( |
| std::move(origins), |
| base::BindOnce( |
| &AuctionRunner::OnLoadDebugReportLockoutAndCooldownsComplete, |
| weak_ptr_factory_.GetWeakPtr())); |
| } else { |
| OnLoadDebugReportLockoutAndCooldownsComplete( |
| /*debug_report_lockout_and_cooldowns=*/std::nullopt); |
| } |
| } |
| |
| void AuctionRunner::OnLoadDebugReportLockoutAndCooldownsComplete( |
| std::optional<DebugReportLockoutAndCooldowns> |
| debug_report_lockout_and_cooldowns) { |
| if (state_ == State::kFailed) { |
| return; |
| } |
| state_ = State::kBiddingAndScoringPhase; |
| auction_.StartBiddingAndScoringPhase( |
| std::move(debug_report_lockout_and_cooldowns), |
| /*on_seller_receiver_callback=*/base::OnceClosure(), |
| base::BindOnce(&AuctionRunner::OnBidsGeneratedAndScored, |
| base::Unretained(this), base::TimeTicks::Now())); |
| } |
| |
| void AuctionRunner::OnBidsGeneratedAndScored(base::TimeTicks start_time, |
| bool success) { |
| if (state_ == State::kFailed) { |
| return; |
| } |
| DCHECK(callback_); |
| // Whether the top-level auction is a server auction. |
| bool is_server_auction = owned_auction_config_->server_response.has_value(); |
| // Whether the top-level auction or any component is a server auction or |
| // on-device auction. |
| auto [contained_server_auction, contained_on_device_auction] = |
| IncludesServerAndOnDeviceAuctions(); |
| |
| blink::InterestGroupSet interest_groups_that_bid; |
| auction_.GetInterestGroupsThatBidAndReportBidCounts(interest_groups_that_bid); |
| if (!success) { |
| FailAuction(/*aborted_by_script=*/false, |
| std::move(interest_groups_that_bid)); |
| return; |
| } |
| |
| DCHECK(auction_.top_bid()->bid->interest_group); |
| const blink::InterestGroup& winning_group = |
| *auction_.top_bid()->bid->interest_group; |
| blink::InterestGroupKey winning_group_key( |
| {winning_group.owner, winning_group.name}); |
| |
| UpdateInterestGroupsPostAuction(); |
| |
| auto errors = auction_.TakeErrors(); |
| // Need these before `CreateReporter()` since the reporter takes over |
| // AuctionConfig and sets it to null, even for all component auctions. |
| auto requested_ad_size = auction_.RequestedAdSize(); |
| auto ad_descriptor_with_replacements = |
| auction_.top_bid()->bid->GetAdDescriptorWithReplacements(); |
| auto component_ad_descriptors_with_replacements = |
| auction_.top_bid()->bid->GetComponentAdDescriptorsWithReplacements(); |
| |
| std::unique_ptr<InterestGroupAuctionReporter> reporter = |
| auction_.CreateReporter( |
| browser_context_, private_aggregation_manager_, |
| ad_auction_page_data_callback_, std::move(owned_auction_config_), |
| main_frame_origin_, frame_origin_, client_security_state_.Clone(), |
| std::move(interest_groups_that_bid)); |
| DCHECK(reporter); |
| |
| if (is_server_auction) { |
| base::UmaHistogramTimes( |
| "Ads.InterestGroup.Auction.ParseBaServerResponseDuration", |
| base::TimeTicks::Now() - start_time); |
| } |
| |
| state_ = State::kSucceeded; |
| std::move(callback_).Run( |
| this, /*aborted_by_script=*/false, std::move(winning_group_key), |
| std::move(requested_ad_size), std::move(ad_descriptor_with_replacements), |
| std::move(component_ad_descriptors_with_replacements), std::move(errors), |
| std::move(reporter), contained_server_auction, |
| contained_on_device_auction, |
| auction_.final_auction_result().value_or( |
| AuctionResult::kDocumentDestruction)); |
| } |
| |
| void AuctionRunner::UpdateInterestGroupsPostAuction() { |
| std::vector<url::Origin> update_owners; |
| auction_.TakePostAuctionUpdateOwners(update_owners); |
| |
| // De-duplicate. |
| std::sort(update_owners.begin(), update_owners.end()); |
| update_owners.erase(std::unique(update_owners.begin(), update_owners.end()), |
| update_owners.end()); |
| |
| // Filter owners not allowed to update. |
| std::erase_if(update_owners, [this](const url::Origin& owner) { |
| return !is_interest_group_api_allowed_callback_.Run( |
| ContentBrowserClient::InterestGroupApiOperation::kUpdate, owner); |
| }); |
| |
| if (base::FeatureList::IsEnabled( |
| features::kFledgeDelayPostAuctionInterestGroupUpdate)) { |
| interest_group_manager_->UpdateInterestGroupsOfOwnersWithDelay( |
| std::move(update_owners), client_security_state_.Clone(), |
| std::move(user_agent_override_), std::move(attestation_callback_), |
| kPostAuctionInterestGroupUpdateDelay); |
| } else { |
| interest_group_manager_->UpdateInterestGroupsOfOwners( |
| std::move(update_owners), client_security_state_.Clone(), |
| std::move(user_agent_override_), attestation_callback_); |
| } |
| } |
| |
| void AuctionRunner::NotifyPromiseResolved( |
| const blink::mojom::AuctionAdConfigAuctionId& auction_id, |
| const blink::AuctionConfig& config) { |
| --promise_fields_in_auction_config_; |
| DCHECK_EQ(promise_fields_in_auction_config_, |
| owned_auction_config_->NumPromises()); |
| |
| if (!auction_id.is_main_auction() && config.NumPromises() == 0) { |
| auction_.NotifyComponentConfigPromisesResolved( |
| auction_id.get_component_auction()); |
| } |
| |
| // This may happen when updating a component auction as well. |
| if (promise_fields_in_auction_config_ == 0) { |
| DCHECK_EQ(config.NumPromises(), 0); |
| auction_.NotifyConfigPromisesResolved(); |
| } |
| } |
| |
| data_decoder::DataDecoder* AuctionRunner::GetDataDecoder( |
| const url::Origin& origin) { |
| AdAuctionPageData* page_data = ad_auction_page_data_callback_.Run(); |
| if (!page_data) { |
| return nullptr; |
| } |
| return page_data->GetDecoderFor(origin); |
| } |
| |
| std::pair<bool, bool> AuctionRunner::IncludesServerAndOnDeviceAuctions() { |
| bool includes_on_device = false; |
| bool includes_server = false; |
| if (owned_auction_config_->server_response) { |
| includes_server = true; |
| } else { |
| includes_on_device = true; |
| } |
| for (const blink::AuctionConfig& config : |
| owned_auction_config_->non_shared_params.component_auctions) { |
| if (config.server_response) { |
| includes_server = true; |
| } else { |
| includes_on_device = true; |
| } |
| } |
| |
| return std::make_pair(includes_server, includes_on_device); |
| } |
| |
| } // namespace content |