blob: 79e65df956f22136ae689c6a1b3e32e600e17dc3 [file] [log] [blame]
// 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/ad_auction_service_impl.h"
#include <map>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include "base/check.h"
#include "base/containers/contains.h"
#include "base/memory/raw_ptr.h"
#include "base/ranges/algorithm.h"
#include "base/strings/strcat.h"
#include "base/time/time.h"
#include "content/browser/devtools/devtools_instrumentation.h"
#include "content/browser/fenced_frame/fenced_frame_url_mapping.h"
#include "content/browser/interest_group/ad_auction_document_data.h"
#include "content/browser/interest_group/ad_auction_result_metrics.h"
#include "content/browser/interest_group/auction_runner.h"
#include "content/browser/interest_group/auction_worklet_manager.h"
#include "content/browser/interest_group/interest_group_manager_impl.h"
#include "content/browser/private_aggregation/private_aggregation_manager.h"
#include "content/browser/renderer_host/page_impl.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/common/aggregatable_report.mojom.h"
#include "content/common/private_aggregation_host.mojom.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/common/content_client.h"
#include "content/services/auction_worklet/public/mojom/private_aggregation_request.mojom.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "net/http/http_response_headers.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "services/metrics/public/cpp/ukm_source_id.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "services/network/public/cpp/wrapper_shared_url_loader_factory.h"
#include "services/network/public/mojom/client_security_state.mojom.h"
#include "services/network/public/mojom/url_loader_factory.mojom.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/fenced_frame/fenced_frame_utils.h"
#include "third_party/blink/public/common/interest_group/auction_config.h"
#include "third_party/blink/public/common/interest_group/interest_group.h"
#include "url/gurl.h"
#include "url/origin.h"
#include "url/scheme_host_port.h"
#include "url/url_constants.h"
namespace content {
namespace {
constexpr base::TimeDelta kMaxExpiry = base::Days(30);
bool IsAdRequestValid(const blink::mojom::AdRequestConfig& config) {
// The ad_request_url origin has to be HTTPS.
if (config.ad_request_url.scheme() != url::kHttpsScheme) {
return false;
}
// At least one adProperties is required to request potential ads.
if (config.ad_properties.size() <= 0) {
return false;
}
// If a fallback source is specified it must be HTTPS.
if (config.fallback_source &&
(config.fallback_source->scheme() != url::kHttpsScheme)) {
return false;
}
return true;
}
// Sends requests for the Private Aggregation API to its manager. Does nothing
// if the manager is unavailable. The map should be keyed by reporting origin
// of the corresponding requests.
void SendPrivateAggregationRequests(
PrivateAggregationManager* private_aggregation_manager,
const url::Origin& main_frame_origin,
std::map<url::Origin,
std::vector<auction_worklet::mojom::PrivateAggregationRequestPtr>>
private_aggregation_requests) {
// Empty vectors should've been filtered out.
DCHECK(base::ranges::none_of(private_aggregation_requests,
[](auto& it) { return it.second.empty(); }));
if (!private_aggregation_manager) {
return;
}
for (auto& [origin, requests] : private_aggregation_requests) {
mojo::Remote<mojom::PrivateAggregationHost> remote;
if (!private_aggregation_manager->BindNewReceiver(
origin, main_frame_origin,
PrivateAggregationBudgetKey::Api::kFledge,
remote.BindNewPipeAndPassReceiver())) {
continue;
}
for (auction_worklet::mojom::PrivateAggregationRequestPtr& request :
requests) {
DCHECK(request);
std::vector<mojom::AggregatableReportHistogramContributionPtr>
contributions;
contributions.push_back(std::move(request->contribution));
remote->SendHistogramReport(std::move(contributions),
request->aggregation_mode,
std::move(request->debug_mode_details));
}
}
}
// Sends reports for a successful auction, both aggregated and event-level, and
// performs interest group updates needed when an auction has a winner. Called
// when a frame navigation maps a winning bid's URN to a URL. Only sends reports
// the first time it's invoked for a given auction, to avoid generating multiple
// reports if the winner of a single auction is used in multiple frames.
//
// `has_sent_reports` True if reports have already been sent for this auction.
// Expected to be false on first invocation, and set to true for future calls.
// Referenced object is expected to be owned by a RepeatingCallback, so it's
// never nullptr.
//
// `private_aggregation_manager` and `interest_group_manager` must be valid and
// non-null. This is ensured by having the URN to URL mapping object, which is
// scoped to a page, own the callback. These two objects are scoped to the
// BrowserContext, which outlives all pages that use it.
//
// `client_security_state` and `trusted_url_loader_factory` are used for
// event-level reports only.
void SendSuccessfulAuctionReportsAndUpdateInterestGroups(
bool* has_sent_reports,
PrivateAggregationManager* private_aggregation_manager,
InterestGroupManagerImpl* interest_group_manager,
const url::Origin& main_frame_origin,
const url::Origin& frame_origin,
const blink::InterestGroupKey& winning_group_key,
const std::string& winning_group_ad_metadata,
std::map<url::Origin,
std::vector<auction_worklet::mojom::PrivateAggregationRequestPtr>>*
private_aggregation_requests,
const std::vector<GURL>& report_urls,
const std::vector<GURL>& debug_loss_report_urls,
const std::vector<GURL>& debug_win_report_urls,
const blink::InterestGroupSet& interest_groups_that_bid,
base::flat_set<std::string> k_anon_keys_to_join,
const network::mojom::ClientSecurityStatePtr& client_security_state,
scoped_refptr<network::WrapperSharedURLLoaderFactory>
trusted_url_loader_factory) {
DCHECK(has_sent_reports);
DCHECK(interest_group_manager);
DCHECK(client_security_state);
if (*has_sent_reports)
return;
*has_sent_reports = true;
interest_group_manager->RecordInterestGroupBids(interest_groups_that_bid);
interest_group_manager->RecordInterestGroupWin(winning_group_key,
winning_group_ad_metadata);
interest_group_manager->RegisterAdKeysAsJoined(
std::move(k_anon_keys_to_join));
SendPrivateAggregationRequests(private_aggregation_manager, main_frame_origin,
std::move(*private_aggregation_requests));
interest_group_manager->EnqueueReports(
report_urls, debug_win_report_urls, debug_loss_report_urls, frame_origin,
client_security_state.Clone(), std::move(trusted_url_loader_factory));
}
} // namespace
// static
void AdAuctionServiceImpl::CreateMojoService(
RenderFrameHost* render_frame_host,
mojo::PendingReceiver<blink::mojom::AdAuctionService> receiver) {
CHECK(render_frame_host);
// The object is bound to the lifetime of `render_frame_host` and the mojo
// connection. See DocumentService for details.
new AdAuctionServiceImpl(*render_frame_host, std::move(receiver));
}
void AdAuctionServiceImpl::JoinInterestGroup(
const blink::InterestGroup& group,
JoinInterestGroupCallback callback) {
if (!JoinOrLeaveApiAllowedFromRenderer(group.owner))
return;
// If the interest group API is not allowed for this origin, report the result
// of the permissions check, but don't actually join the interest group.
// The return value of IsInterestGroupAPIAllowed() is potentially affected by
// a user's browser configuration, which shouldn't be leaked to sites to
// protect against fingerprinting.
bool report_result_only = !IsInterestGroupAPIAllowed(
ContentBrowserClient::InterestGroupApiOperation::kJoin, group.owner);
blink::InterestGroup updated_group = group;
base::Time max_expiry = base::Time::Now() + kMaxExpiry;
if (updated_group.expiry > max_expiry)
updated_group.expiry = max_expiry;
GetInterestGroupManager().CheckPermissionsAndJoinInterestGroup(
std::move(updated_group), main_frame_url_, origin(),
GetFrame()->GetNetworkIsolationKey(), report_result_only,
*GetFrameURLLoaderFactory(), std::move(callback));
}
void AdAuctionServiceImpl::LeaveInterestGroup(
const url::Origin& owner,
const std::string& name,
LeaveInterestGroupCallback callback) {
if (!JoinOrLeaveApiAllowedFromRenderer(owner))
return;
// If the interest group API is not allowed for this origin, report the result
// of the permissions check, but don't actually join the interest group.
// The return value of IsInterestGroupAPIAllowed() is potentially affected by
// a user's browser configuration, which shouldn't be leaked to sites to
// protect against fingerprinting.
bool report_result_only = !IsInterestGroupAPIAllowed(
ContentBrowserClient::InterestGroupApiOperation::kLeave, owner);
GetInterestGroupManager().CheckPermissionsAndLeaveInterestGroup(
blink::InterestGroupKey(owner, name), main_frame_origin_, origin(),
GetFrame()->GetNetworkIsolationKey(), report_result_only,
*GetFrameURLLoaderFactory(), std::move(callback));
}
void AdAuctionServiceImpl::LeaveInterestGroupForDocument() {
// Based on the spec, permission policy is bypassed for leaving implicit
// interest groups.
// If the interest group API is not allowed for this origin do nothing.
if (!IsInterestGroupAPIAllowed(
ContentBrowserClient::InterestGroupApiOperation::kLeave, origin())) {
return;
}
if (origin().scheme() != url::kHttpsScheme) {
ReportBadMessageAndDeleteThis(
"Unexpected request: LeaveInterestGroupForDocument only supported for "
"secure origins");
return;
}
if (!render_frame_host().IsNestedWithinFencedFrame()) {
ReportBadMessageAndDeleteThis(
"Unexpected request: LeaveInterestGroupForDocument only supported "
"within fenced frames");
return;
}
// Get interest group owner and name. AdAuctionDocumentData is created as
// part of navigation to a mapped URN URL. We need to find the top-level
// fenced frame, since only the top-level frame has the document data.
RenderFrameHost* rfh = &render_frame_host();
while (!rfh->IsFencedFrameRoot()) {
rfh = rfh->GetParentOrOuterDocument();
if (!rfh) {
return;
}
}
AdAuctionDocumentData* auction_data =
AdAuctionDocumentData::GetForCurrentDocument(rfh);
if (!auction_data) {
return;
}
if (auction_data->interest_group_owner() != origin()) {
// The ad page calling LeaveAdInterestGroup is not the owner of the group.
return;
}
GetInterestGroupManager().LeaveInterestGroup(
blink::InterestGroupKey(auction_data->interest_group_owner(),
auction_data->interest_group_name()),
main_frame_origin_);
}
void AdAuctionServiceImpl::UpdateAdInterestGroups() {
// If the interest group API is not allowed for this context by Permissions
// Policy, do nothing
if (!render_frame_host().IsFeatureEnabled(
blink::mojom::PermissionsPolicyFeature::kJoinAdInterestGroup)) {
ReportBadMessageAndDeleteThis("Unexpected request");
return;
}
// If the interest group API is not allowed for this origin do nothing.
if (!IsInterestGroupAPIAllowed(
ContentBrowserClient::InterestGroupApiOperation::kUpdate, origin())) {
return;
}
GetInterestGroupManager().UpdateInterestGroupsOfOwner(
origin(), GetClientSecurityState());
}
void AdAuctionServiceImpl::RunAdAuction(
const blink::AuctionConfig& config,
mojo::PendingReceiver<blink::mojom::AbortableAdAuction> abort_receiver,
RunAdAuctionCallback callback) {
// If the run ad auction API is not allowed for this context by Permissions
// Policy, do nothing
if (!render_frame_host().IsFeatureEnabled(
blink::mojom::PermissionsPolicyFeature::kRunAdAuction)) {
ReportBadMessageAndDeleteThis("Unexpected request");
return;
}
auto* auction_result_metrics =
AdAuctionResultMetrics::GetOrCreateForPage(render_frame_host().GetPage());
if (!auction_result_metrics->ShouldRunAuction()) {
std::move(callback).Run(/*manually_aborted=*/false,
/*config=*/absl::nullopt);
return;
}
FencedFrameURLMapping& fenced_frame_urls_map =
GetFrame()->GetPage().fenced_frame_urls_map();
auto urn_uuid = fenced_frame_urls_map.GeneratePendingMappedURN();
// If pending mapped URN cannot be generated due to number of mappings has
// reached limit, stop the auction.
if (!urn_uuid.has_value()) {
std::move(callback).Run(/*manually_aborted=*/false,
/*config=*/absl::nullopt);
return;
}
std::unique_ptr<AuctionRunner> auction = AuctionRunner::CreateAndStart(
&auction_worklet_manager_, &GetInterestGroupManager(), config,
GetClientSecurityState(),
base::BindRepeating(&AdAuctionServiceImpl::IsInterestGroupAPIAllowed,
base::Unretained(this)),
std::move(abort_receiver),
base::BindOnce(&AdAuctionServiceImpl::OnAuctionComplete,
base::Unretained(this), std::move(callback),
std::move(urn_uuid.value())));
AuctionRunner* raw_auction = auction.get();
auctions_.emplace(raw_auction, std::move(auction));
}
namespace {
// Helper class to retrieve the URL that a given URN is mapped to.
class FencedFrameURLMappingObserver
: public FencedFrameURLMapping::MappingResultObserver {
public:
// Retrieves the URL that `urn_url` is mapped to, if any. If `send_reports` is
// true, sends the reports associated with `urn_url`, if there are any.
static absl::optional<GURL> GetURL(RenderFrameHostImpl& render_frame_host,
const GURL& urn_url,
bool send_reports) {
absl::optional<GURL> mapped_url;
FencedFrameURLMappingObserver obs(&mapped_url, send_reports);
content::FencedFrameURLMapping& mapping =
render_frame_host.GetPage().fenced_frame_urls_map();
// FLEDGE URN URLs should already be mapped, so the observer will be called
// synchronously.
mapping.ConvertFencedFrameURNToURL(urn_url, &obs);
if (!obs.called_)
mapping.RemoveObserverForURN(urn_url, &obs);
return mapped_url;
}
private:
FencedFrameURLMappingObserver(absl::optional<GURL>* mapped_url,
bool send_reports)
: mapped_url_(mapped_url), send_reports_(send_reports) {}
~FencedFrameURLMappingObserver() override = default;
void OnFencedFrameURLMappingComplete(
const absl::optional<FencedFrameProperties>& properties) override {
if (properties) {
if (properties->mapped_url_) {
*mapped_url_ = properties->mapped_url_->GetValueIgnoringVisibility();
}
if (send_reports_ && properties->on_navigate_callback_) {
properties->on_navigate_callback_.Run();
}
}
called_ = true;
}
bool called_ = false;
raw_ptr<absl::optional<GURL>> mapped_url_;
bool send_reports_;
};
} // namespace
void AdAuctionServiceImpl::DeprecatedGetURLFromURN(
const GURL& urn_url,
bool send_reports,
DeprecatedGetURLFromURNCallback callback) {
if (!blink::IsValidUrnUuidURL(urn_url)) {
ReportBadMessageAndDeleteThis("Unexpected request: invalid URN");
return;
}
std::move(callback).Run(FencedFrameURLMappingObserver::GetURL(
static_cast<RenderFrameHostImpl&>(render_frame_host()), urn_url,
send_reports));
}
void AdAuctionServiceImpl::DeprecatedReplaceInURN(
const GURL& urn_url,
std::vector<blink::mojom::AdKeywordReplacementPtr> replacements,
DeprecatedReplaceInURNCallback callback) {
if (!blink::IsValidUrnUuidURL(urn_url)) {
ReportBadMessageAndDeleteThis("Unexpected request: invalid URN");
return;
}
std::vector<std::pair<std::string, std::string>> local_replacements;
for (const auto& replacement : replacements) {
if (!(base::StartsWith(replacement->match, "${") &&
base::EndsWith(replacement->match, "}")) &&
!(base::StartsWith(replacement->match, "%%") &&
base::EndsWith(replacement->match, "%%"))) {
ReportBadMessageAndDeleteThis("Unexpected request: bad replacement");
return;
}
local_replacements.emplace_back(std::move(replacement->match),
std::move(replacement->replacement));
}
content::FencedFrameURLMapping& mapping =
static_cast<RenderFrameHostImpl&>(render_frame_host())
.GetPage()
.fenced_frame_urls_map();
mapping.SubstituteMappedURL(urn_url, local_replacements);
std::move(callback).Run();
}
void AdAuctionServiceImpl::CreateAdRequest(
blink::mojom::AdRequestConfigPtr config,
CreateAdRequestCallback callback) {
if (!IsAdRequestValid(*config)) {
std::move(callback).Run(absl::nullopt);
return;
}
// TODO(https://crbug.com/1249186): Actually request Ads and return a guid.
// For now just act like it failed.
std::move(callback).Run(absl::nullopt);
}
void AdAuctionServiceImpl::FinalizeAd(const std::string& ads_guid,
const blink::AuctionConfig& config,
FinalizeAdCallback callback) {
if (ads_guid.empty()) {
ReportBadMessageAndDeleteThis("GUID empty");
return;
}
// TODO(https://crbug.com/1249186): Actually finalize Ad and return an URL.
// For now just act like it failed.
std::move(callback).Run(absl::nullopt);
}
network::mojom::URLLoaderFactory*
AdAuctionServiceImpl::GetFrameURLLoaderFactory() {
if (!frame_url_loader_factory_ || !frame_url_loader_factory_.is_connected()) {
frame_url_loader_factory_.reset();
render_frame_host().CreateNetworkServiceDefaultFactory(
frame_url_loader_factory_.BindNewPipeAndPassReceiver());
}
return frame_url_loader_factory_.get();
}
network::mojom::URLLoaderFactory*
AdAuctionServiceImpl::GetTrustedURLLoaderFactory() {
if (!trusted_url_loader_factory_ ||
!trusted_url_loader_factory_.is_connected()) {
trusted_url_loader_factory_.reset();
mojo::PendingReceiver<network::mojom::URLLoaderFactory> factory_receiver =
trusted_url_loader_factory_.BindNewPipeAndPassReceiver();
// TODO(mmenke): Should this have its own URLLoaderFactoryType? FLEDGE
// requests are very different from subresource requests.
//
// TODO(mmenke): Hook up devtools.
GetContentClient()->browser()->WillCreateURLLoaderFactory(
render_frame_host().GetSiteInstance()->GetBrowserContext(),
&render_frame_host(), render_frame_host().GetProcess()->GetID(),
ContentBrowserClient::URLLoaderFactoryType::kDocumentSubResource,
url::Origin(), /*navigation_id=*/absl::nullopt,
ukm::SourceIdObj::FromInt64(render_frame_host().GetPageUkmSourceId()),
&factory_receiver, /*header_client=*/nullptr,
/*bypass_redirect_checks=*/nullptr, /*disable_secure_dns=*/nullptr,
/*factory_override=*/nullptr);
render_frame_host()
.GetStoragePartition()
->GetURLLoaderFactoryForBrowserProcess()
->Clone(std::move(factory_receiver));
mojo::Remote<network::mojom::URLLoaderFactory> shared_remote;
trusted_url_loader_factory_->Clone(
shared_remote.BindNewPipeAndPassReceiver());
ref_counted_trusted_url_loader_factory_ =
base::MakeRefCounted<network::WrapperSharedURLLoaderFactory>(
std::move(shared_remote));
}
return trusted_url_loader_factory_.get();
}
void AdAuctionServiceImpl::PreconnectSocket(
const GURL& url,
const net::NetworkAnonymizationKey& network_anonymization_key) {
render_frame_host()
.GetStoragePartition()
->GetNetworkContext()
->PreconnectSockets(/*num_streams=*/1, url, /*allow_credentials=*/false,
network_anonymization_key);
}
scoped_refptr<network::WrapperSharedURLLoaderFactory>
AdAuctionServiceImpl::GetRefCountedTrustedURLLoaderFactory() {
GetTrustedURLLoaderFactory();
return ref_counted_trusted_url_loader_factory_;
}
RenderFrameHostImpl* AdAuctionServiceImpl::GetFrame() {
return static_cast<RenderFrameHostImpl*>(&render_frame_host());
}
scoped_refptr<SiteInstance> AdAuctionServiceImpl::GetFrameSiteInstance() {
return render_frame_host().GetSiteInstance();
}
network::mojom::ClientSecurityStatePtr
AdAuctionServiceImpl::GetClientSecurityState() {
return GetFrame()->BuildClientSecurityState();
}
AdAuctionServiceImpl::AdAuctionServiceImpl(
RenderFrameHost& render_frame_host,
mojo::PendingReceiver<blink::mojom::AdAuctionService> receiver)
: DocumentService(render_frame_host, std::move(receiver)),
main_frame_origin_(
render_frame_host.GetMainFrame()->GetLastCommittedOrigin()),
main_frame_url_(render_frame_host.GetMainFrame()->GetLastCommittedURL()),
auction_worklet_manager_(
&GetInterestGroupManager().auction_process_manager(),
GetTopWindowOrigin(),
origin(),
this),
private_aggregation_manager_(PrivateAggregationManager::GetManager(
*render_frame_host.GetBrowserContext())) {}
AdAuctionServiceImpl::~AdAuctionServiceImpl() {
while (!auctions_.empty()) {
// Need to fail all auctions rather than just deleting them, to ensure Mojo
// callbacks from the renderers are invoked. Uninvoked Mojo callbacks may
// not be destroyed before the Mojo pipe is, and the parent DocumentService
// class owns the pipe, so it may still be open at this point.
auctions_.begin()->first->FailAuction(/*manually_aborted=*/false);
}
}
bool AdAuctionServiceImpl::JoinOrLeaveApiAllowedFromRenderer(
const url::Origin& owner) {
// If the interest group API is not allowed for this context by Permissions
// Policy, do nothing
if (!render_frame_host().IsFeatureEnabled(
blink::mojom::PermissionsPolicyFeature::kJoinAdInterestGroup)) {
ReportBadMessageAndDeleteThis("Unexpected request");
return false;
}
if (owner.scheme() != url::kHttpsScheme) {
ReportBadMessageAndDeleteThis(
"Unexpected request: Interest groups may only be owned by secure "
"origins");
return false;
}
if (origin().scheme() != url::kHttpsScheme) {
ReportBadMessageAndDeleteThis(
"Unexpected request: Interest groups may only be joined or left from "
"secure origins");
return false;
}
return true;
}
bool AdAuctionServiceImpl::IsInterestGroupAPIAllowed(
ContentBrowserClient::InterestGroupApiOperation
interest_group_api_operation,
const url::Origin& origin) const {
return GetContentClient()->browser()->IsInterestGroupAPIAllowed(
&render_frame_host(), interest_group_api_operation, main_frame_origin_,
origin);
}
void AdAuctionServiceImpl::OnAuctionComplete(
RunAdAuctionCallback callback,
GURL urn_uuid,
AuctionRunner* auction,
bool manually_aborted,
absl::optional<blink::InterestGroupKey> winning_group_key,
absl::optional<GURL> render_url,
std::vector<GURL> ad_component_urls,
std::string winning_group_ad_metadata,
std::vector<GURL> debug_loss_report_urls,
std::vector<GURL> debug_win_report_urls,
std::map<url::Origin,
std::vector<auction_worklet::mojom::PrivateAggregationRequestPtr>>
private_aggregation_requests,
blink::InterestGroupSet interest_groups_that_bid,
base::flat_set<std::string> k_anon_keys_to_join,
std::vector<std::string> errors,
std::unique_ptr<InterestGroupAuctionReporter> reporter) {
// Remove `auction` from `auctions_` but tmeporarily keep it alive - on
// success, it owns a AuctionWorkletManager::WorkletHandle for the top-level
// auction, which `reporter` can reuse once started. Fine to delete after
// starting the reporter.
auto auction_it = auctions_.find(auction);
DCHECK(auction_it != auctions_.end());
std::unique_ptr<AuctionRunner> owned_auction = std::move(auction_it->second);
auctions_.erase(auction_it);
// Forward debug information to devtools.
for (const std::string& error : errors) {
devtools_instrumentation::LogWorkletMessage(
*GetFrame(), blink::mojom::ConsoleMessageLevel::kError,
base::StrCat({"Worklet error: ", error}));
}
auto* auction_result_metrics =
AdAuctionResultMetrics::GetForPage(render_frame_host().GetPage());
if (!render_url) {
DCHECK(!reporter);
MaybeLogPrivateAggregationFeature(private_aggregation_requests);
if (!manually_aborted) {
SendPrivateAggregationRequests(private_aggregation_manager_,
main_frame_origin_,
std::move(private_aggregation_requests));
GetInterestGroupManager().RegisterAdKeysAsJoined(
std::move(k_anon_keys_to_join));
if (!interest_groups_that_bid.empty()) {
GetInterestGroupManager().RecordInterestGroupBids(
interest_groups_that_bid);
}
}
DCHECK(winning_group_ad_metadata.empty());
std::move(callback).Run(manually_aborted, /*config=*/absl::nullopt);
if (auction_result_metrics) {
// `auction_result_metrics` can be null since PageUserData like
// AdAuctionResultMetrics isn't guaranteed to be destroyed after document
// services like `this`, even though this typically is the case for
// destruction of the RenderFrameHost (except for renderer crashes).
//
// So, we need to guard against this.
auction_result_metrics->ReportAuctionResult(
AdAuctionResultMetrics::AuctionResult::kFailed);
}
GetInterestGroupManager().EnqueueReports(
std::vector<GURL>(), std::vector<GURL>(), debug_loss_report_urls,
origin(), GetClientSecurityState(),
GetRefCountedTrustedURLLoaderFactory());
return;
}
DCHECK(reporter);
// `reporter` has any aggregation requests generated in this case.
DCHECK(private_aggregation_requests.empty());
DCHECK(winning_group_key); // Should always be present with a render_url
DCHECK(!winning_group_ad_metadata.empty());
DCHECK(blink::IsValidFencedFrameURL(*render_url));
DCHECK(urn_uuid.is_valid());
DCHECK(!interest_groups_that_bid.empty());
reporters_.emplace_front(std::move(reporter));
reporters_.front()->Start(base::BindOnce(
&AdAuctionServiceImpl::OnReporterComplete, base::Unretained(this),
reporters_.begin(), std::move(callback), std::move(urn_uuid),
std::move(*winning_group_key), std::move(*render_url),
std::move(ad_component_urls), std::move(winning_group_ad_metadata),
std::move(debug_loss_report_urls), std::move(debug_win_report_urls),
std::move(interest_groups_that_bid), std::move(k_anon_keys_to_join)));
if (auction_result_metrics) {
auction_result_metrics->ReportAuctionResult(
AdAuctionResultMetrics::AuctionResult::kSucceeded);
}
}
void AdAuctionServiceImpl::OnReporterComplete(
ReporterList::iterator reporter_it,
RunAdAuctionCallback callback,
GURL urn_uuid,
blink::InterestGroupKey winning_group_key,
GURL render_url,
std::vector<GURL> ad_component_urls,
std::string winning_group_ad_metadata,
std::vector<GURL> debug_loss_report_urls,
std::vector<GURL> debug_win_report_urls,
blink::InterestGroupSet interest_groups_that_bid,
base::flat_set<std::string> k_anon_keys_to_join) {
// Forward debug information to devtools.
//
// TODO(https://crbug.com/1394777): Ideally this will share code with the
// handling of the errors from the earlier phases of the auction.
InterestGroupAuctionReporter* reporter = reporter_it->get();
for (const std::string& error : reporter->errors()) {
devtools_instrumentation::LogWorkletMessage(
*GetFrame(), blink::mojom::ConsoleMessageLevel::kError,
base::StrCat({"Worklet error: ", error}));
}
auto ad_beacon_map = reporter->TakeAdBeaconMap();
auto report_urls = reporter->TakeReportUrls();
auto private_aggregation_requests =
reporter->TakePrivateAggregationRequests();
MaybeLogPrivateAggregationFeature(private_aggregation_requests);
reporters_.erase(reporter_it);
FencedFrameURLMapping& fenced_frame_urls_map =
GetFrame()->GetPage().fenced_frame_urls_map();
// Need to send reports when the navigation code replaces a winning ad's URN
// with its URL, but should only do so once for the results from a given
// auction. FencedFrameURLMapping takes a RepeatingCallback, as it can map the
// same URN to a URL multiple times. To avoid multiple invocations, pass in a
// base::Owned bool, which is set to true by first invocation.
//
// The callback can also potentially be invoked after the AdAuctionServiceImpl
// is destroyed, in a number of cases, such as running an auction in an
// iframe, closing the iframe, and then navigating another frame to the URN.
// To handle this, the must not dereference `this`, so have to pass everything
// the callback needs directly.
content::AdAuctionData ad_auction_data{winning_group_key.owner,
winning_group_key.name};
blink::FencedFrame::RedactedFencedFrameConfig config =
fenced_frame_urls_map.AssignFencedFrameURLAndInterestGroupInfo(
urn_uuid, render_url, std::move(ad_auction_data),
base::BindRepeating(
&SendSuccessfulAuctionReportsAndUpdateInterestGroups,
/*has_sent_reports=*/base::Owned(std::make_unique<bool>(false)),
private_aggregation_manager_, &GetInterestGroupManager(),
main_frame_origin_, origin(), std::move(winning_group_key),
std::move(winning_group_ad_metadata),
base::Owned(
std::make_unique<
std::map<url::Origin,
std::vector<auction_worklet::mojom::
PrivateAggregationRequestPtr>>>(
std::move(private_aggregation_requests))),
std::move(report_urls), std::move(debug_win_report_urls),
std::move(debug_loss_report_urls),
std::move(interest_groups_that_bid),
std::move(k_anon_keys_to_join), GetClientSecurityState(),
GetRefCountedTrustedURLLoaderFactory()),
ad_component_urls, ad_beacon_map);
std::move(callback).Run(/*manually_aborted=*/false, std::move(config));
}
void AdAuctionServiceImpl::MaybeLogPrivateAggregationFeature(
const std::map<
url::Origin,
std::vector<auction_worklet::mojom::PrivateAggregationRequestPtr>>&
private_aggregation_requests) {
// TODO(crbug.com/1356654): Improve coverage of these use counters, i.e.
// for API usage that does not result in a successful request.
if (!private_aggregation_requests.empty()) {
GetContentClient()->browser()->LogWebFeatureForCurrentPage(
&render_frame_host(),
blink::mojom::WebFeature::kPrivateAggregationApiAll);
GetContentClient()->browser()->LogWebFeatureForCurrentPage(
&render_frame_host(),
blink::mojom::WebFeature::kPrivateAggregationApiFledge);
}
}
InterestGroupManagerImpl& AdAuctionServiceImpl::GetInterestGroupManager()
const {
return *static_cast<InterestGroupManagerImpl*>(
render_frame_host().GetStoragePartition()->GetInterestGroupManager());
}
url::Origin AdAuctionServiceImpl::GetTopWindowOrigin() const {
if (!render_frame_host().GetParent())
return origin();
return render_frame_host().GetMainFrame()->GetLastCommittedOrigin();
}
} // namespace content