blob: a38a39043008546e504fdc8e2038f2f115322499 [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/fenced_frame/fenced_frame_url_mapping.h"
#include <optional>
#include "base/functional/callback.h"
#include "base/memory/scoped_refptr.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "content/browser/fenced_frame/fenced_frame_reporter.h"
#include "content/public/test/test_renderer_host.h"
#include "content/test/fenced_frame_test_utils.h"
#include "net/base/schemeful_site.h"
#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/fenced_frame/fenced_frame_utils.h"
#include "third_party/blink/public/common/interest_group/ad_auction_constants.h"
#include "third_party/blink/public/common/interest_group/ad_display_size.h"
#include "url/gurl.h"
#include "url/origin.h"
#include "url/url_constants.h"
namespace content {
namespace {
// Validates the mapping contained in `pending_ad_components`.
//
// `expected_mapped_ad_descriptors` contains the URLs the first URNs are
// expected to map to, and will be padded with "about:blank" URLs until it's
// blink::MaxAdAuctionAdComponents() in length.
// TODO(crbug.com/40202462): the ShadowDOM implementation is deprecated, and
// these tests should be cleaned up to only reflect MPArch behavior.
void ValidatePendingAdComponentsMap(
FencedFrameURLMapping* fenced_frame_url_mapping,
const std::vector<std::pair<GURL, FencedFrameConfig>>&
nested_urn_config_pairs,
std::vector<blink::AdDescriptor> expected_mapped_ad_descriptors) {
// Get URN array from `nested_urn_config_pairs` and validate the returned
// URN array as much as possible prior to adding the URNs to
// `fenced_frame_url_mapping`, to the extent that's possible. This needs to
// be done before adding the URNs to `fenced_frame_url_mapping` so that this
// loop can make sure the URNs don't exist in the mapping yet.
std::vector<GURL> ad_component_urns;
for (auto& urn_config_pair : nested_urn_config_pairs) {
ad_component_urns.push_back(urn_config_pair.first);
}
ASSERT_EQ(blink::MaxAdAuctionAdComponents(), ad_component_urns.size());
for (size_t i = 0; i < ad_component_urns.size(); ++i) {
// All entries in `ad_component_urns` should be distinct URNs.
EXPECT_EQ(url::kUrnScheme, ad_component_urns[i].scheme_piece());
for (size_t j = 0; j < i; ++j) {
EXPECT_NE(ad_component_urns[j], ad_component_urns[i]);
}
// The URNs should not yet be in `fenced_frame_url_mapping`, so they can
// safely be added to it.
TestFencedFrameURLMappingResultObserver observer;
fenced_frame_url_mapping->ConvertFencedFrameURNToURL(ad_component_urns[i],
&observer);
EXPECT_TRUE(observer.mapping_complete_observed());
EXPECT_FALSE(observer.mapped_url());
EXPECT_FALSE(observer.nested_urn_config_pairs());
}
// Add the `nested_urn_config_pairs` to a mapping.
FencedFrameURLMapping new_frame_url_mapping;
fenced_frame_url_mapping = &new_frame_url_mapping;
fenced_frame_url_mapping->ImportPendingAdComponents(nested_urn_config_pairs);
// Now validate the changes made to `fenced_frame_url_mapping`.
for (size_t i = 0; i < ad_component_urns.size(); ++i) {
// The URNs should now be in `fenced_frame_url_mapping`. Look up the
// corresponding URL, and make sure it's mapped to the correct URL.
TestFencedFrameURLMappingResultObserver observer;
fenced_frame_url_mapping->ConvertFencedFrameURNToURL(ad_component_urns[i],
&observer);
EXPECT_TRUE(observer.mapping_complete_observed());
if (i < expected_mapped_ad_descriptors.size()) {
EXPECT_EQ(expected_mapped_ad_descriptors[i].url, observer.mapped_url());
} else {
EXPECT_EQ(GURL(url::kAboutBlankURL), observer.mapped_url());
}
// Each added URN should also have a populated
// `observer.nested_urn_config_pairs()` structure, to prevent ads from
// knowing if they were loaded in a fenced frame as an ad component or as
// the main ad. Any information passed to ads violates the k-anonymity
// requirement.
EXPECT_TRUE(observer.nested_urn_config_pairs());
// If this it not an about:blank URL, then when loaded in a fenced frame,
// it can recursively access its own nested ad components array, so
// recursively check those as well.
if (*observer.mapped_url() != GURL(url::kAboutBlankURL)) {
// Nested URL maps map everything to "about:blank". They exist solely so
// that top-level and nested component ads can't tell which one they
// are, to prevent smuggling data based on whether an ad is loaded in a
// top-level ad URL or a component ad URL.
ValidatePendingAdComponentsMap(fenced_frame_url_mapping,
*observer.nested_urn_config_pairs(),
/*expected_mapped_ad_descriptors=*/
{});
}
}
}
GURL GenerateAndVerifyPendingMappedURN(
FencedFrameURLMapping* fenced_frame_url_mapping) {
std::optional<GURL> pending_urn =
fenced_frame_url_mapping->GeneratePendingMappedURN();
EXPECT_TRUE(pending_urn.has_value());
EXPECT_TRUE(pending_urn->is_valid());
return pending_urn.value();
}
class FencedFrameURLMappingTest : public RenderViewHostTestHarness {
public:
FencedFrameURLMappingTest() = default;
// Creates a dummy FencedFrameReporter that will never used to send any
// reports. Tests only check for pointer equality, so the configuration of the
// FencedFrameReporter does not matter.
scoped_refptr<FencedFrameReporter> CreateReporter() {
return FencedFrameReporter::CreateForFledge(
base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
nullptr),
/*browser_context=*/browser_context(),
/*direct_seller_is_seller=*/false,
/*private_aggregation_manager=*/nullptr,
/*main_frame_origin=*/url::Origin(),
/*winner_origin=*/url::Origin(),
/*winner_aggregation_coordinator_origin=*/std::nullopt);
}
const base::HistogramTester& histogram_tester() const {
return histogram_tester_;
}
private:
base::HistogramTester histogram_tester_;
};
} // namespace
TEST_F(FencedFrameURLMappingTest, AddAndConvert) {
FencedFrameURLMapping fenced_frame_url_mapping;
GURL test_url("https://foo.test");
std::optional<GURL> urn_uuid =
fenced_frame_url_mapping.AddFencedFrameURLForTesting(test_url);
EXPECT_TRUE(urn_uuid.has_value());
TestFencedFrameURLMappingResultObserver observer;
fenced_frame_url_mapping.ConvertFencedFrameURNToURL(urn_uuid.value(),
&observer);
EXPECT_TRUE(observer.mapping_complete_observed());
EXPECT_EQ(test_url, observer.mapped_url());
EXPECT_EQ(std::nullopt, observer.nested_urn_config_pairs());
}
TEST_F(FencedFrameURLMappingTest, NonExistentUUID) {
FencedFrameURLMapping fenced_frame_url_mapping;
GURL urn_uuid("urn:uuid:c36973b5-e5d9-de59-e4c4-364f137b3c7a");
TestFencedFrameURLMappingResultObserver observer;
fenced_frame_url_mapping.ConvertFencedFrameURNToURL(urn_uuid, &observer);
EXPECT_TRUE(observer.mapping_complete_observed());
EXPECT_EQ(std::nullopt, observer.mapped_url());
EXPECT_EQ(std::nullopt, observer.nested_urn_config_pairs());
}
TEST_F(FencedFrameURLMappingTest, PendingMappedUUID) {
FencedFrameURLMapping fenced_frame_url_mapping;
const GURL urn_uuid1 =
GenerateAndVerifyPendingMappedURN(&fenced_frame_url_mapping);
const GURL urn_uuid2 =
GenerateAndVerifyPendingMappedURN(&fenced_frame_url_mapping);
TestFencedFrameURLMappingResultObserver observer1;
fenced_frame_url_mapping.ConvertFencedFrameURNToURL(urn_uuid1, &observer1);
EXPECT_FALSE(observer1.mapping_complete_observed());
TestFencedFrameURLMappingResultObserver observer2;
fenced_frame_url_mapping.ConvertFencedFrameURNToURL(urn_uuid2, &observer2);
EXPECT_FALSE(observer2.mapping_complete_observed());
net::SchemefulSite shared_storage_site(GURL("https://bar.com"));
GURL mapped_url = GURL("https://foo.com");
// Two SharedStorageBudgetMetadata for the same site can happen if the same
// blink::Document invokes window.sharedStorage.runURLSelectionOperation()
// twice. Each call will generate a distinct URN. And if the input urls have
// different size, the budget_to_charge (i.e. log(n)) will be also different.
SimulateSharedStorageURNMappingComplete(fenced_frame_url_mapping, urn_uuid1,
mapped_url, shared_storage_site,
/*budget_to_charge=*/2.0);
SimulateSharedStorageURNMappingComplete(fenced_frame_url_mapping, urn_uuid2,
mapped_url, shared_storage_site,
/*budget_to_charge=*/3.0);
EXPECT_TRUE(observer1.mapping_complete_observed());
EXPECT_EQ(mapped_url, observer1.mapped_url());
EXPECT_EQ(std::nullopt, observer1.nested_urn_config_pairs());
EXPECT_TRUE(observer2.mapping_complete_observed());
EXPECT_EQ(mapped_url, observer2.mapped_url());
EXPECT_EQ(std::nullopt, observer2.nested_urn_config_pairs());
SharedStorageBudgetMetadata* metadata1 =
fenced_frame_url_mapping.GetSharedStorageBudgetMetadataForTesting(
urn_uuid1);
EXPECT_TRUE(metadata1);
EXPECT_EQ(metadata1->site, shared_storage_site);
EXPECT_DOUBLE_EQ(metadata1->budget_to_charge, 2.0);
SharedStorageBudgetMetadata* metadata2 =
fenced_frame_url_mapping.GetSharedStorageBudgetMetadataForTesting(
urn_uuid2);
EXPECT_TRUE(metadata2);
EXPECT_EQ(metadata2->site, shared_storage_site);
EXPECT_DOUBLE_EQ(metadata2->budget_to_charge, 3.0);
}
TEST_F(FencedFrameURLMappingTest, RemoveObserverOnPendingMappedUUID) {
FencedFrameURLMapping fenced_frame_url_mapping;
const GURL urn_uuid =
GenerateAndVerifyPendingMappedURN(&fenced_frame_url_mapping);
TestFencedFrameURLMappingResultObserver observer;
fenced_frame_url_mapping.ConvertFencedFrameURNToURL(urn_uuid, &observer);
EXPECT_FALSE(observer.mapping_complete_observed());
fenced_frame_url_mapping.RemoveObserverForURN(urn_uuid, &observer);
SimulateSharedStorageURNMappingComplete(
fenced_frame_url_mapping, urn_uuid,
/*mapped_url=*/GURL("https://foo.com"),
/*shared_storage_site=*/
net::SchemefulSite::Deserialize("https://bar.com"),
/*budget_to_charge=*/2.0);
EXPECT_FALSE(observer.mapping_complete_observed());
}
TEST_F(FencedFrameURLMappingTest, RegisterTwoObservers) {
FencedFrameURLMapping fenced_frame_url_mapping;
const GURL urn_uuid =
GenerateAndVerifyPendingMappedURN(&fenced_frame_url_mapping);
TestFencedFrameURLMappingResultObserver observer1;
fenced_frame_url_mapping.ConvertFencedFrameURNToURL(urn_uuid, &observer1);
EXPECT_FALSE(observer1.mapping_complete_observed());
TestFencedFrameURLMappingResultObserver observer2;
fenced_frame_url_mapping.ConvertFencedFrameURNToURL(urn_uuid, &observer2);
EXPECT_FALSE(observer2.mapping_complete_observed());
SimulateSharedStorageURNMappingComplete(
fenced_frame_url_mapping, urn_uuid,
/*mapped_url=*/GURL("https://foo.com"),
/*shared_storage_site=*/
net::SchemefulSite::Deserialize("https://bar.com"),
/*budget_to_charge=*/2.0);
EXPECT_TRUE(observer1.mapping_complete_observed());
EXPECT_EQ(GURL("https://foo.com"), observer1.mapped_url());
EXPECT_EQ(std::nullopt, observer1.nested_urn_config_pairs());
EXPECT_TRUE(observer2.mapping_complete_observed());
EXPECT_EQ(GURL("https://foo.com"), observer2.mapped_url());
EXPECT_EQ(std::nullopt, observer2.nested_urn_config_pairs());
}
// Test the case `ad_component_descriptors` is empty. In this case, it should
// be filled with URNs that are mapped to about:blank.
TEST_F(FencedFrameURLMappingTest,
AssignFencedFrameURLAndInterestGroupInfoNoAdComponentsUrls) {
FencedFrameURLMapping fenced_frame_url_mapping;
GURL top_level_url("https://foo.test");
url::Origin interest_group_owner = url::Origin::Create(top_level_url);
std::string interest_group_name = "bars";
std::vector<blink::AdDescriptor> ad_component_descriptors;
auto urn_uuid = GenerateAndVerifyPendingMappedURN(&fenced_frame_url_mapping);
bool on_navigate_callback_invoked = false;
base::RepeatingCallback on_navigate_callback =
base::BindLambdaForTesting([&on_navigate_callback_invoked]() {
EXPECT_FALSE(on_navigate_callback_invoked);
on_navigate_callback_invoked = true;
});
fenced_frame_url_mapping.AssignFencedFrameURLAndInterestGroupInfo(
urn_uuid, /*container_size=*/std::nullopt,
blink::AdDescriptor(top_level_url),
{interest_group_owner, interest_group_name}, on_navigate_callback,
ad_component_descriptors);
TestFencedFrameURLMappingResultObserver observer;
fenced_frame_url_mapping.ConvertFencedFrameURNToURL(urn_uuid, &observer);
EXPECT_TRUE(observer.mapping_complete_observed());
EXPECT_EQ(top_level_url, observer.mapped_url());
EXPECT_EQ(interest_group_owner,
observer.ad_auction_data()->interest_group_owner);
EXPECT_EQ(interest_group_name,
observer.ad_auction_data()->interest_group_name);
EXPECT_TRUE(observer.nested_urn_config_pairs());
ASSERT_TRUE(observer.on_navigate_callback());
EXPECT_FALSE(on_navigate_callback_invoked);
observer.on_navigate_callback().Run();
EXPECT_TRUE(on_navigate_callback_invoked);
ValidatePendingAdComponentsMap(&fenced_frame_url_mapping,
*observer.nested_urn_config_pairs(),
/*expected_mapped_ad_descriptors=*/{});
histogram_tester().ExpectTotalCount(
blink::kSameSiteAdComponentsMaxCountForWinningBidHistogram, 0);
histogram_tester().ExpectTotalCount(
blink::kAdComponentsCountForWinningBidHistogram, 1);
histogram_tester().ExpectBucketCount(
blink::kAdComponentsCountForWinningBidHistogram, 0, 1);
}
// Test the case `ad_component_descriptors` has a single URL.
TEST_F(FencedFrameURLMappingTest,
AssignFencedFrameURLAndInterestGroupInfoOneAdComponentUrl) {
FencedFrameURLMapping fenced_frame_url_mapping;
GURL top_level_url("https://foo.test");
url::Origin interest_group_owner = url::Origin::Create(top_level_url);
std::string interest_group_name = "bars";
std::vector<blink::AdDescriptor> ad_component_descriptors{
blink::AdDescriptor(GURL("https://bar.test"))};
auto urn_uuid = GenerateAndVerifyPendingMappedURN(&fenced_frame_url_mapping);
fenced_frame_url_mapping.AssignFencedFrameURLAndInterestGroupInfo(
urn_uuid, /*container_size=*/std::nullopt,
blink::AdDescriptor(top_level_url),
{interest_group_owner, interest_group_name},
/*on_navigate_callback=*/base::RepeatingClosure(),
ad_component_descriptors);
TestFencedFrameURLMappingResultObserver observer;
fenced_frame_url_mapping.ConvertFencedFrameURNToURL(urn_uuid, &observer);
EXPECT_TRUE(observer.mapping_complete_observed());
EXPECT_EQ(top_level_url, observer.mapped_url());
EXPECT_EQ(interest_group_owner,
observer.ad_auction_data()->interest_group_owner);
EXPECT_EQ(interest_group_name,
observer.ad_auction_data()->interest_group_name);
EXPECT_TRUE(observer.nested_urn_config_pairs());
EXPECT_FALSE(observer.on_navigate_callback());
ValidatePendingAdComponentsMap(&fenced_frame_url_mapping,
*observer.nested_urn_config_pairs(),
ad_component_descriptors);
histogram_tester().ExpectTotalCount(
blink::kSameSiteAdComponentsMaxCountForWinningBidHistogram, 1);
histogram_tester().ExpectBucketCount(
blink::kSameSiteAdComponentsMaxCountForWinningBidHistogram, 1, 1);
histogram_tester().ExpectTotalCount(
blink::kAdComponentsCountForWinningBidHistogram, 1);
histogram_tester().ExpectBucketCount(
blink::kAdComponentsCountForWinningBidHistogram, 1, 1);
}
// Test the case `ad_component_descriptors` has the maximum number of allowed
// ad component URLs.
TEST_F(FencedFrameURLMappingTest,
AssignFencedFrameURLAndInterestGroupInfoMaxAdComponentUrl) {
FencedFrameURLMapping fenced_frame_url_mapping;
GURL top_level_url("https://foo.test");
url::Origin interest_group_owner = url::Origin::Create(top_level_url);
std::string interest_group_name = "bars";
std::vector<blink::AdDescriptor> ad_component_descriptors;
const size_t kMaxAdAuctionAdComponents = blink::MaxAdAuctionAdComponents();
for (size_t i = 0; i < kMaxAdAuctionAdComponents; ++i) {
ad_component_descriptors.emplace_back(
GURL(base::StringPrintf("https://%zu.test/", i)));
}
auto urn_uuid = GenerateAndVerifyPendingMappedURN(&fenced_frame_url_mapping);
fenced_frame_url_mapping.AssignFencedFrameURLAndInterestGroupInfo(
urn_uuid, /*container_size=*/std::nullopt,
blink::AdDescriptor(top_level_url),
{interest_group_owner, interest_group_name},
/*on_navigate_callback=*/base::RepeatingClosure(),
ad_component_descriptors);
TestFencedFrameURLMappingResultObserver observer;
fenced_frame_url_mapping.ConvertFencedFrameURNToURL(urn_uuid, &observer);
EXPECT_TRUE(observer.mapping_complete_observed());
EXPECT_EQ(top_level_url, observer.mapped_url());
EXPECT_EQ(interest_group_owner,
observer.ad_auction_data()->interest_group_owner);
EXPECT_EQ(interest_group_name,
observer.ad_auction_data()->interest_group_name);
EXPECT_TRUE(observer.nested_urn_config_pairs());
EXPECT_FALSE(observer.on_navigate_callback());
ValidatePendingAdComponentsMap(&fenced_frame_url_mapping,
*observer.nested_urn_config_pairs(),
ad_component_descriptors);
histogram_tester().ExpectTotalCount(
blink::kSameSiteAdComponentsMaxCountForWinningBidHistogram, 1);
// All of the ad component descriptors are cross-site to one another.
histogram_tester().ExpectBucketCount(
blink::kSameSiteAdComponentsMaxCountForWinningBidHistogram, 1, 1);
histogram_tester().ExpectTotalCount(
blink::kAdComponentsCountForWinningBidHistogram, 1);
histogram_tester().ExpectBucketCount(
blink::kAdComponentsCountForWinningBidHistogram,
kMaxAdAuctionAdComponents, 1);
}
// Test the case `ad_component_descriptors` has multiple ad component URLs from
// different sites.
TEST_F(FencedFrameURLMappingTest,
AssignFencedFrameURLAndInterestGroupInfoCrossSiteAdComponentUrl) {
FencedFrameURLMapping fenced_frame_url_mapping;
GURL top_level_url("https://foo.test");
url::Origin interest_group_owner = url::Origin::Create(top_level_url);
std::string interest_group_name = "bars";
std::vector<blink::AdDescriptor> ad_component_descriptors;
ad_component_descriptors.emplace_back(GURL("https://a.test/"));
ad_component_descriptors.emplace_back(GURL("https://a.test/"));
ad_component_descriptors.emplace_back(GURL("https://b.test/"));
auto urn_uuid = GenerateAndVerifyPendingMappedURN(&fenced_frame_url_mapping);
fenced_frame_url_mapping.AssignFencedFrameURLAndInterestGroupInfo(
urn_uuid, /*container_size=*/std::nullopt,
blink::AdDescriptor(top_level_url),
{interest_group_owner, interest_group_name},
/*on_navigate_callback=*/base::RepeatingClosure(),
ad_component_descriptors);
TestFencedFrameURLMappingResultObserver observer;
fenced_frame_url_mapping.ConvertFencedFrameURNToURL(urn_uuid, &observer);
EXPECT_TRUE(observer.mapping_complete_observed());
EXPECT_EQ(top_level_url, observer.mapped_url());
EXPECT_EQ(interest_group_owner,
observer.ad_auction_data()->interest_group_owner);
EXPECT_EQ(interest_group_name,
observer.ad_auction_data()->interest_group_name);
EXPECT_TRUE(observer.nested_urn_config_pairs());
EXPECT_FALSE(observer.on_navigate_callback());
ValidatePendingAdComponentsMap(&fenced_frame_url_mapping,
*observer.nested_urn_config_pairs(),
ad_component_descriptors);
histogram_tester().ExpectTotalCount(
blink::kSameSiteAdComponentsMaxCountForWinningBidHistogram, 1);
histogram_tester().ExpectBucketCount(
blink::kSameSiteAdComponentsMaxCountForWinningBidHistogram, 2, 1);
histogram_tester().ExpectTotalCount(
blink::kAdComponentsCountForWinningBidHistogram, 1);
histogram_tester().ExpectBucketCount(
blink::kAdComponentsCountForWinningBidHistogram, 3, 1);
}
// Test the case `ad_component_descriptors` has multiple ad component URLs from
// different sites.
TEST_F(FencedFrameURLMappingTest,
AssignFencedFrameURLAndInterestGroupInfoParentAndComponentSameSiteUrl) {
FencedFrameURLMapping fenced_frame_url_mapping;
GURL top_level_url("https://a.test");
url::Origin interest_group_owner = url::Origin::Create(top_level_url);
std::string interest_group_name = "bars";
std::vector<blink::AdDescriptor> ad_component_descriptors;
ad_component_descriptors.emplace_back(GURL("https://a.test/"));
ad_component_descriptors.emplace_back(GURL("https://a.test/"));
ad_component_descriptors.emplace_back(GURL("https://b.test/"));
auto urn_uuid = GenerateAndVerifyPendingMappedURN(&fenced_frame_url_mapping);
fenced_frame_url_mapping.AssignFencedFrameURLAndInterestGroupInfo(
urn_uuid, /*container_size=*/std::nullopt,
blink::AdDescriptor(top_level_url),
{interest_group_owner, interest_group_name},
/*on_navigate_callback=*/base::RepeatingClosure(),
ad_component_descriptors);
TestFencedFrameURLMappingResultObserver observer;
fenced_frame_url_mapping.ConvertFencedFrameURNToURL(urn_uuid, &observer);
EXPECT_TRUE(observer.mapping_complete_observed());
EXPECT_EQ(top_level_url, observer.mapped_url());
EXPECT_EQ(interest_group_owner,
observer.ad_auction_data()->interest_group_owner);
EXPECT_EQ(interest_group_name,
observer.ad_auction_data()->interest_group_name);
EXPECT_TRUE(observer.nested_urn_config_pairs());
EXPECT_FALSE(observer.on_navigate_callback());
ValidatePendingAdComponentsMap(&fenced_frame_url_mapping,
*observer.nested_urn_config_pairs(),
ad_component_descriptors);
histogram_tester().ExpectTotalCount(
blink::kSameSiteAdComponentsMaxCountForWinningBidHistogram, 1);
// In this case, the parent/top-level ad is same-site to some of the ad
// components, so it should contribute to the same-site max count.
histogram_tester().ExpectBucketCount(
blink::kSameSiteAdComponentsMaxCountForWinningBidHistogram, 3, 1);
histogram_tester().ExpectTotalCount(
blink::kAdComponentsCountForWinningBidHistogram, 1);
histogram_tester().ExpectBucketCount(
blink::kAdComponentsCountForWinningBidHistogram, 3, 1);
}
// Test the case `ad_component_descriptors` has the maximum number of allowed
// ad component URLs, and they're all identical. The main purpose of this test
// is to make sure they receive unique URNs, despite being identical URLs.
TEST_F(FencedFrameURLMappingTest,
AssignFencedFrameURLAndInterestGroupInfoMaxIdenticalAdComponentUrl) {
FencedFrameURLMapping fenced_frame_url_mapping;
GURL top_level_url("https://foo.test");
url::Origin interest_group_owner = url::Origin::Create(top_level_url);
std::string interest_group_name = "bars";
std::vector<blink::AdDescriptor> ad_component_descriptors(
blink::MaxAdAuctionAdComponents(),
blink::AdDescriptor(GURL("https://bar.test/")));
auto urn_uuid = GenerateAndVerifyPendingMappedURN(&fenced_frame_url_mapping);
fenced_frame_url_mapping.AssignFencedFrameURLAndInterestGroupInfo(
urn_uuid, /*container_size=*/std::nullopt,
blink::AdDescriptor(top_level_url),
{interest_group_owner, interest_group_name},
/*on_navigate_callback=*/base::RepeatingClosure(),
ad_component_descriptors);
TestFencedFrameURLMappingResultObserver observer;
fenced_frame_url_mapping.ConvertFencedFrameURNToURL(urn_uuid, &observer);
EXPECT_TRUE(observer.mapping_complete_observed());
EXPECT_EQ(top_level_url, observer.mapped_url());
EXPECT_EQ(interest_group_owner,
observer.ad_auction_data()->interest_group_owner);
EXPECT_EQ(interest_group_name,
observer.ad_auction_data()->interest_group_name);
EXPECT_TRUE(observer.nested_urn_config_pairs());
EXPECT_FALSE(observer.on_navigate_callback());
ValidatePendingAdComponentsMap(
&fenced_frame_url_mapping, *observer.nested_urn_config_pairs(),
/*expected_mapped_ad_descriptors=*/ad_component_descriptors);
histogram_tester().ExpectTotalCount(
blink::kSameSiteAdComponentsMaxCountForWinningBidHistogram, 1);
histogram_tester().ExpectBucketCount(
blink::kSameSiteAdComponentsMaxCountForWinningBidHistogram,
blink::MaxAdAuctionAdComponents(), 1);
histogram_tester().ExpectTotalCount(
blink::kAdComponentsCountForWinningBidHistogram, 1);
histogram_tester().ExpectBucketCount(
blink::kAdComponentsCountForWinningBidHistogram,
blink::MaxAdAuctionAdComponents(), 1);
}
// Test the case `ad_component_descriptors` has a single URL.
TEST_F(FencedFrameURLMappingTest, SubstituteFencedFrameURLs) {
FencedFrameURLMapping fenced_frame_url_mapping;
GURL top_level_url(
"https://foo.test/page?%%TT%%${oo%%}p%%${p%%${%%l}%%%%%%%%evl%%");
url::Origin interest_group_owner = url::Origin::Create(top_level_url);
std::string interest_group_name = "bars";
std::vector<blink::AdDescriptor> ad_component_descriptors{
blink::AdDescriptor(GURL("https://bar.test/page?${REPLACED}"))};
auto urn_uuid = GenerateAndVerifyPendingMappedURN(&fenced_frame_url_mapping);
fenced_frame_url_mapping.AssignFencedFrameURLAndInterestGroupInfo(
urn_uuid, /*container_size=*/std::nullopt,
blink::AdDescriptor(top_level_url),
{interest_group_owner, interest_group_name},
/*on_navigate_callback=*/base::RepeatingClosure(),
ad_component_descriptors);
fenced_frame_url_mapping.SubstituteMappedURL(
urn_uuid,
{{"%%notPresent%%",
"not inserted"}, // replacements not present not inserted
{"%%TT%%", "t"}, // %% replacement works
{"${oo%%}", "o"}, // mixture of sequences works
{"%%${p%%${%%l}%%%%%%", "_l"}, // mixture of sequences works
{"${%%l}", "Don't replace"}, // earlier replacements take precedence
{"%%evl%%", "evel_%%still_got_it%%"}, // output can contain
// replacement sequences
{"%%still_got_it%%",
"not replaced"}, // output of replacement is not replaced
{"${REPLACED}", "component"}}); // replacements affect components
TestFencedFrameURLMappingResultObserver observer;
fenced_frame_url_mapping.ConvertFencedFrameURNToURL(urn_uuid, &observer);
EXPECT_TRUE(observer.mapping_complete_observed());
EXPECT_EQ(GURL("https://foo.test/page?top_level_%%still_got_it%%"),
observer.mapped_url());
EXPECT_EQ(interest_group_owner,
observer.ad_auction_data()->interest_group_owner);
EXPECT_EQ(interest_group_name,
observer.ad_auction_data()->interest_group_name);
EXPECT_TRUE(observer.nested_urn_config_pairs());
EXPECT_FALSE(observer.on_navigate_callback());
std::vector<blink::AdDescriptor> expected_ad_component_descriptors{
blink::AdDescriptor(GURL("https://bar.test/page?component"))};
ValidatePendingAdComponentsMap(&fenced_frame_url_mapping,
*observer.nested_urn_config_pairs(),
expected_ad_component_descriptors);
histogram_tester().ExpectTotalCount(
blink::kSameSiteAdComponentsMaxCountForWinningBidHistogram, 1);
histogram_tester().ExpectBucketCount(
blink::kSameSiteAdComponentsMaxCountForWinningBidHistogram, 1, 1);
histogram_tester().ExpectTotalCount(
blink::kAdComponentsCountForWinningBidHistogram, 1);
histogram_tester().ExpectBucketCount(
blink::kAdComponentsCountForWinningBidHistogram, 1, 1);
}
// Test the correctness of the URN format. The URN is expected to be in the
// format "urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" as per RFC-4122.
TEST_F(FencedFrameURLMappingTest, HasCorrectFormat) {
FencedFrameURLMapping fenced_frame_url_mapping;
GURL test_url("https://foo.test");
std::optional<GURL> urn_uuid =
fenced_frame_url_mapping.AddFencedFrameURLForTesting(test_url);
EXPECT_TRUE(urn_uuid.has_value());
std::string spec = urn_uuid->spec();
ASSERT_TRUE(base::StartsWith(
spec, "urn:uuid:", base::CompareCase::INSENSITIVE_ASCII));
EXPECT_EQ(spec.at(17), '-');
EXPECT_EQ(spec.at(22), '-');
EXPECT_EQ(spec.at(27), '-');
EXPECT_EQ(spec.at(32), '-');
EXPECT_TRUE(blink::IsValidUrnUuidURL(urn_uuid.value()));
}
// Test that reporting metadata gets saved successfully.
TEST_F(FencedFrameURLMappingTest, ReportingMetadataSuccess) {
FencedFrameURLMapping fenced_frame_url_mapping;
scoped_refptr<FencedFrameReporter> fenced_frame_reporter = CreateReporter();
GURL test_url("https://foo.test");
std::optional<GURL> urn_uuid =
fenced_frame_url_mapping.AddFencedFrameURLForTesting(
test_url, fenced_frame_reporter);
EXPECT_TRUE(urn_uuid.has_value());
EXPECT_TRUE(urn_uuid->is_valid());
TestFencedFrameURLMappingResultObserver observer;
fenced_frame_url_mapping.ConvertFencedFrameURNToURL(urn_uuid.value(),
&observer);
EXPECT_TRUE(observer.mapping_complete_observed());
EXPECT_EQ(fenced_frame_reporter.get(), observer.fenced_frame_reporter());
}
// Test that reporting metadata gets saved successfully.
TEST_F(FencedFrameURLMappingTest, ReporterSuccessWithInterestGroupInfo) {
FencedFrameURLMapping fenced_frame_url_mapping;
scoped_refptr<FencedFrameReporter> fenced_frame_reporter = CreateReporter();
GURL top_level_url("https://bar.test");
url::Origin interest_group_owner = url::Origin::Create(top_level_url);
std::string interest_group_name = "bars";
std::vector<blink::AdDescriptor> ad_component_descriptors;
auto urn_uuid = GenerateAndVerifyPendingMappedURN(&fenced_frame_url_mapping);
fenced_frame_url_mapping.AssignFencedFrameURLAndInterestGroupInfo(
urn_uuid, /*container_size=*/std::nullopt,
blink::AdDescriptor(top_level_url),
{interest_group_owner, interest_group_name},
/*on_navigate_callback=*/base::RepeatingClosure(),
ad_component_descriptors, fenced_frame_reporter);
TestFencedFrameURLMappingResultObserver observer;
fenced_frame_url_mapping.ConvertFencedFrameURNToURL(urn_uuid, &observer);
EXPECT_TRUE(observer.mapping_complete_observed());
EXPECT_EQ(fenced_frame_reporter.get(), observer.fenced_frame_reporter());
histogram_tester().ExpectTotalCount(
blink::kSameSiteAdComponentsMaxCountForWinningBidHistogram, 0);
histogram_tester().ExpectTotalCount(
blink::kAdComponentsCountForWinningBidHistogram, 1);
histogram_tester().ExpectBucketCount(
blink::kAdComponentsCountForWinningBidHistogram, 0, 1);
}
// Test that number of urn mappings limit is enforced for pending mapped urn
// generation.
TEST_F(FencedFrameURLMappingTest, ExceedNumOfUrnMappingsLimitFailsAddURL) {
FencedFrameURLMapping fenced_frame_url_mapping;
// Able to generate pending mapped URN when map is not full.
EXPECT_TRUE(fenced_frame_url_mapping.GeneratePendingMappedURN().has_value());
// Able to add urn mapping when map is not full.
const GURL test_url("https://test.test");
std::optional<GURL> urn_uuid =
fenced_frame_url_mapping.AddFencedFrameURLForTesting(test_url);
EXPECT_TRUE(urn_uuid.has_value());
// Fill the map until its size reaches the limit.
FencedFrameURLMappingTestPeer fenced_frame_url_mapping_test_peer(
&fenced_frame_url_mapping);
GURL url("https://a.test");
fenced_frame_url_mapping_test_peer.FillMap(url);
// Cannot generate pending mapped URN when map is full.
EXPECT_FALSE(fenced_frame_url_mapping.GeneratePendingMappedURN().has_value());
// Subsequent additions of urn mapping should fail when map is full.
const GURL extra_url("https://extra.test");
std::optional<GURL> extra_urn_uuid =
fenced_frame_url_mapping.AddFencedFrameURLForTesting(extra_url);
EXPECT_FALSE(extra_urn_uuid.has_value());
}
} // namespace content