Skip to content

Commit

Permalink
[shared storage] Store SharedStorageBudgetMetadata to FFURLMapping::M…
Browse files Browse the repository at this point in the history
…apInfo

- Implement the calculation of shared storage budget. Store it into
FFURLMapping::MapInfo. (Step 2 in the design doc)
- Also a relevant behavior change: if the shared storage’s url mapping
fails, the default URL at index 0 will be returned and
`budget_to_charge` will be set to to Min(1, log(n)) (as opposed to the
pre-existing way to return nullopt to fail the FF navigation). So from
FFURLMapping’s perspective the mapping shall always succeed.

Design doc:
https://docs.google.com/document/d/1KPRxM4tId0R0uIdJH4PEgSiHgx3IMRlQyzUffh_0FeE

Bug: 1218540
Change-Id: Ic994122113cc68b70799f2de13280d94763fbdfd
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3472984
Reviewed-by: Shivani Sharma <shivanisha@chromium.org>
Reviewed-by: Alex Moshchuk <alexmos@chromium.org>
Reviewed-by: Cammie Smith Barnes <cammie@chromium.org>
Commit-Queue: Yao Xiao <yaoxia@chromium.org>
Cr-Commit-Position: refs/heads/main@{#985049}
  • Loading branch information
yaoxiachromium authored and Chromium LUCI CQ committed Mar 24, 2022
1 parent 91289ce commit 1099e4c
Show file tree
Hide file tree
Showing 13 changed files with 428 additions and 89 deletions.
30 changes: 25 additions & 5 deletions content/browser/fenced_frame/fenced_frame_url_mapping.cc
Expand Up @@ -95,6 +95,12 @@ FencedFrameURLMapping::MapInfo::MapInfo() = default;
FencedFrameURLMapping::MapInfo::MapInfo(const GURL& mapped_url)
: mapped_url(mapped_url) {}

FencedFrameURLMapping::MapInfo::MapInfo(
const GURL& mapped_url,
const SharedStorageBudgetMetadata& shared_storage_budget_metadata)
: mapped_url(mapped_url),
shared_storage_budget_metadata(shared_storage_budget_metadata) {}

FencedFrameURLMapping::MapInfo::MapInfo(const MapInfo&) = default;
FencedFrameURLMapping::MapInfo::MapInfo(MapInfo&&) = default;
FencedFrameURLMapping::MapInfo::~MapInfo() = default;
Expand Down Expand Up @@ -184,29 +190,43 @@ void FencedFrameURLMapping::RemoveObserverForURN(
it->second.erase(observer_it);
}

void FencedFrameURLMapping::OnURNMappingResultDetermined(
void FencedFrameURLMapping::OnSharedStorageURNMappingResultDetermined(
const GURL& urn_uuid,
const absl::optional<GURL>& mapped_url) {
const SharedStorageURNMappingResult& mapping_result) {
auto it = pending_urn_uuid_to_url_map_.find(urn_uuid);
DCHECK(it != pending_urn_uuid_to_url_map_.end());

DCHECK(!IsMapped(urn_uuid));

if (mapped_url)
urn_uuid_to_url_map_.emplace(urn_uuid, mapped_url.value());
urn_uuid_to_url_map_.emplace(
urn_uuid, MapInfo(mapping_result.mapped_url, mapping_result.metadata));

std::set<raw_ptr<MappingResultObserver>>& observers = it->second;

for (raw_ptr<MappingResultObserver> observer : observers) {
observer->OnFencedFrameURLMappingComplete(
mapped_url,
absl::make_optional<GURL>(mapping_result.mapped_url),
/*ad_auction_data=*/absl::nullopt,
/*pending_ad_components_map=*/absl::nullopt);
}

pending_urn_uuid_to_url_map_.erase(it);
}

absl::optional<FencedFrameURLMapping::SharedStorageBudgetMetadata>
FencedFrameURLMapping::ReleaseSharedStorageBudgetMetadata(
const GURL& urn_uuid) {
auto it = urn_uuid_to_url_map_.find(urn_uuid);
DCHECK(it != urn_uuid_to_url_map_.end());

absl::optional<SharedStorageBudgetMetadata> metadata =
it->second.shared_storage_budget_metadata;

it->second.shared_storage_budget_metadata.reset();

return metadata;
}

bool FencedFrameURLMapping::HasObserverForTesting(
const GURL& urn_uuid,
MappingResultObserver* observer) {
Expand Down
59 changes: 48 additions & 11 deletions content/browser/fenced_frame/fenced_frame_url_mapping.h
Expand Up @@ -27,9 +27,27 @@ struct AdAuctionData {

// Keeps a mapping of fenced frames URN:UUID and URL. Also keeps a set of
// pending mapped URN:UUIDs to support asynchronous mapping. See
// https://github.com/shivanigithub/fenced-frame/blob/master/OpaqueSrc.md
// https://github.com/shivanigithub/fenced-frame/blob/master/explainer/opaque_src.md
class CONTENT_EXPORT FencedFrameURLMapping {
public:
// The metadata for the shared storage runURLSelectionOperation's budget,
// which includes the shared storage's origin and the amount of budget to
// charge when a fenced frame that originates from the URN is navigating a top
// frame. Before the fenced frame results in a top navigation, this
// `SharedStorageBudgetMetadata` will be stored/associated with the URN inside
// the `FencedFrameURLMapping`.
struct CONTENT_EXPORT SharedStorageBudgetMetadata {
url::Origin origin;
double budget_to_charge = 0;
};

// The runURLSelectionOperation's url mapping result. It contains the mapped
// url and the `SharedStorageBudgetMetadata`.
struct CONTENT_EXPORT SharedStorageURNMappingResult {
GURL mapped_url;
SharedStorageBudgetMetadata metadata;
};

// When the result of an ad auction is a main ad URL with a set of ad
// component URLs (instead of just a single ad URL), a URN that maps to the
// main ad URL needs to be loaded in a (parent) fenced frame, and then that
Expand Down Expand Up @@ -149,16 +167,26 @@ class CONTENT_EXPORT FencedFrameURLMapping {
void RemoveObserverForURN(const GURL& urn_uuid,
MappingResultObserver* observer);

// Called when the mapping decision is made for `urn_uuid`. On success,
// `mapped_url` will be the result url; on failure,`mapped_url` will be
// absl::nullopt. Should only be invoked with a `urn_uuid` pending to be
// mapped. This method will trigger the observers'
// OnFencedFrameURLMappingComplete() method associated with the `urn_uuid`,
// unregister those observers, and remove `urn_uuid` from
// `pending_urn_uuid_to_url_map_`. If the mapping succeeded, the `urn_uuid`
// will be added to `urn_uuid_to_url_map_`.
void OnURNMappingResultDetermined(const GURL& urn_uuid,
const absl::optional<GURL>& mapped_url);
// Called when the shared storage mapping decision is made for `urn_uuid`.
// Should only be invoked on a `urn_uuid` pending to be mapped. This method
// will trigger the observers' OnFencedFrameURLMappingComplete() method
// associated with the `urn_uuid`, unregister those observers, and move the
// `urn_uuid` from `pending_urn_uuid_to_url_map_` to `urn_uuid_to_url_map_`.
void OnSharedStorageURNMappingResultDetermined(
const GURL& urn_uuid,
const SharedStorageURNMappingResult& mapping_result);

// Get the `SharedStorageBudgetMetadata` associated with `urn_uuid`, and reset
// the current metadata to absl::nullopt. Precondition: `urn_uuid` exists in
// `urn_uuid_to_url_map_`.
//
// This method will be called when a fenced frame is navigating a top frame:
// if the fenced frame originates from a URN generated from the shared
// storage, then the shared storage origin's budget will be charged. For each
// URN, we only need to charge the budget once, thus the value here is
// released (i.e. returned and reset).
absl::optional<SharedStorageBudgetMetadata>
ReleaseSharedStorageBudgetMetadata(const GURL& urn_uuid);

bool HasObserverForTesting(const GURL& urn_uuid,
MappingResultObserver* observer);
Expand All @@ -172,6 +200,8 @@ class CONTENT_EXPORT FencedFrameURLMapping {
struct MapInfo {
MapInfo();
explicit MapInfo(const GURL& url);
MapInfo(const GURL& url,
const SharedStorageBudgetMetadata& shared_storage_budget_metadata);
MapInfo(const MapInfo&);
MapInfo(MapInfo&&);
~MapInfo();
Expand All @@ -185,6 +215,13 @@ class CONTENT_EXPORT FencedFrameURLMapping {
// to fill in `AdAuctionDocumentData` for the fenced frame that navigates
// to `mapped_url`.
absl::optional<AdAuctionData> ad_auction_data;

// Contains the metadata needed for shared storage budget charging. Will be
// initialized to absl::nullopt if the associated URN is not generated from
// shared storage; also will be reset to absl::nullopt if the budget has
// already been charged for the associated URN.
absl::optional<SharedStorageBudgetMetadata> shared_storage_budget_metadata;

// Ad component URLs if `mapped_url` is the result of a FLEDGE auction. When
// a fenced frame navigates to `mapped_url`, these will be mapped to URNs
// themselves, and those URNs will be provided to the fenced frame.
Expand Down
89 changes: 63 additions & 26 deletions content/browser/fenced_frame/fenced_frame_url_mapping_unittest.cc
Expand Up @@ -128,36 +128,66 @@ TEST(FencedFrameURLMappingTest, NonExistentUUID) {
EXPECT_EQ(absl::nullopt, observer.pending_ad_components_map());
}

TEST(FencedFrameURLMappingTest, PendingMappedUUID_MappingSuccess) {
TEST(FencedFrameURLMappingTest, PendingMappedUUID) {
FencedFrameURLMapping fenced_frame_url_mapping;
const GURL urn_uuid = fenced_frame_url_mapping.GeneratePendingMappedURN();
const GURL urn_uuid1 = fenced_frame_url_mapping.GeneratePendingMappedURN();
const GURL urn_uuid2 = fenced_frame_url_mapping.GeneratePendingMappedURN();

TestFencedFrameURLMappingResultObserver observer;
fenced_frame_url_mapping.ConvertFencedFrameURNToURL(urn_uuid, &observer);
EXPECT_FALSE(observer.mapping_complete_observed());
TestFencedFrameURLMappingResultObserver observer1;
fenced_frame_url_mapping.ConvertFencedFrameURNToURL(urn_uuid1, &observer1);
EXPECT_FALSE(observer1.mapping_complete_observed());

fenced_frame_url_mapping.OnURNMappingResultDetermined(
urn_uuid, GURL("https://foo.com"));
TestFencedFrameURLMappingResultObserver observer2;
fenced_frame_url_mapping.ConvertFencedFrameURNToURL(urn_uuid2, &observer2);
EXPECT_FALSE(observer2.mapping_complete_observed());

EXPECT_TRUE(observer.mapping_complete_observed());
EXPECT_EQ(GURL("https://foo.com"), observer.mapped_url());
EXPECT_EQ(absl::nullopt, observer.pending_ad_components_map());
}
url::Origin shared_storage_origin =
url::Origin::Create(GURL("https://bar.com"));
GURL mapped_url = GURL("https://foo.com");

TEST(FencedFrameURLMappingTest, PendingMappedUUID_MappingFailure) {
FencedFrameURLMapping fenced_frame_url_mapping;
const GURL urn_uuid = fenced_frame_url_mapping.GeneratePendingMappedURN();
// Two SharedStorageBudgetMetadata for the same origin 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_origin,
/*budget_to_charge=*/2.0);

TestFencedFrameURLMappingResultObserver observer;
fenced_frame_url_mapping.ConvertFencedFrameURNToURL(urn_uuid, &observer);
EXPECT_FALSE(observer.mapping_complete_observed());
SimulateSharedStorageURNMappingComplete(fenced_frame_url_mapping, urn_uuid2,
mapped_url, shared_storage_origin,
/*budget_to_charge=*/3.0);

fenced_frame_url_mapping.OnURNMappingResultDetermined(urn_uuid,
absl::nullopt);
EXPECT_TRUE(observer1.mapping_complete_observed());
EXPECT_EQ(mapped_url, observer1.mapped_url());
EXPECT_EQ(absl::nullopt, observer1.pending_ad_components_map());

EXPECT_TRUE(observer.mapping_complete_observed());
EXPECT_EQ(absl::nullopt, observer.mapped_url());
EXPECT_EQ(absl::nullopt, observer.pending_ad_components_map());
EXPECT_TRUE(observer2.mapping_complete_observed());
EXPECT_EQ(mapped_url, observer2.mapped_url());
EXPECT_EQ(absl::nullopt, observer2.pending_ad_components_map());

absl::optional<FencedFrameURLMapping::SharedStorageBudgetMetadata>
metadata1_first_retrieval =
fenced_frame_url_mapping.ReleaseSharedStorageBudgetMetadata(
urn_uuid1);

EXPECT_TRUE(metadata1_first_retrieval);
EXPECT_EQ(metadata1_first_retrieval->origin, shared_storage_origin);
EXPECT_DOUBLE_EQ(metadata1_first_retrieval->budget_to_charge, 2.0);

EXPECT_FALSE(
fenced_frame_url_mapping.ReleaseSharedStorageBudgetMetadata(urn_uuid1));

absl::optional<FencedFrameURLMapping::SharedStorageBudgetMetadata>
metadata2_first_retrieval =
fenced_frame_url_mapping.ReleaseSharedStorageBudgetMetadata(
urn_uuid2);

EXPECT_TRUE(metadata2_first_retrieval);
EXPECT_EQ(metadata2_first_retrieval->origin, shared_storage_origin);
EXPECT_DOUBLE_EQ(metadata2_first_retrieval->budget_to_charge, 3.0);

EXPECT_FALSE(
fenced_frame_url_mapping.ReleaseSharedStorageBudgetMetadata(urn_uuid2));
}

TEST(FencedFrameURLMappingTest, RemoveObserverOnPendingMappedUUID) {
Expand All @@ -169,8 +199,12 @@ TEST(FencedFrameURLMappingTest, RemoveObserverOnPendingMappedUUID) {
EXPECT_FALSE(observer.mapping_complete_observed());

fenced_frame_url_mapping.RemoveObserverForURN(urn_uuid, &observer);
fenced_frame_url_mapping.OnURNMappingResultDetermined(
urn_uuid, GURL("https://foo.com"));

SimulateSharedStorageURNMappingComplete(
fenced_frame_url_mapping, urn_uuid,
/*mapped_url=*/GURL("https://foo.com"),
/*shared_storage_origin=*/url::Origin::Create(GURL("https://bar.com")),
/*budget_to_charge=*/2.0);

EXPECT_FALSE(observer.mapping_complete_observed());
}
Expand All @@ -187,8 +221,11 @@ TEST(FencedFrameURLMappingTest, RegisterTwoObservers) {
fenced_frame_url_mapping.ConvertFencedFrameURNToURL(urn_uuid, &observer2);
EXPECT_FALSE(observer2.mapping_complete_observed());

fenced_frame_url_mapping.OnURNMappingResultDetermined(
urn_uuid, GURL("https://foo.com"));
SimulateSharedStorageURNMappingComplete(
fenced_frame_url_mapping, urn_uuid,
/*mapped_url=*/GURL("https://foo.com"),
/*shared_storage_origin=*/url::Origin::Create(GURL("https://bar.com")),
/*budget_to_charge=*/2.0);

EXPECT_TRUE(observer1.mapping_complete_observed());
EXPECT_EQ(GURL("https://foo.com"), observer1.mapped_url());
Expand Down
24 changes: 19 additions & 5 deletions content/browser/renderer_host/frame_tree_browsertest.cc
Expand Up @@ -1084,9 +1084,11 @@ IN_PROC_BROWSER_TEST_P(FencedFrameTreeBrowserTest,
EXPECT_EQ(0, EvalJs(root, "window.frames.length"));
}

// Test the scenario where the FF navigation is deferred and then resumed, and
// the mapped url is a valid one. The navigation is expected to succeed.
IN_PROC_BROWSER_TEST_P(
FencedFrameTreeBrowserTest,
FencedFrameNavigationWithPendingMappedUUID_MappingSuccess) {
FencedFrameNavigationWithPendingMappedUUID_MappingSuccess_ValidURL) {
GURL main_url = https_server()->GetURL("b.test", "/hello.html");
EXPECT_TRUE(NavigateToURL(shell(), main_url));
// It is safe to obtain the root frame tree node here, as it doesn't change.
Expand Down Expand Up @@ -1135,7 +1137,10 @@ IN_PROC_BROWSER_TEST_P(
EXPECT_TRUE(url_mapping.HasObserverForTesting(urn_uuid, request));

// Trigger the mapping to resume the deferred navigation.
url_mapping.OnURNMappingResultDetermined(urn_uuid, mapped_url);
SimulateSharedStorageURNMappingComplete(
url_mapping, urn_uuid, mapped_url,
/*shared_storage_origin=*/url::Origin::Create(GURL("https://bar.com")),
/*budget_to_charge=*/2.0);

EXPECT_FALSE(url_mapping.HasObserverForTesting(urn_uuid, request));

Expand All @@ -1146,9 +1151,11 @@ IN_PROC_BROWSER_TEST_P(
fenced_frame_root_node->current_frame_host()->GetLastCommittedURL());
}

// Test the scenario where the FF navigation is deferred and then resumed, and
// the mapped url is invalid. The navigation is expected to fail.
IN_PROC_BROWSER_TEST_P(
FencedFrameTreeBrowserTest,
FencedFrameNavigationWithPendingMappedUUID_MappingFailure) {
FencedFrameNavigationWithPendingMappedUUID_MappingSuccess_InvalidURL) {
GURL main_url = https_server()->GetURL("b.test", "/hello.html");
EXPECT_TRUE(NavigateToURL(shell(), main_url));
// It is safe to obtain the root frame tree node here, as it doesn't change.
Expand All @@ -1172,6 +1179,8 @@ IN_PROC_BROWSER_TEST_P(
root->current_frame_host()->GetPage().fenced_frame_urls_map();

const GURL urn_uuid = url_mapping.GeneratePendingMappedURN();
const GURL mapped_url =
https_server()->GetURL("a.test", "/fenced_frames/nonexistent-url.html");
std::string navigate_urn_script = JsReplace("f.src = $1;", urn_uuid.spec());

TestFrameNavigationObserver observer(
Expand All @@ -1195,12 +1204,17 @@ IN_PROC_BROWSER_TEST_P(
EXPECT_TRUE(url_mapping.HasObserverForTesting(urn_uuid, request));

// Trigger the mapping to resume the deferred navigation.
url_mapping.OnURNMappingResultDetermined(urn_uuid, absl::nullopt);
SimulateSharedStorageURNMappingComplete(
url_mapping, urn_uuid, mapped_url,
/*shared_storage_origin=*/url::Origin::Create(GURL("https://bar.com")),
/*budget_to_charge=*/2.0);

EXPECT_FALSE(url_mapping.HasObserverForTesting(urn_uuid, request));

// In NavigationRequest::OnResponseStarted(), for fenced frame, it manually
// fails the navigation with net::ERR_BLOCKED_BY_RESPONSE.
observer.Wait();
EXPECT_EQ(observer.last_net_error_code(), InvalidUrnError());
EXPECT_EQ(observer.last_net_error_code(), net::ERR_BLOCKED_BY_RESPONSE);
}

IN_PROC_BROWSER_TEST_P(
Expand Down
6 changes: 5 additions & 1 deletion content/browser/renderer_host/navigation_request_unittest.cc
Expand Up @@ -16,6 +16,7 @@
#include "content/public/common/content_client.h"
#include "content/public/common/url_constants.h"
#include "content/public/test/test_navigation_throttle.h"
#include "content/test/fenced_frame_test_utils.h"
#include "content/test/navigation_simulator_impl.h"
#include "content/test/test_content_browser_client.h"
#include "content/test/test_render_frame_host.h"
Expand Down Expand Up @@ -364,7 +365,10 @@ TEST_F(NavigationRequestTest, FencedFrameNavigationToPendingMappedURN) {

EXPECT_EQ(navigation_simulator->GetNavigationHandle()->GetURL(), urn_uuid);

fenced_frame_urls_map.OnURNMappingResultDetermined(urn_uuid, mapped_url);
SimulateSharedStorageURNMappingComplete(
fenced_frame_urls_map, urn_uuid, mapped_url,
/*shared_storage_origin=*/url::Origin::Create(GURL("https://bar.com")),
/*budget_to_charge=*/2.0);

// Expect that the url in the NavigationRequest is already mapped.
EXPECT_EQ(navigation_simulator->GetNavigationHandle()->GetURL(), mapped_url);
Expand Down

0 comments on commit 1099e4c

Please sign in to comment.