[go: up one dir, main page]

Skip to content

Commit

Permalink
Fenced frames: Propagate size from winning bid to fenced frame config.
Browse files Browse the repository at this point in the history
Now with FLEDGE auction able to be run with size specified for ad in
interest groups and bids, the winning ad may contain size info.

This CL propagates the size from the winning bid to the fenced frame
config. With this, fenced frame navigations using configs should render
an ad according to the size in the config object.

See Turtledove issue: WICG/turtledove#312
See Turtledove PR: WICG/turtledove#417

Bug: 1347953
Change-Id: I436c9a989cd941ef3788fb7abe0c960945fc4abf
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4356285
Reviewed-by: Nasko Oskov <nasko@chromium.org>
Commit-Queue: Xiaochen Zhou <xiaochenzh@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1126568}
  • Loading branch information
xiaochen-z authored and Chromium LUCI CQ committed Apr 5, 2023
1 parent edcd124 commit 1cd2c91
Show file tree
Hide file tree
Showing 13 changed files with 335 additions and 46 deletions.
12 changes: 3 additions & 9 deletions content/browser/fenced_frame/fenced_frame_browsertest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4477,9 +4477,7 @@ IN_PROC_BROWSER_TEST_F(FencedFrameParameterizedBrowserTest,
EvalJs(nodeA, "getComputedStyle(nested_fenced_frame).height")
.ExtractString();

// Wait for 2 rAFs to make things deterministic.
ASSERT_TRUE(EvalJsAfterLifecycleUpdate(nodeA, "", "").error.empty());
ASSERT_TRUE(EvalJsAfterLifecycleUpdate(nodeA, "", "").error.empty());
ASSERT_TRUE(WaitForFencedFrameSizeFreeze(nodeA->current_frame_host()));

// Navigate the fenced frame, which should force its inner size to the
// nearest allowed one.
Expand All @@ -4496,9 +4494,7 @@ IN_PROC_BROWSER_TEST_F(FencedFrameParameterizedBrowserTest,
.ExtractString(),
frame_height);

// Wait for 2 rAFs to make things deterministic.
ASSERT_TRUE(EvalJsAfterLifecycleUpdate(nodeA, "", "").error.empty());
ASSERT_TRUE(EvalJsAfterLifecycleUpdate(nodeA, "", "").error.empty());
ASSERT_TRUE(WaitForFencedFrameSizeFreeze(nodeA->current_frame_host()));

// Check that the inner size is what we expect.
int inner_width = EvalJs(nodeB, "innerWidth").ExtractInt();
Expand All @@ -4517,9 +4513,7 @@ IN_PROC_BROWSER_TEST_F(FencedFrameParameterizedBrowserTest,
ASSERT_TRUE(EvalJs(nodeA, "getComputedStyle(nested_fenced_frame).width")
.error.empty());

// Wait for 2 rAFs to make things deterministic.
ASSERT_TRUE(EvalJsAfterLifecycleUpdate(nodeA, "", "").error.empty());
ASSERT_TRUE(EvalJsAfterLifecycleUpdate(nodeA, "", "").error.empty());
ASSERT_TRUE(WaitForFencedFrameSizeFreeze(nodeA->current_frame_host()));

// Check that the inner size hasn't changed.
inner_width = EvalJs(nodeB, "innerWidth").ExtractInt();
Expand Down
17 changes: 17 additions & 0 deletions content/browser/fenced_frame/fenced_frame_config.cc
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,23 @@ FencedFrameConfig::FencedFrameConfig(const GURL& mapped_url)
VisibilityToContent::kTransparent),
mode_(DeprecatedFencedFrameMode::kOpaqueAds) {}

FencedFrameConfig::FencedFrameConfig(const GURL& mapped_url,
const gfx::Size& content_size,
bool is_ad_component)
: mapped_url_(absl::in_place,
mapped_url,
VisibilityToEmbedder::kOpaque,
VisibilityToContent::kTransparent),
content_size_(absl::in_place,
content_size,
VisibilityToEmbedder::kTransparent,
VisibilityToContent::kTransparent),
deprecated_should_freeze_initial_size_(absl::in_place,
false,
VisibilityToEmbedder::kTransparent,
VisibilityToContent::kOpaque),
is_ad_component_(is_ad_component) {}

FencedFrameConfig::FencedFrameConfig(const GURL& urn_uuid,
const GURL& mapped_url)
: urn_uuid_(urn_uuid),
Expand Down
3 changes: 3 additions & 0 deletions content/browser/fenced_frame/fenced_frame_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,9 @@ class CONTENT_EXPORT FencedFrameProperty {
struct CONTENT_EXPORT FencedFrameConfig {
FencedFrameConfig();
explicit FencedFrameConfig(const GURL& mapped_url);
explicit FencedFrameConfig(const GURL& mapped_url,
const gfx::Size& content_size,
bool is_ad_component);
FencedFrameConfig(const GURL& urn_uuid, const GURL& url);
FencedFrameConfig(const GURL& mapped_url, bool is_ad_component);
FencedFrameConfig(
Expand Down
46 changes: 33 additions & 13 deletions content/browser/fenced_frame/fenced_frame_url_mapping.cc
Original file line number Diff line number Diff line change
Expand Up @@ -56,22 +56,29 @@ std::string SubstituteMappedStrings(
return base::StrCat(output_vec);
}

double AdSizeToPixels(double size, blink::AdSize::LengthUnit unit) {
int AdSizeToPixels(double size, blink::AdSize::LengthUnit unit) {
switch (unit) {
case blink::AdSize::LengthUnit::kPixels:
return size;
return static_cast<int>(size);
case blink::AdSize::LengthUnit::kScreenWidth: {
double screen_width = display::Screen::GetScreen()
->GetPrimaryDisplay()
.GetSizeInPixel()
.width();
return size / 100.0 * screen_width;
return static_cast<int>(size / 100.0 * screen_width);
}
case blink::AdSize::LengthUnit::kInvalid:
NOTREACHED_NORETURN();
}
}

gfx::Size AdSizeToGfxSize(const blink::AdSize& ad_size) {
int width_in_pixels = AdSizeToPixels(ad_size.width, ad_size.width_units);
int height_in_pixels = AdSizeToPixels(ad_size.height, ad_size.height_units);

return gfx::Size(width_in_pixels, height_in_pixels);
}

// TODO(crbug.com/1420638): Once the representation of size in fenced frame
// config is finalized, change the type of substituted width and height to the
// same.
Expand All @@ -82,16 +89,12 @@ GURL SubstituteSizeIntoURL(const blink::AdDescriptor& ad_descriptor) {
}

// Convert dimensions to pixels.
int width_in_pixels = static_cast<int>(AdSizeToPixels(
ad_descriptor.size->width, ad_descriptor.size->width_units));
int height_in_pixels = static_cast<int>(AdSizeToPixels(
ad_descriptor.size->height, ad_descriptor.size->height_units));
gfx::Size size = AdSizeToGfxSize(ad_descriptor.size.value());

return GURL(SubstituteMappedStrings(
ad_descriptor.url.spec(),
{std::make_pair("{%AD_WIDTH%}", base::NumberToString(width_in_pixels)),
std::make_pair("{%AD_HEIGHT%}",
base::NumberToString(height_in_pixels))}));
{std::make_pair("{%AD_WIDTH%}", base::NumberToString(size.width())),
std::make_pair("{%AD_HEIGHT%}", base::NumberToString(size.height()))}));
}

} // namespace
Expand Down Expand Up @@ -210,8 +213,15 @@ FencedFrameURLMapping::AssignFencedFrameURLAndInterestGroupInfo(
config.mapped_url_.emplace(SubstituteSizeIntoURL(ad_descriptor),
VisibilityToEmbedder::kOpaque,
VisibilityToContent::kTransparent);
if (ad_descriptor.size) {
gfx::Size content_size = AdSizeToGfxSize(ad_descriptor.size.value());
config.content_size_.emplace(content_size,
VisibilityToEmbedder::kTransparent,
VisibilityToContent::kTransparent);
}
config.deprecated_should_freeze_initial_size_.emplace(
true, VisibilityToEmbedder::kTransparent, VisibilityToContent::kOpaque);
!ad_descriptor.size.has_value(), VisibilityToEmbedder::kTransparent,
VisibilityToContent::kOpaque);
config.ad_auction_data_.emplace(std::move(ad_auction_data),
VisibilityToEmbedder::kOpaque,
VisibilityToContent::kOpaque);
Expand All @@ -227,8 +237,18 @@ FencedFrameURLMapping::AssignFencedFrameURLAndInterestGroupInfo(
// TODO(crbug.com/1420638): Once the representation of size in fenced frame
// config is finalized, pass the ad component size from the winning bid to
// its fenced frame config.
nested_configs.emplace_back(SubstituteSizeIntoURL(ad_component_descriptor),
/*is_ad_component=*/true);
if (ad_component_descriptor.size) {
gfx::Size component_content_size =
AdSizeToGfxSize(ad_component_descriptor.size.value());
nested_configs.emplace_back(
/*mapped_url=*/SubstituteSizeIntoURL(ad_component_descriptor),
/*content_size=*/component_content_size,
/*is_ad_component=*/true);
} else {
nested_configs.emplace_back(
/*mapped_url=*/SubstituteSizeIntoURL(ad_component_descriptor),
/*is_ad_component=*/true);
}
}
config.nested_configs_.emplace(std::move(nested_configs),
VisibilityToEmbedder::kOpaque,
Expand Down
143 changes: 143 additions & 0 deletions content/browser/interest_group/interest_group_browsertest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6438,6 +6438,149 @@ perBuyerSignals: {$1: {even: 'more', x: 4.5}}
->trusted_params->isolation_info.network_isolation_key());
}

// Runs auction just like test
// InterestGroupBrowserTest.RunAdAuctionWithSizeWithWinner, but load the winning
// ad in a fenced frame and verify the ad size.
IN_PROC_BROWSER_TEST_F(InterestGroupFencedFrameBrowserTest,
RunAdAuctionWithSizeWithWinner) {
GURL test_url = https_server_->GetURL("a.test", "/fenced_frames/basic.html");
ASSERT_TRUE(NavigateToURL(shell(), test_url));
url::Origin test_origin = url::Origin::Create(test_url);
GURL ad_url = https_server_->GetURL(
"c.test", "/set-header?Supports-Loading-Mode: fenced-frame");

EXPECT_EQ(
kSuccess,
JoinInterestGroupAndVerify(
blink::TestInterestGroupBuilder(
/*owner=*/test_origin,
/*name=*/"cars")
.SetBiddingUrl(https_server_->GetURL(
"a.test", "/interest_group/bidding_logic_with_size.js"))
.SetAds(/*ads=*/{{{ad_url, /*size_group=*/"group_1",
/*metadata=*/absl::nullopt}}})
.SetAdSizes(
{{{"size_1",
blink::AdSize(100, blink::AdSize::LengthUnit::kScreenWidth,
50, blink::AdSize::LengthUnit::kPixels)}}})
.SetSizeGroups({{{"group_1", {"size_1"}}}})
.Build()));

std::string auction_config = JsReplace(
R"({
seller: $1,
decisionLogicUrl: $2,
interestGroupBuyers: [$1]
})",
test_origin,
https_server_->GetURL("a.test", "/interest_group/decision_logic.js"));
ASSERT_NO_FATAL_FAILURE(
RunAuctionAndNavigateFencedFrame(ad_url, auction_config));

// Verify the ad is loaded with the size specified in the winning bid.
int screen_width = static_cast<int>(display::Screen::GetScreen()
->GetPrimaryDisplay()
.GetSizeInPixel()
.width());
RenderFrameHost* ad_frame = GetFencedFrameRenderFrameHost(shell());
EXPECT_TRUE(WaitForLoadStop(web_contents()));
// Wait for 2 requestAnimationFrame calls to make things deterministic.
// Without this, the fenced frame may end up with its default size 300px *
// 150px. (Width * Height)
ASSERT_TRUE(WaitForFencedFrameSizeFreeze(ad_frame));
// Force layout.
EXPECT_TRUE(
ExecJs(ad_frame, "getComputedStyle(document.documentElement).width;"));
EXPECT_EQ(EvalJs(ad_frame, "innerWidth").ExtractInt(), screen_width);
EXPECT_EQ(EvalJs(ad_frame, "innerHeight").ExtractInt(), 50);
}

IN_PROC_BROWSER_TEST_F(InterestGroupFencedFrameBrowserTest,
RunAdAuctionWithAdComponentWithSize) {
GURL test_url = https_server_->GetURL("a.test", "/fenced_frames/basic.html");
ASSERT_TRUE(NavigateToURL(shell(), test_url));
GURL ad_component_url = https_server_->GetURL(
"d.test", "/set-header?Supports-Loading-Mode: fenced-frame");

GURL ad_url = https_server_->GetURL("c.test", "/fenced_frames/basic.html");
EXPECT_EQ(
kSuccess,
JoinInterestGroupAndVerify(
blink::TestInterestGroupBuilder(
/*owner=*/url::Origin::Create(test_url),
/*name=*/"cars")
.SetBiddingUrl(https_server_->GetURL(
"a.test", "/interest_group/bidding_logic_with_size.js"))
.SetAds(/*ads=*/{{{ad_url, /*size_group=*/"group_1",
/*metadata=*/absl::nullopt}}})
.SetAdComponents({{{ad_component_url, /*size_group=*/"group_2",
/*metadata=*/absl::nullopt}}})
.SetAdSizes(
{{{"size_1",
blink::AdSize(100, blink::AdSize::LengthUnit::kScreenWidth,
50, blink::AdSize::LengthUnit::kPixels)},
{"size_2",
blink::AdSize(50, blink::AdSize::LengthUnit::kPixels, 25,
blink::AdSize::LengthUnit::kPixels)}}})
.SetSizeGroups(
{{{"group_1", {"size_1"}}, {"group_2", {"size_2"}}}})
.Build()));

ASSERT_NO_FATAL_FAILURE(RunAuctionAndNavigateFencedFrame(
ad_url, JsReplace(
R"({
seller: $1,
decisionLogicUrl: $2,
interestGroupBuyers: [$1]
})",
url::Origin::Create(test_url),
https_server_->GetURL("a.test",
"/interest_group/decision_logic.js"))));

RenderFrameHost* ad_frame = GetFencedFrameRenderFrameHost(shell());

// Verify the ad is loaded with the size specified in the winning bid.
int screen_width = static_cast<int>(display::Screen::GetScreen()
->GetPrimaryDisplay()
.GetSizeInPixel()
.width());
EXPECT_TRUE(WaitForLoadStop(web_contents()));
// Wait for 2 requestAnimationFrame calls to make things deterministic.
// Without this, the fenced frame may end up with its default size 300px *
// 150px. (Width * Height)
ASSERT_TRUE(WaitForFencedFrameSizeFreeze(ad_frame));
// Force layout.
EXPECT_TRUE(
ExecJs(ad_frame, "getComputedStyle(document.documentElement).width;"));
EXPECT_EQ(EvalJs(ad_frame, "innerWidth").ExtractInt(), screen_width);
EXPECT_EQ(EvalJs(ad_frame, "innerHeight").ExtractInt(), 50);

// Get the first component config from the fenced frame. Load it in the
// nested fenced frame. The load should succeed.
TestFrameNavigationObserver observer(GetFencedFrameRenderFrameHost(ad_frame));

EXPECT_TRUE(ExecJs(ad_frame, R"(
const configs = window.fence.getNestedConfigs();
document.querySelector('fencedframe').config = configs[0];
)"));

WaitForFencedFrameNavigation(ad_component_url, ad_frame, observer);

// Verify the ad component is loaded with the size specified in the winning
// bid.
RenderFrameHost* ad_component_frame = GetFencedFrameRenderFrameHost(ad_frame);
EXPECT_TRUE(WaitForLoadStop(web_contents()));
// Wait for 2 requestAnimationFrame calls to make things deterministic.
// Without this, the fenced frame may end up with its default size 300px *
// 150px. (Width * Height)
ASSERT_TRUE(WaitForFencedFrameSizeFreeze(ad_component_frame));
// Force layout.
EXPECT_TRUE(ExecJs(ad_component_frame,
"getComputedStyle(document.documentElement).width;"));
EXPECT_EQ(EvalJs(ad_component_frame, "innerWidth").ExtractInt(), 50);
EXPECT_EQ(EvalJs(ad_component_frame, "innerHeight").ExtractInt(), 25);
}

IN_PROC_BROWSER_TEST_F(InterestGroupFencedFrameBrowserTest,
RunAdAuctionWithWinnerReplacedURN) {
URLLoaderMonitor url_loader_monitor;
Expand Down
6 changes: 5 additions & 1 deletion content/test/data/interest_group/bidding_logic_with_size.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ function generateBid(interestGroup, auctionSignals, perBuyerSignals,
'allowComponentAuction': allowComponentAuction
};
if (interestGroup.adComponents && interestGroup.adComponents[0])
result.adComponents = [interestGroup.adComponents[0].renderUrl];
result.adComponents = [{
url: interestGroup.adComponents[0].renderUrl,
width: "50px",
height: "25px"
}];
return result;
}

Expand Down
8 changes: 8 additions & 0 deletions content/test/fenced_frame_test_utils.cc
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,12 @@ void FencedFrameURLMappingTestPeer::FillMap(const GURL& url) {
DCHECK(fenced_frame_url_mapping_->IsFull());
}

bool WaitForFencedFrameSizeFreeze(RenderFrameHost* rfh) {
// Currently we observed that the size freezing requires two
// `requestAnimationFrame` calls to make sure it is completed. If only calling
// `requestAnimationFrame` once, the test can still be flaky.
return EvalJsAfterLifecycleUpdate(rfh, "", "").error.empty() &&
EvalJsAfterLifecycleUpdate(rfh, "", "").error.empty();
}

} // namespace content
7 changes: 7 additions & 0 deletions content/test/fenced_frame_test_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
namespace content {

class FrameTreeNode;
class RenderFrameHost;
class MappingResultObserver;

// `node` is expected to be the child FrameTreeNode created in response to a
Expand Down Expand Up @@ -118,6 +119,12 @@ class FencedFrameURLMappingTestPeer {
raw_ptr<FencedFrameURLMapping> fenced_frame_url_mapping_;
};

// TODO(xiaochenzh): Once fenced frame size freezing has no time gap, remove
// this.
// This function is needed because the freezing only takes effect after layout
// has happened.
bool WaitForFencedFrameSizeFreeze(RenderFrameHost* rfh);

} // namespace content

#endif // CONTENT_TEST_FENCED_FRAME_TEST_UTILS_H_
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<!DOCTYPE html>
<title>Fenced frames loading a winning ad from FLEDGE auction with size</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/utils.js"></script>
<script src="/common/dispatcher/dispatcher.js"></script>
<script src="/common/utils.js"></script>

<body>
<script>
promise_test(async (t) => {
var frame = await attachFencedFrameContext({
generator_api: "fledge",
resolve_to_config: true,
ad_with_size: true
});

const assert_dimensions =
(expected_width, expected_height) => {
getComputedStyle(document.documentElement).width; // Force layout.
assert_equals(window.innerWidth, expected_width, "width");
assert_equals(window.innerHeight, expected_height, "height");
}
await frame.execute(assert_dimensions, [100, 50]);
}, "Fenced frame loading an ad with size.");
</script>
</body>

</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[load-ad-with-size.https.html]
[Fenced frame loading an ad with size.]
expected: FAIL
Loading

0 comments on commit 1cd2c91

Please sign in to comment.