FLEDGE: Provide browserSignals.utf8{Encode,Decode} to worklets
In place of TextEncoder/TextEncoder which we don't have at hand and which are more complex than what's actually needed. (Requested in https://github.com/WICG/turtledove/issues/961) Bug: 397936915 Change-Id: Iaa9018e05119a4dc0ef1579134cb50347fb57c30 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6269638 Reviewed-by: Russ Hamilton <behamilton@google.com> Commit-Queue: Maks Orlovich <morlovich@chromium.org> Cr-Commit-Position: refs/heads/main@{#1425747}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
30569131b6
commit
e013281fdb
content
browser
interest_group
services
auction_worklet
test
testing/variations
third_party/blink/web_tests/external/wpt/fledge/tentative
@ -93,6 +93,7 @@
|
||||
#include "content/public/test/test_frame_navigation_observer.h"
|
||||
#include "content/public/test/test_navigation_observer.h"
|
||||
#include "content/public/test/url_loader_monitor.h"
|
||||
#include "content/services/auction_worklet/public/cpp/auction_worklet_features.h"
|
||||
#include "content/services/auction_worklet/public/cpp/cbor_test_util.h"
|
||||
#include "content/services/auction_worklet/public/mojom/bidder_worklet.mojom.h"
|
||||
#include "content/shell/browser/shell.h"
|
||||
@ -766,7 +767,8 @@ class InterestGroupBrowserTest : public ContentBrowserTest {
|
||||
// TODO(crrev.com/c/6096602): Remove once implementation is removed.
|
||||
{blink::features::kFledgeDirectFromSellerSignalsWebBundles, {}},
|
||||
{blink::features::kFledgeTrustedSignalsKVv2Support, {}},
|
||||
{blink::features::kFledgeTrustedSignalsKVv1CreativeScanning, {}}},
|
||||
{blink::features::kFledgeTrustedSignalsKVv1CreativeScanning, {}},
|
||||
{features::kFledgeTextConversionHelpers, {}}},
|
||||
/*disabled_features=*/
|
||||
{blink::features::kFencedFrames,
|
||||
blink::features::kFledgeEnforceKAnonymity,
|
||||
|
@ -90,6 +90,8 @@ source_set("auction_worklet") {
|
||||
"set_priority_signals_override_bindings.h",
|
||||
"shared_storage_bindings.cc",
|
||||
"shared_storage_bindings.h",
|
||||
"text_conversion_helpers.cc",
|
||||
"text_conversion_helpers.h",
|
||||
"trusted_kvv2_signals.cc",
|
||||
"trusted_kvv2_signals.h",
|
||||
"trusted_signals.cc",
|
||||
|
@ -60,6 +60,7 @@
|
||||
#include "content/services/auction_worklet/set_priority_bindings.h"
|
||||
#include "content/services/auction_worklet/set_priority_signals_override_bindings.h"
|
||||
#include "content/services/auction_worklet/shared_storage_bindings.h"
|
||||
#include "content/services/auction_worklet/text_conversion_helpers.h"
|
||||
#include "content/services/auction_worklet/trusted_signals.h"
|
||||
#include "content/services/auction_worklet/trusted_signals_kvv2_manager.h"
|
||||
#include "content/services/auction_worklet/trusted_signals_request_manager.h"
|
||||
@ -861,6 +862,7 @@ BidderWorklet::V8State::SingleGenerateBidResult::operator=(
|
||||
|
||||
bool BidderWorklet::V8State::SetBrowserSignals(
|
||||
ContextRecycler& context_recycler,
|
||||
v8::Local<v8::Context>& context,
|
||||
bool is_for_additional_bid,
|
||||
const std::optional<std::string>& interest_group_name_reporting_id,
|
||||
const std::optional<std::string>& buyer_reporting_id,
|
||||
@ -938,6 +940,11 @@ bool BidderWorklet::V8State::SetBrowserSignals(
|
||||
return false;
|
||||
}
|
||||
|
||||
if (base::FeatureList::IsEnabled(features::kFledgeTextConversionHelpers)) {
|
||||
context_recycler.text_conversion_helpers()->ReInitialize(context,
|
||||
browser_signals);
|
||||
}
|
||||
|
||||
if (!context_recycler.report_win_lazy_filler()->FillInObject(
|
||||
browser_signal_modeling_signals, browser_signal_join_count,
|
||||
!is_for_additional_bid
|
||||
@ -1027,7 +1034,7 @@ bool BidderWorklet::V8State::SetReportAggregateWinArgs(
|
||||
}
|
||||
|
||||
if (!SetBrowserSignals(
|
||||
context_recycler, is_for_additional_bid,
|
||||
context_recycler, context, is_for_additional_bid,
|
||||
interest_group_name_reporting_id, buyer_reporting_id,
|
||||
buyer_and_seller_reporting_id, selected_buyer_and_seller_reporting_id,
|
||||
browser_signal_render_url,
|
||||
@ -1165,6 +1172,7 @@ void BidderWorklet::V8State::ReportWin(
|
||||
}
|
||||
|
||||
context_recycler.AddReportWinBrowserSignalsLazyFiller();
|
||||
context_recycler.AddTextConversionHelpers();
|
||||
|
||||
DeprecatedUrlLazyFiller deprecated_render_url(
|
||||
v8_helper_.get(), &v8_logger, &browser_signal_render_url,
|
||||
@ -1175,11 +1183,11 @@ void BidderWorklet::V8State::ReportWin(
|
||||
? *browser_signal_reporting_timeout
|
||||
: AuctionV8Helper::kScriptTimeout;
|
||||
const bool browser_signals_set = SetBrowserSignals(
|
||||
context_recycler, is_for_additional_bid, interest_group_name_reporting_id,
|
||||
buyer_reporting_id, buyer_and_seller_reporting_id,
|
||||
selected_buyer_and_seller_reporting_id, browser_signal_render_url,
|
||||
&deprecated_render_url, browser_signal_bid, browser_signal_bid_currency,
|
||||
browser_signal_highest_scoring_other_bid,
|
||||
context_recycler, context, is_for_additional_bid,
|
||||
interest_group_name_reporting_id, buyer_reporting_id,
|
||||
buyer_and_seller_reporting_id, selected_buyer_and_seller_reporting_id,
|
||||
browser_signal_render_url, &deprecated_render_url, browser_signal_bid,
|
||||
browser_signal_bid_currency, browser_signal_highest_scoring_other_bid,
|
||||
browser_signal_highest_scoring_other_bid_currency,
|
||||
browser_signal_made_highest_scoring_other_bid, browser_signal_ad_cost,
|
||||
browser_signal_modeling_signals, browser_signal_join_count,
|
||||
@ -1867,6 +1875,10 @@ BidderWorklet::V8State::RunGenerateBidOnce(
|
||||
}
|
||||
|
||||
v8::Local<v8::Object> browser_signals = v8::Object::New(isolate);
|
||||
if (base::FeatureList::IsEnabled(features::kFledgeTextConversionHelpers)) {
|
||||
context_recycler->text_conversion_helpers()->ReInitialize(context,
|
||||
browser_signals);
|
||||
}
|
||||
gin::Dictionary browser_signals_dict(isolate, browser_signals);
|
||||
// TODO(crbug.com/336164429): Construct the fields of browser signals lazily.
|
||||
if (!browser_signals_dict.Set("topWindowHostname",
|
||||
@ -2137,6 +2149,7 @@ BidderWorklet::V8State::CreateContextRecyclerAndRunTopLevelForGenerateBid(
|
||||
context_recycler->AddSetBidBindings();
|
||||
context_recycler->AddSetPriorityBindings();
|
||||
context_recycler->AddSetPrioritySignalsOverrideBindings();
|
||||
context_recycler->AddTextConversionHelpers();
|
||||
context_recycler->AddInterestGroupLazyFiller();
|
||||
context_recycler->AddBiddingBrowserSignalsLazyFiller();
|
||||
|
||||
|
@ -498,6 +498,7 @@ class CONTENT_EXPORT BidderWorklet : public mojom::BidderWorklet,
|
||||
|
||||
bool SetBrowserSignals(
|
||||
ContextRecycler& context_recycler,
|
||||
v8::Local<v8::Context>& context,
|
||||
bool is_for_additional_bid,
|
||||
const std::optional<std::string>& interest_group_name_reporting_id,
|
||||
const std::optional<std::string>& buyer_reporting_id,
|
||||
|
@ -1191,6 +1191,16 @@ class BidderWorkletMultiBidDisabledTest : public BidderWorkletTest {
|
||||
base::test::ScopedFeatureList feature_list_;
|
||||
};
|
||||
|
||||
class BidderWorkletTextConversionsTest : public BidderWorkletTest {
|
||||
public:
|
||||
BidderWorkletTextConversionsTest() {
|
||||
feature_list_.InitAndEnableFeature(features::kFledgeTextConversionHelpers);
|
||||
}
|
||||
|
||||
protected:
|
||||
base::test::ScopedFeatureList feature_list_;
|
||||
};
|
||||
|
||||
// Test the case the BidderWorklet pipe is closed before invoking the
|
||||
// GenerateBidCallback. The invocation of the GenerateBidCallback is not
|
||||
// observed, since the callback is on the pipe that was just closed. There
|
||||
@ -4652,6 +4662,23 @@ TEST_F(BidderWorkletTest, GenerateBidInterestGroupCreativeScanningMetadata) {
|
||||
R"(!('creativeScanningMetadata' in interestGroup.adComponents[0]))");
|
||||
}
|
||||
|
||||
TEST_F(BidderWorkletTest, GenerateBidTextConversions) {
|
||||
RunGenerateBidExpectingExpressionIsTrue(
|
||||
R"(!('encodeUtf8' in browserSignals))");
|
||||
RunGenerateBidExpectingExpressionIsTrue(
|
||||
R"(!('decodeUtf8' in browserSignals))");
|
||||
}
|
||||
|
||||
TEST_F(BidderWorkletTextConversionsTest, GenerateBidTextConversions) {
|
||||
RunGenerateBidExpectingExpressionIsTrue(R"('encodeUtf8' in browserSignals)");
|
||||
RunGenerateBidExpectingExpressionIsTrue(R"('decodeUtf8' in browserSignals)");
|
||||
|
||||
RunGenerateBidExpectingExpressionIsTrue(
|
||||
"browserSignals.encodeUtf8('A')[0] === 65");
|
||||
RunGenerateBidExpectingExpressionIsTrue(
|
||||
"browserSignals.decodeUtf8(new Uint8Array([65, 32, 68])) === 'A D'");
|
||||
}
|
||||
|
||||
class BidderWorkletCreativeScanningTest : public BidderWorkletTest {
|
||||
public:
|
||||
BidderWorkletCreativeScanningTest() {
|
||||
@ -7529,6 +7556,24 @@ TEST_F(BidderWorkletTest, ReportWinTopLevelTimeout) {
|
||||
{"https://url.test/ top-level execution timed out."});
|
||||
}
|
||||
|
||||
TEST_F(BidderWorkletTest, ReportWinTextConversions) {
|
||||
RunReportWinWithFunctionBodyExpectingResult(
|
||||
"sendReportTo('https://foo.test?' + ('encodeUtf8' in browserSignals))",
|
||||
GURL("https://foo.test/?false"));
|
||||
RunReportWinWithFunctionBodyExpectingResult(
|
||||
"sendReportTo('https://foo.test?' + ('decodeUtf8' in browserSignals))",
|
||||
GURL("https://foo.test/?false"));
|
||||
}
|
||||
|
||||
TEST_F(BidderWorkletTextConversionsTest, ReportWinTextConversions) {
|
||||
RunReportWinWithFunctionBodyExpectingResult(
|
||||
"sendReportTo('https://foo.test?' + ('encodeUtf8' in browserSignals))",
|
||||
GURL("https://foo.test/?true"));
|
||||
RunReportWinWithFunctionBodyExpectingResult(
|
||||
"sendReportTo('https://foo.test?' + ('decodeUtf8' in browserSignals))",
|
||||
GURL("https://foo.test/?true"));
|
||||
}
|
||||
|
||||
TEST_F(BidderWorkletTest, SendReportToLongUrl) {
|
||||
// Copying large URLs can cause flaky generateBid() timeouts with the default
|
||||
// value, even on the standard debug bots.
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include "content/services/auction_worklet/set_priority_bindings.h"
|
||||
#include "content/services/auction_worklet/set_priority_signals_override_bindings.h"
|
||||
#include "content/services/auction_worklet/shared_storage_bindings.h"
|
||||
#include "content/services/auction_worklet/text_conversion_helpers.h"
|
||||
#include "v8/include/v8-context.h"
|
||||
|
||||
namespace auction_worklet {
|
||||
@ -116,6 +117,13 @@ void ContextRecycler::AddSharedStorageBindings(
|
||||
AddBindings(shared_storage_bindings_.get());
|
||||
}
|
||||
|
||||
void ContextRecycler::AddTextConversionHelpers() {
|
||||
DCHECK(!text_conversion_helpers_);
|
||||
text_conversion_helpers_ =
|
||||
std::make_unique<TextConversionHelpers>(v8_helper_);
|
||||
AddBindings(text_conversion_helpers_.get());
|
||||
}
|
||||
|
||||
void ContextRecycler::AddInterestGroupLazyFiller() {
|
||||
DCHECK(!interest_group_lazy_filler_);
|
||||
interest_group_lazy_filler_ =
|
||||
|
@ -35,6 +35,7 @@ class SetBidBindings;
|
||||
class SetPriorityBindings;
|
||||
class SetPrioritySignalsOverrideBindings;
|
||||
class SharedStorageBindings;
|
||||
class TextConversionHelpers;
|
||||
class AuctionConfigLazyFiller;
|
||||
class BiddingBrowserSignalsLazyFiller;
|
||||
class InterestGroupLazyFiller;
|
||||
@ -145,6 +146,11 @@ class CONTENT_EXPORT ContextRecycler {
|
||||
return shared_storage_bindings_.get();
|
||||
}
|
||||
|
||||
void AddTextConversionHelpers();
|
||||
TextConversionHelpers* text_conversion_helpers() {
|
||||
return text_conversion_helpers_.get();
|
||||
}
|
||||
|
||||
void AddInterestGroupLazyFiller();
|
||||
InterestGroupLazyFiller* interest_group_lazy_filler() {
|
||||
return interest_group_lazy_filler_.get();
|
||||
@ -204,6 +210,7 @@ class CONTENT_EXPORT ContextRecycler {
|
||||
std::unique_ptr<SetPrioritySignalsOverrideBindings>
|
||||
set_priority_signals_override_bindings_;
|
||||
std::unique_ptr<SharedStorageBindings> shared_storage_bindings_;
|
||||
std::unique_ptr<TextConversionHelpers> text_conversion_helpers_;
|
||||
|
||||
// everything here is owned by one of the unique_ptr's above.
|
||||
std::vector<raw_ptr<Bindings, VectorExperimental>> bindings_list_;
|
||||
|
@ -33,6 +33,7 @@
|
||||
#include "content/services/auction_worklet/seller_lazy_filler.h"
|
||||
#include "content/services/auction_worklet/set_bid_bindings.h"
|
||||
#include "content/services/auction_worklet/set_priority_bindings.h"
|
||||
#include "content/services/auction_worklet/text_conversion_helpers.h"
|
||||
#include "content/services/auction_worklet/worklet_test_util.h"
|
||||
#include "gin/converter.h"
|
||||
#include "gin/dictionary.h"
|
||||
@ -6141,6 +6142,271 @@ TEST_F(ContextRecyclerTest, RegisterAdMacroBindings) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ContextRecyclerTest, EncodeUtf8) {
|
||||
const char kScript[] = R"(
|
||||
function assertEq(l, r, label) {
|
||||
if (l !== r)
|
||||
throw 'Mismatch ' + label;
|
||||
}
|
||||
|
||||
function assertByteArray(result, expect) {
|
||||
if (!(result instanceof Uint8Array)) {
|
||||
throw 'Not a Uint8Array!';
|
||||
}
|
||||
assertEq(result.length, expect.length, 'length');
|
||||
for (var i = 0; i < result.length; ++i) {
|
||||
assertEq(result[i], expect[i], i);
|
||||
}
|
||||
}
|
||||
|
||||
function test1() {
|
||||
assertByteArray(encoderObj.encodeUtf8('ABC'),
|
||||
[65, 66, 67]);
|
||||
}
|
||||
|
||||
function test2() {
|
||||
assertByteArray(encoderObj.encodeUtf8('A \u0490'),
|
||||
[65, 32, 0xD2, 0x90]);
|
||||
}
|
||||
|
||||
// Unmatched surrogate.
|
||||
function test3() {
|
||||
assertByteArray(encoderObj.encodeUtf8('A\uD800C'),
|
||||
[65, 0xEF, 0xBF, 0xBD, 67]);
|
||||
}
|
||||
|
||||
// Matched surrogate.
|
||||
function test4() {
|
||||
assertByteArray(encoderObj.encodeUtf8('A\uD83D\uDE02C'),
|
||||
[65, 0xF0, 0x9F, 0x98, 0x82, 67]);
|
||||
}
|
||||
|
||||
// Custom conversion.
|
||||
function test5() {
|
||||
let obj = {
|
||||
toString: () => "ABC"
|
||||
};
|
||||
assertByteArray(encoderObj.encodeUtf8(obj),
|
||||
[65, 66, 67]);
|
||||
}
|
||||
)";
|
||||
|
||||
v8::Local<v8::UnboundScript> script = Compile(kScript);
|
||||
ASSERT_FALSE(script.IsEmpty());
|
||||
|
||||
ContextRecycler context_recycler(helper_.get());
|
||||
{
|
||||
ContextRecyclerScope scope(context_recycler); // Initialize context
|
||||
context_recycler.AddTextConversionHelpers();
|
||||
}
|
||||
|
||||
for (const char* test : {"test1", "test2", "test3", "test4", "test5"}) {
|
||||
SCOPED_TRACE(test);
|
||||
ContextRecyclerScope scope(context_recycler);
|
||||
v8::Local<v8::Context> context = scope.GetContext();
|
||||
|
||||
v8::Local<v8::Object> obj = v8::Object::New(helper_->isolate());
|
||||
context_recycler.text_conversion_helpers()->ReInitialize(context, obj);
|
||||
context->Global()
|
||||
->Set(context, helper_->CreateStringFromLiteral("encoderObj"), obj)
|
||||
.Check();
|
||||
|
||||
std::vector<std::string> error_msgs;
|
||||
Run(scope, script, test, error_msgs);
|
||||
EXPECT_THAT(error_msgs, ElementsAre());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ContextRecyclerTest, EncodeUtf8Failure) {
|
||||
const char kScript[] = R"(
|
||||
// Not enough arguments.
|
||||
function test1() {
|
||||
encoderObj.encodeUtf8();
|
||||
}
|
||||
|
||||
// String conversion failure.
|
||||
function test2() {
|
||||
let obj = {
|
||||
toString: () => { throw 'ouch' }
|
||||
};
|
||||
encoderObj.encodeUtf8(obj);
|
||||
}
|
||||
)";
|
||||
|
||||
v8::Local<v8::UnboundScript> script = Compile(kScript);
|
||||
ASSERT_FALSE(script.IsEmpty());
|
||||
|
||||
ContextRecycler context_recycler(helper_.get());
|
||||
{
|
||||
ContextRecyclerScope scope(context_recycler); // Initialize context
|
||||
context_recycler.AddTextConversionHelpers();
|
||||
}
|
||||
|
||||
const struct TestCase {
|
||||
const char* functionName;
|
||||
const char* error;
|
||||
} kTests[] = {
|
||||
{"test1",
|
||||
"https://example.test/script.js:4 Uncaught TypeError: encodeUtf8 at "
|
||||
"least 1 argument(s) are required."},
|
||||
{"test2", "https://example.test/script.js:12 Uncaught ouch."}};
|
||||
|
||||
for (const auto& test : kTests) {
|
||||
SCOPED_TRACE(test.functionName);
|
||||
ContextRecyclerScope scope(context_recycler);
|
||||
v8::Local<v8::Context> context = scope.GetContext();
|
||||
|
||||
v8::Local<v8::Object> obj = v8::Object::New(helper_->isolate());
|
||||
context_recycler.text_conversion_helpers()->ReInitialize(context, obj);
|
||||
context->Global()
|
||||
->Set(context, helper_->CreateStringFromLiteral("encoderObj"), obj)
|
||||
.Check();
|
||||
|
||||
std::vector<std::string> error_msgs;
|
||||
Run(scope, script, test.functionName, error_msgs);
|
||||
EXPECT_THAT(error_msgs, ElementsAre(test.error));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ContextRecyclerTest, DecodeUtf8) {
|
||||
const char kScript[] = R"(
|
||||
function assertEq(l, r, label) {
|
||||
if (l !== r)
|
||||
throw 'Mismatch ' + label + ' ' + l + ' vs ' + r;
|
||||
}
|
||||
|
||||
function assertString(result, expect) {
|
||||
if (typeof result !== 'string') {
|
||||
throw 'Not a string';
|
||||
}
|
||||
assertEq(result.length, expect.length, 'length');
|
||||
for (var i = 0; i < result.length; ++i) {
|
||||
assertEq(result.charCodeAt(i), expect.charCodeAt(i), i);
|
||||
}
|
||||
}
|
||||
|
||||
function test1() {
|
||||
assertString(encoderObj.decodeUtf8(new Uint8Array([65, 66, 67])),
|
||||
'ABC');
|
||||
}
|
||||
|
||||
function test2() {
|
||||
assertString(encoderObj.decodeUtf8(new Uint8Array([65, 32, 0xD2, 0x90])),
|
||||
'A \u0490');
|
||||
}
|
||||
|
||||
// Broken utf-8 --- gets a replacement character.
|
||||
function test3() {
|
||||
assertString(encoderObj.decodeUtf8(new Uint8Array([65, 32, 0xD2])),
|
||||
'A \uFFFD');
|
||||
}
|
||||
|
||||
// Utf-8 for just a single surrogate. Every byte ended up replaced with a
|
||||
// replacement character.
|
||||
function test4() {
|
||||
assertString(encoderObj.decodeUtf8(new Uint8Array(
|
||||
[65, 32, 0xED, 0xA0, 0x80, 66])),
|
||||
'A \uFFFD\uFFFD\uFFFDB');
|
||||
}
|
||||
|
||||
// Utf-8 for something that requires two Utf-16 characters.
|
||||
function test5() {
|
||||
assertString(encoderObj.decodeUtf8(new Uint8Array(
|
||||
[65, 0xF0, 0x9F, 0x98, 0x82, 67])),
|
||||
'A\uD83D\uDE02C');
|
||||
}
|
||||
|
||||
// Partial view into an ArrayBuffer.
|
||||
function test6() {
|
||||
let buffer = new ArrayBuffer(8);
|
||||
let fullView = new Uint8Array(buffer);
|
||||
for (let i = 0; i < fullView.length; ++i)
|
||||
fullView[i] = 65 + i;
|
||||
let partialView = new Uint8Array(buffer, 2, 3);
|
||||
assertString(encoderObj.decodeUtf8(fullView),
|
||||
'ABCDEFGH');
|
||||
assertString(encoderObj.decodeUtf8(partialView),
|
||||
'CDE');
|
||||
}
|
||||
)";
|
||||
|
||||
v8::Local<v8::UnboundScript> script = Compile(kScript);
|
||||
ASSERT_FALSE(script.IsEmpty());
|
||||
|
||||
ContextRecycler context_recycler(helper_.get());
|
||||
{
|
||||
ContextRecyclerScope scope(context_recycler); // Initialize context
|
||||
context_recycler.AddTextConversionHelpers();
|
||||
}
|
||||
|
||||
for (const char* test :
|
||||
{"test1", "test2", "test3", "test4", "test5", "test6"}) {
|
||||
SCOPED_TRACE(test);
|
||||
ContextRecyclerScope scope(context_recycler);
|
||||
v8::Local<v8::Context> context = scope.GetContext();
|
||||
|
||||
v8::Local<v8::Object> obj = v8::Object::New(helper_->isolate());
|
||||
context_recycler.text_conversion_helpers()->ReInitialize(context, obj);
|
||||
context->Global()
|
||||
->Set(context, helper_->CreateStringFromLiteral("encoderObj"), obj)
|
||||
.Check();
|
||||
|
||||
std::vector<std::string> error_msgs;
|
||||
Run(scope, script, test, error_msgs);
|
||||
EXPECT_THAT(error_msgs, ElementsAre());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ContextRecyclerTest, DecodeUtf8Failure) {
|
||||
const char kScript[] = R"(
|
||||
// Not enough arguments.
|
||||
function test1() {
|
||||
encoderObj.decodeUtf8();
|
||||
}
|
||||
|
||||
// Wrong type.
|
||||
function test2() {
|
||||
encoderObj.decodeUtf8([65,66]);
|
||||
}
|
||||
)";
|
||||
|
||||
v8::Local<v8::UnboundScript> script = Compile(kScript);
|
||||
ASSERT_FALSE(script.IsEmpty());
|
||||
|
||||
ContextRecycler context_recycler(helper_.get());
|
||||
{
|
||||
ContextRecyclerScope scope(context_recycler); // Initialize context
|
||||
context_recycler.AddTextConversionHelpers();
|
||||
}
|
||||
|
||||
const struct TestCase {
|
||||
const char* functionName;
|
||||
const char* error;
|
||||
} kTests[] = {
|
||||
{"test1",
|
||||
"https://example.test/script.js:4 Uncaught TypeError: decodeUtf8 "
|
||||
"expects a Uint8Array argument."},
|
||||
{"test2",
|
||||
"https://example.test/script.js:9 Uncaught TypeError: decodeUtf8 "
|
||||
"expects a Uint8Array argument."}};
|
||||
|
||||
for (const auto& test : kTests) {
|
||||
SCOPED_TRACE(test.functionName);
|
||||
ContextRecyclerScope scope(context_recycler);
|
||||
v8::Local<v8::Context> context = scope.GetContext();
|
||||
|
||||
v8::Local<v8::Object> obj = v8::Object::New(helper_->isolate());
|
||||
context_recycler.text_conversion_helpers()->ReInitialize(context, obj);
|
||||
context->Global()
|
||||
->Set(context, helper_->CreateStringFromLiteral("encoderObj"), obj)
|
||||
.Check();
|
||||
|
||||
std::vector<std::string> error_msgs;
|
||||
Run(scope, script, test.functionName, error_msgs);
|
||||
EXPECT_THAT(error_msgs, ElementsAre(test.error));
|
||||
}
|
||||
}
|
||||
|
||||
class ContextRecyclerRealTimeReportingEnabledTest : public ContextRecyclerTest {
|
||||
public:
|
||||
ContextRecyclerRealTimeReportingEnabledTest() {
|
||||
|
@ -4,8 +4,6 @@ source_set("cpp") {
|
||||
"auction_downloader.h",
|
||||
"auction_network_events_delegate.cc",
|
||||
"auction_network_events_delegate.h",
|
||||
"auction_worklet_features.cc",
|
||||
"auction_worklet_features.h",
|
||||
"private_aggregation_reporting.cc",
|
||||
"private_aggregation_reporting.h",
|
||||
"real_time_reporting.h",
|
||||
@ -26,6 +24,24 @@ source_set("cpp") {
|
||||
"//services/network/public/cpp",
|
||||
"//url",
|
||||
]
|
||||
public_deps = [ "//content/services/auction_worklet/public/cpp:features" ]
|
||||
}
|
||||
|
||||
source_set("features") {
|
||||
sources = [
|
||||
"auction_worklet_features.cc",
|
||||
"auction_worklet_features.h",
|
||||
]
|
||||
|
||||
configs += [
|
||||
"//build/config/compiler:wexit_time_destructors",
|
||||
"//content:content_implementation",
|
||||
]
|
||||
|
||||
deps = [
|
||||
"//base",
|
||||
"//content:export",
|
||||
]
|
||||
}
|
||||
|
||||
static_library("test_support") {
|
||||
|
@ -84,4 +84,8 @@ BASE_FEATURE(kFledgeSplitTrustedSignalsFetchingURL,
|
||||
"FledgeSplitTrustedSignalsFetchingURL",
|
||||
base::FEATURE_ENABLED_BY_DEFAULT);
|
||||
|
||||
BASE_FEATURE(kFledgeTextConversionHelpers,
|
||||
"FledgeTextConversionHelpers",
|
||||
base::FEATURE_DISABLED_BY_DEFAULT);
|
||||
|
||||
} // namespace features
|
||||
|
@ -63,6 +63,9 @@ CONTENT_EXPORT BASE_DECLARE_FEATURE_PARAM(
|
||||
|
||||
CONTENT_EXPORT BASE_DECLARE_FEATURE(kFledgeSplitTrustedSignalsFetchingURL);
|
||||
|
||||
// Provide encodeUtf8/decodeUtf8 helpers.
|
||||
CONTENT_EXPORT BASE_DECLARE_FEATURE(kFledgeTextConversionHelpers);
|
||||
|
||||
} // namespace features
|
||||
|
||||
#endif // CONTENT_SERVICES_AUCTION_WORKLET_PUBLIC_CPP_AUCTION_WORKLET_FEATURES_H_
|
||||
|
@ -49,6 +49,7 @@
|
||||
#include "content/services/auction_worklet/report_bindings.h"
|
||||
#include "content/services/auction_worklet/seller_lazy_filler.h"
|
||||
#include "content/services/auction_worklet/shared_storage_bindings.h"
|
||||
#include "content/services/auction_worklet/text_conversion_helpers.h"
|
||||
#include "content/services/auction_worklet/trusted_signals.h"
|
||||
#include "content/services/auction_worklet/trusted_signals_kvv2_manager.h"
|
||||
#include "content/services/auction_worklet/webidl_compat.h"
|
||||
@ -921,6 +922,7 @@ SellerWorklet::V8State::CreateContextRecyclerAndRunTopLevel(
|
||||
permissions_policy_state_->private_aggregation_allowed,
|
||||
/*reserved_once_allowed=*/true);
|
||||
context_recycler->AddRealTimeReportingBindings();
|
||||
context_recycler->AddTextConversionHelpers();
|
||||
if (base::FeatureList::IsEnabled(blink::features::kSharedStorageAPI)) {
|
||||
context_recycler->AddSharedStorageBindings(
|
||||
shared_storage_host_remote_.is_bound()
|
||||
@ -1139,6 +1141,10 @@ void SellerWorklet::V8State::ScoreAd(
|
||||
}
|
||||
context_recycler->seller_browser_signals_lazy_filler()->FillInObject(
|
||||
browser_signal_render_url, &ad_components, browser_signals);
|
||||
if (base::FeatureList::IsEnabled(features::kFledgeTextConversionHelpers)) {
|
||||
context_recycler->text_conversion_helpers()->ReInitialize(context,
|
||||
browser_signals);
|
||||
}
|
||||
// TODO(crbug.com/336164429): Construct the fields of browser signals lazily.
|
||||
if (!browser_signals_dict.Set("topWindowHostname",
|
||||
top_window_origin_.host()) ||
|
||||
@ -1697,6 +1703,11 @@ void SellerWorklet::V8State::ReportResult(
|
||||
|
||||
v8::Local<v8::Object> browser_signals = v8::Object::New(isolate);
|
||||
gin::Dictionary browser_signals_dict(isolate, browser_signals);
|
||||
context_recycler.AddTextConversionHelpers();
|
||||
if (base::FeatureList::IsEnabled(features::kFledgeTextConversionHelpers)) {
|
||||
context_recycler.text_conversion_helpers()->ReInitialize(context,
|
||||
browser_signals);
|
||||
}
|
||||
|
||||
context_recycler.AddSellerBrowserSignalsLazyFiller();
|
||||
// Passing null for ad_components here since we do not want creative scanning
|
||||
|
@ -1007,6 +1007,16 @@ class SellerWorkletTwoThreadsTest : public SellerWorkletTest {
|
||||
size_t NumThreads() override { return 2u; }
|
||||
};
|
||||
|
||||
class SellerWorkletTextConversionsTest : public SellerWorkletTest {
|
||||
public:
|
||||
SellerWorkletTextConversionsTest() {
|
||||
feature_list_.InitAndEnableFeature(features::kFledgeTextConversionHelpers);
|
||||
}
|
||||
|
||||
protected:
|
||||
base::test::ScopedFeatureList feature_list_;
|
||||
};
|
||||
|
||||
class SellerWorkletMultiThreadingTest
|
||||
: public SellerWorkletTest,
|
||||
public testing::WithParamInterface<std::tuple<size_t, bool>> {
|
||||
@ -1938,6 +1948,26 @@ TEST_F(SellerWorkletTest, ScoreAdAdComponentsCreativeScanningMetadata) {
|
||||
3);
|
||||
}
|
||||
|
||||
TEST_F(SellerWorkletTest, ScoreAdTextConversions) {
|
||||
RunScoreAdWithReturnValueExpectingResult(
|
||||
R"('encodeUtf8' in browserSignals? 3 : 2)", 2);
|
||||
RunScoreAdWithReturnValueExpectingResult(
|
||||
R"('decodeUtf8' in browserSignals? 3 : 2)", 2);
|
||||
}
|
||||
|
||||
TEST_F(SellerWorkletTextConversionsTest, ScoreAdTextConversions) {
|
||||
RunScoreAdWithReturnValueExpectingResult(
|
||||
R"('encodeUtf8' in browserSignals? 3 : 2)", 3);
|
||||
RunScoreAdWithReturnValueExpectingResult(
|
||||
R"('decodeUtf8' in browserSignals? 3 : 2)", 3);
|
||||
|
||||
RunScoreAdWithReturnValueExpectingResult("browserSignals.encodeUtf8('A')[0]",
|
||||
65);
|
||||
RunScoreAdWithReturnValueExpectingResult(
|
||||
"browserSignals.decodeUtf8(new Uint8Array([65, 68])) === 'AD' ? 3 : 2",
|
||||
3);
|
||||
}
|
||||
|
||||
TEST_F(SellerWorkletTest, ScoreAdBid) {
|
||||
bid_ = 5;
|
||||
RunScoreAdWithReturnValueExpectingResult("bid", 5);
|
||||
@ -3427,6 +3457,28 @@ TEST_F(SellerWorkletTest, ReportResultNoAdComponentsCreativeScanningMetadata) {
|
||||
/*expected_signals_for_winner=*/"1", GURL("https://foo.test/?2"));
|
||||
}
|
||||
|
||||
TEST_F(SellerWorkletTest, ReportResultTextConversions) {
|
||||
RunReportResultCreatedScriptExpectingResult(
|
||||
"('encodeUtf8' in browserSignals) ? 2 : 1",
|
||||
/*extra_code=*/std::string(), "1",
|
||||
/*expected_report_url=*/std::nullopt);
|
||||
RunReportResultCreatedScriptExpectingResult(
|
||||
"('decodeUtf8' in browserSignals) ? 2 : 1",
|
||||
/*extra_code=*/std::string(), "1",
|
||||
/*expected_report_url=*/std::nullopt);
|
||||
}
|
||||
|
||||
TEST_F(SellerWorkletTextConversionsTest, ReportResultTextConversions) {
|
||||
RunReportResultCreatedScriptExpectingResult(
|
||||
"('encodeUtf8' in browserSignals) ? 2 : 1",
|
||||
/*extra_code=*/std::string(), "2",
|
||||
/*expected_report_url=*/std::nullopt);
|
||||
RunReportResultCreatedScriptExpectingResult(
|
||||
"('decodeUtf8' in browserSignals) ? 2 : 1",
|
||||
/*extra_code=*/std::string(), "2",
|
||||
/*expected_report_url=*/std::nullopt);
|
||||
}
|
||||
|
||||
TEST_F(SellerWorkletTest, ReportResultTopWindowOrigin) {
|
||||
top_window_origin_ = url::Origin::Create(GURL("https://foo.test/"));
|
||||
RunReportResultCreatedScriptExpectingResult(
|
||||
|
140
content/services/auction_worklet/text_conversion_helpers.cc
Normal file
140
content/services/auction_worklet/text_conversion_helpers.cc
Normal file
@ -0,0 +1,140 @@
|
||||
// Copyright 2025 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/services/auction_worklet/text_conversion_helpers.h"
|
||||
|
||||
#include "base/compiler_specific.h"
|
||||
#include "base/memory/ref_counted.h"
|
||||
#include "content/services/auction_worklet/auction_v8_helper.h"
|
||||
#include "content/services/auction_worklet/webidl_compat.h"
|
||||
#include "v8/include/v8-array-buffer.h"
|
||||
#include "v8/include/v8-exception.h"
|
||||
#include "v8/include/v8-function-callback.h"
|
||||
#include "v8/include/v8-function.h"
|
||||
#include "v8/include/v8-primitive.h"
|
||||
#include "v8/include/v8-typed-array.h"
|
||||
|
||||
namespace auction_worklet {
|
||||
|
||||
TextConversionHelpers::TextConversionHelpers(AuctionV8Helper* v8_helper)
|
||||
: v8_helper_(v8_helper) {}
|
||||
|
||||
void TextConversionHelpers::AttachToContext(v8::Local<v8::Context> context) {
|
||||
// We do everything in ReInitialize, since we attach to an object and not
|
||||
// globally.
|
||||
}
|
||||
|
||||
void TextConversionHelpers::Reset() {}
|
||||
|
||||
void TextConversionHelpers::ReInitialize(v8::Local<v8::Context> context,
|
||||
v8::Local<v8::Object> object) {
|
||||
v8::Local<v8::External> v8_this =
|
||||
v8::External::New(v8_helper_->isolate(), this);
|
||||
v8::Local<v8::Function> encode =
|
||||
v8::Function::New(context, &TextConversionHelpers::EncodeUtf8, v8_this)
|
||||
.ToLocalChecked();
|
||||
v8::Local<v8::Function> decode =
|
||||
v8::Function::New(context, &TextConversionHelpers::DecodeUtf8, v8_this)
|
||||
.ToLocalChecked();
|
||||
object
|
||||
->Set(context, v8_helper_->CreateStringFromLiteral("encodeUtf8"), encode)
|
||||
.Check();
|
||||
object
|
||||
->Set(context, v8_helper_->CreateStringFromLiteral("decodeUtf8"), decode)
|
||||
.Check();
|
||||
}
|
||||
|
||||
void TextConversionHelpers::EncodeUtf8(
|
||||
const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
TextConversionHelpers* bindings = static_cast<TextConversionHelpers*>(
|
||||
v8::External::Cast(*args.Data())->Value());
|
||||
AuctionV8Helper* v8_helper = bindings->v8_helper_;
|
||||
v8::Isolate* isolate = v8_helper->isolate();
|
||||
|
||||
AuctionV8Helper::TimeLimitScope time_limit_scope(v8_helper->GetTimeLimit());
|
||||
ArgsConverter args_converter(v8_helper, time_limit_scope, "encodeUtf8 ",
|
||||
&args,
|
||||
/*min_required_args=*/1);
|
||||
if (!args_converter.is_success()) {
|
||||
args_converter.TakeStatus().PropagateErrorsToV8(v8_helper);
|
||||
return;
|
||||
}
|
||||
|
||||
// We don't use webidl_compat to do the string conversion here since we don't
|
||||
// want the output as a std::string, we want to stick it into a UInt8Array.
|
||||
// (Also it would get DOMString and not USVString semantics, but that's
|
||||
// secondary).
|
||||
v8::Local<v8::String> v8_string;
|
||||
IdlConvert::Status conversion_status;
|
||||
{
|
||||
if (args[0]->IsString()) {
|
||||
v8_string = args[0].As<v8::String>();
|
||||
} else {
|
||||
v8::TryCatch try_catch(isolate);
|
||||
if (!args[0]
|
||||
->ToString(isolate->GetCurrentContext())
|
||||
.ToLocal(&v8_string)) {
|
||||
conversion_status = IdlConvert::MakeConversionFailure(
|
||||
try_catch, "encodeUtf8 ", {"argument 0"}, "String");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!conversion_status.is_success()) {
|
||||
conversion_status.PropagateErrorsToV8(v8_helper);
|
||||
return;
|
||||
}
|
||||
|
||||
size_t required_len = v8_string->Utf8LengthV2(isolate);
|
||||
v8::Local<v8::ArrayBuffer> buffer;
|
||||
if (!v8::ArrayBuffer::MaybeNew(
|
||||
isolate, required_len,
|
||||
v8::BackingStoreInitializationMode::kZeroInitialized)
|
||||
.ToLocal(&buffer)) {
|
||||
isolate->ThrowException(
|
||||
v8::Exception::TypeError(v8_helper->CreateStringFromLiteral(
|
||||
"Unable to allocate buffer for result")));
|
||||
return;
|
||||
}
|
||||
|
||||
size_t used_len = v8_string->WriteUtf8V2(
|
||||
isolate, reinterpret_cast<char*>(buffer->Data()), buffer->ByteLength(),
|
||||
v8::String::WriteFlags::kReplaceInvalidUtf8);
|
||||
|
||||
// The length should be as expected (despite Utf8LengthV2 not knowing about
|
||||
// kReplaceInvalidUtf8) since a mismatched surrogate and the unicode
|
||||
// replacement character both end up the same length (3 bytes).
|
||||
DCHECK_EQ(used_len, required_len);
|
||||
args.GetReturnValue().Set(v8::Uint8Array::New(buffer,
|
||||
/*byte_offset=*/0,
|
||||
/*length=*/used_len));
|
||||
}
|
||||
|
||||
void TextConversionHelpers::DecodeUtf8(
|
||||
const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
TextConversionHelpers* bindings = static_cast<TextConversionHelpers*>(
|
||||
v8::External::Cast(*args.Data())->Value());
|
||||
AuctionV8Helper* v8_helper = bindings->v8_helper_;
|
||||
v8::Isolate* isolate = v8_helper->isolate();
|
||||
|
||||
if (!args[0]->IsUint8Array()) {
|
||||
isolate->ThrowException(
|
||||
v8::Exception::TypeError(v8_helper->CreateStringFromLiteral(
|
||||
"decodeUtf8 expects a Uint8Array argument")));
|
||||
return;
|
||||
}
|
||||
|
||||
v8::Local<v8::Uint8Array> array = args[0].As<v8::Uint8Array>();
|
||||
// SAFETY: Uint8Array data is from ByteOffset and of length ByteLength inside
|
||||
// the ArrayBuffer it wraps.
|
||||
args.GetReturnValue().Set(
|
||||
v8::String::NewFromUtf8(
|
||||
isolate,
|
||||
UNSAFE_BUFFERS(reinterpret_cast<char*>(array->Buffer()->Data()) +
|
||||
array->ByteOffset()),
|
||||
v8::NewStringType::kNormal, array->ByteLength())
|
||||
.ToLocalChecked());
|
||||
}
|
||||
|
||||
} // namespace auction_worklet
|
37
content/services/auction_worklet/text_conversion_helpers.h
Normal file
37
content/services/auction_worklet/text_conversion_helpers.h
Normal file
@ -0,0 +1,37 @@
|
||||
// Copyright 2025 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef CONTENT_SERVICES_AUCTION_WORKLET_TEXT_CONVERSION_HELPERS_H_
|
||||
#define CONTENT_SERVICES_AUCTION_WORKLET_TEXT_CONVERSION_HELPERS_H_
|
||||
|
||||
#include "content/common/content_export.h"
|
||||
#include "content/services/auction_worklet/context_recycler.h"
|
||||
#include "v8/include/v8-forward.h"
|
||||
|
||||
namespace auction_worklet {
|
||||
|
||||
class AuctionV8Helper;
|
||||
|
||||
// Utilities to add JS functions to help convert between JS Strings and utf-8
|
||||
// arrays.
|
||||
class CONTENT_EXPORT TextConversionHelpers : public Bindings {
|
||||
public:
|
||||
explicit TextConversionHelpers(AuctionV8Helper* v8_helper);
|
||||
|
||||
void AttachToContext(v8::Local<v8::Context> context) override;
|
||||
void Reset() override;
|
||||
|
||||
void ReInitialize(v8::Local<v8::Context> context,
|
||||
v8::Local<v8::Object> object);
|
||||
|
||||
private:
|
||||
static void EncodeUtf8(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void DecodeUtf8(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
|
||||
const raw_ptr<AuctionV8Helper> v8_helper_;
|
||||
};
|
||||
|
||||
} // namespace auction_worklet
|
||||
|
||||
#endif // CONTENT_SERVICES_AUCTION_WORKLET_TEXT_CONVERSION_HELPERS_H_
|
@ -1851,6 +1851,7 @@ test("content_browsertests") {
|
||||
"//content/public/gpu",
|
||||
"//content/public/renderer",
|
||||
"//content/renderer:for_content_tests",
|
||||
"//content/services/auction_worklet/public/cpp:features",
|
||||
"//content/services/auction_worklet/public/cpp:test_support",
|
||||
"//content/services/auction_worklet/public/mojom:for_content_tests",
|
||||
"//content/shell:content_browsertests_mojom",
|
||||
|
@ -212,9 +212,13 @@ function validateBrowserSignals(browserSignals, isGenerateBid) {
|
||||
throw 'Wrong seller ' + browserSignals.seller;
|
||||
if ('topLevelSeller' in browserSignals)
|
||||
throw 'Wrong topLevelSeller ' + browserSignals.topLevelSeller;
|
||||
if (!(browserSignals.decodeUtf8 instanceof Function))
|
||||
throw 'Wrong decodeUtf8';
|
||||
if (!(browserSignals.encodeUtf8 instanceof Function))
|
||||
throw 'Wrong encodeUtf8';
|
||||
|
||||
if (isGenerateBid) {
|
||||
if (Object.keys(browserSignals).length !== 11) {
|
||||
if (Object.keys(browserSignals).length !== 13) {
|
||||
throw 'Wrong number of browser signals fields ' +
|
||||
JSON.stringify(browserSignals);
|
||||
}
|
||||
@ -237,7 +241,7 @@ function validateBrowserSignals(browserSignals, isGenerateBid) {
|
||||
if (browserSignals.multiBidLimit !== 1)
|
||||
throw 'Wrong multiBidLimit ' + browserSignals.multiBidLimit;
|
||||
} else {
|
||||
if (Object.keys(browserSignals).length !== 16) {
|
||||
if (Object.keys(browserSignals).length !== 18) {
|
||||
throw 'Wrong number of browser signals fields ' +
|
||||
JSON.stringify(browserSignals);
|
||||
}
|
||||
|
@ -192,9 +192,13 @@ function validateBrowserSignals(browserSignals, isGenerateBid) {
|
||||
throw 'Wrong seller ' + browserSignals.seller;
|
||||
if (!browserSignals.topLevelSeller.startsWith('https://b.test'))
|
||||
throw 'Wrong topLevelSeller ' + browserSignals.topLevelSeller;
|
||||
if (!(browserSignals.decodeUtf8 instanceof Function))
|
||||
throw 'Wrong decodeUtf8';
|
||||
if (!(browserSignals.encodeUtf8 instanceof Function))
|
||||
throw 'Wrong encodeUtf8';
|
||||
|
||||
if (isGenerateBid) {
|
||||
if (Object.keys(browserSignals).length !== 12) {
|
||||
if (Object.keys(browserSignals).length !== 14) {
|
||||
throw 'Wrong number of browser signals fields ' +
|
||||
JSON.stringify(browserSignals);
|
||||
}
|
||||
@ -217,7 +221,7 @@ function validateBrowserSignals(browserSignals, isGenerateBid) {
|
||||
if (browserSignals.multiBidLimit !== 1)
|
||||
throw 'Wrong multiBidLimit ' + browserSignals.multiBidLimit;
|
||||
} else {
|
||||
if (Object.keys(browserSignals).length !== 17) {
|
||||
if (Object.keys(browserSignals).length !== 19) {
|
||||
throw 'Wrong number of browser signals fields ' +
|
||||
JSON.stringify(browserSignals);
|
||||
}
|
||||
|
@ -171,10 +171,14 @@ function validateBrowserSignals(browserSignals, isScoreAd) {
|
||||
throw 'Wrong renderUrl ' + browserSignals.renderUrl;
|
||||
if (browserSignals.bidCurrency != 'USD')
|
||||
throw 'Wrong bidCurrency ' + browserSignals.bidCurrency;
|
||||
if (!(browserSignals.decodeUtf8 instanceof Function))
|
||||
throw 'Wrong decodeUtf8';
|
||||
if (!(browserSignals.encodeUtf8 instanceof Function))
|
||||
throw 'Wrong encodeUtf8';
|
||||
|
||||
// Fields that vary by method.
|
||||
if (isScoreAd) {
|
||||
if (Object.keys(browserSignals).length !== 12) {
|
||||
if (Object.keys(browserSignals).length !== 14) {
|
||||
throw 'Wrong number of browser signals fields ' +
|
||||
JSON.stringify(browserSignals);
|
||||
}
|
||||
@ -197,7 +201,7 @@ function validateBrowserSignals(browserSignals, isScoreAd) {
|
||||
throw 'Wrong forDebuggingOnlySampling ' +
|
||||
browserSignals.forDebuggingOnlySampling;
|
||||
} else {
|
||||
if (Object.keys(browserSignals).length !== 13) {
|
||||
if (Object.keys(browserSignals).length !== 15) {
|
||||
throw 'Wrong number of browser signals fields ' +
|
||||
JSON.stringify(browserSignals);
|
||||
}
|
||||
|
@ -185,12 +185,16 @@ function validateBrowserSignals(browserSignals, isScoreAd) {
|
||||
throw 'Wrong renderURL ' + browserSignals.renderURL;
|
||||
if (browserSignals.renderUrl !== "https://example.com/render")
|
||||
throw 'Wrong renderUrl ' + browserSignals.renderUrl;
|
||||
if (browserSignals.bidCurrency !== 'CAD')
|
||||
throw 'Wrong bidCurrency ' + browserSignals.bidCurrency;
|
||||
if (browserSignals.bidCurrency !== 'CAD')
|
||||
throw 'Wrong bidCurrency ' + browserSignals.bidCurrency;
|
||||
if (!(browserSignals.decodeUtf8 instanceof Function))
|
||||
throw 'Wrong decodeUtf8';
|
||||
if (!(browserSignals.encodeUtf8 instanceof Function))
|
||||
throw 'Wrong encodeUtf8';
|
||||
|
||||
// Fields that vary by method.
|
||||
if (isScoreAd) {
|
||||
if (Object.keys(browserSignals).length !== 12) {
|
||||
if (Object.keys(browserSignals).length !== 14) {
|
||||
throw 'Wrong number of browser signals fields ' +
|
||||
JSON.stringify(browserSignals);
|
||||
}
|
||||
@ -213,7 +217,7 @@ function validateBrowserSignals(browserSignals, isScoreAd) {
|
||||
throw 'Wrong forDebuggingOnlySampling ' +
|
||||
browserSignals.forDebuggingOnlySampling;
|
||||
} else {
|
||||
if (Object.keys(browserSignals).length !== 11) {
|
||||
if (Object.keys(browserSignals).length !== 13) {
|
||||
throw 'Wrong number of browser signals fields ' +
|
||||
JSON.stringify(browserSignals);
|
||||
}
|
||||
|
@ -193,10 +193,14 @@ function validateBrowserSignals(browserSignals, isScoreAd) {
|
||||
throw 'Wrong dataVersion ' + browserSignals.dataVersion;
|
||||
if (browserSignals.bidCurrency !== 'USD')
|
||||
throw 'Wrong bidCurrency ' + browserSignals.bidCurrency;
|
||||
if (!(browserSignals.decodeUtf8 instanceof Function))
|
||||
throw 'Wrong decodeUtf8';
|
||||
if (!(browserSignals.encodeUtf8 instanceof Function))
|
||||
throw 'Wrong encodeUtf8';
|
||||
|
||||
// Fields that vary by method.
|
||||
if (isScoreAd) {
|
||||
if (Object.keys(browserSignals).length !== 11) {
|
||||
if (Object.keys(browserSignals).length !== 13) {
|
||||
throw 'Wrong number of browser signals fields ' +
|
||||
JSON.stringify(browserSignals);
|
||||
}
|
||||
@ -217,7 +221,7 @@ function validateBrowserSignals(browserSignals, isScoreAd) {
|
||||
throw 'Wrong forDebuggingOnlySampling ' +
|
||||
browserSignals.forDebuggingOnlySampling;
|
||||
} else {
|
||||
if (Object.keys(browserSignals).length !== 10) {
|
||||
if (Object.keys(browserSignals).length !== 12) {
|
||||
throw 'Wrong number of browser signals fields ' +
|
||||
JSON.stringify(browserSignals);
|
||||
}
|
||||
|
@ -18378,6 +18378,25 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"ProtectedAudienceAuctionTextConversionHelpers": [
|
||||
{
|
||||
"platforms": [
|
||||
"android",
|
||||
"chromeos",
|
||||
"linux",
|
||||
"mac",
|
||||
"windows"
|
||||
],
|
||||
"experiments": [
|
||||
{
|
||||
"name": "Enabled",
|
||||
"enable_features": [
|
||||
"FledgeTextConversionHelpers"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"ProtectedAudienceBAndAServerAPIMultiSeller": [
|
||||
{
|
||||
"platforms": [
|
||||
|
@ -51,6 +51,11 @@ subsetTest(promise_test, async test => {
|
||||
// Remove deprecated field, if present.
|
||||
delete browserSignals.prevWins;
|
||||
|
||||
// encode/decode utf-8 are tested separately, and aren't
|
||||
// suitable to equality testing.
|
||||
delete browserSignals.encodeUtf8;
|
||||
delete browserSignals.decodeUtf8;
|
||||
|
||||
if (!deepEquals(browserSignals, expectedBrowserSignals))
|
||||
throw "Unexpected browserSignals: " + JSON.stringify(browserSignals);`
|
||||
});
|
||||
|
209
third_party/blink/web_tests/external/wpt/fledge/tentative/utf8-helpers.https.window.js
vendored
Normal file
209
third_party/blink/web_tests/external/wpt/fledge/tentative/utf8-helpers.https.window.js
vendored
Normal file
@ -0,0 +1,209 @@
|
||||
// META: script=/resources/testdriver.js
|
||||
// META: script=/resources/testdriver-vendor.js
|
||||
// META: script=/common/utils.js
|
||||
// META: script=resources/fledge-util.sub.js
|
||||
// META: script=/common/subset-tests.js
|
||||
// META: timeout=long
|
||||
// META: variant=?1-5
|
||||
// META: variant=?6-10
|
||||
// META: variant=?11-15
|
||||
|
||||
'use strict;'
|
||||
|
||||
// These tests cover encodeUtf8 and decodeUtf8.
|
||||
|
||||
const helpers = `
|
||||
function assertEq(l, r, label) {
|
||||
if (l !== r)
|
||||
throw 'Mismatch ' + label;
|
||||
}
|
||||
|
||||
function assertByteArray(result, expect) {
|
||||
if (!(result instanceof Uint8Array)) {
|
||||
throw 'Not a Uint8Array!';
|
||||
}
|
||||
assertEq(result.length, expect.length, 'length');
|
||||
for (var i = 0; i < result.length; ++i) {
|
||||
assertEq(result[i], expect[i], i);
|
||||
}
|
||||
}
|
||||
|
||||
function assertString(result, expect) {
|
||||
if (typeof result !== 'string') {
|
||||
throw 'Not a string';
|
||||
}
|
||||
assertEq(result.length, expect.length, 'length');
|
||||
for (var i = 0; i < result.length; ++i) {
|
||||
assertEq(result.charCodeAt(i), expect.charCodeAt(i), i);
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
async function testConversion(test, conversionBody) {
|
||||
const uuid = generateUuid(test);
|
||||
let sellerReportURL = createSellerReportURL(uuid);
|
||||
let bidderReportURL = createBidderReportURL(uuid);
|
||||
|
||||
let fullBody = `
|
||||
${helpers}
|
||||
${conversionBody}
|
||||
`;
|
||||
|
||||
let biddingLogicURL = createBiddingScriptURL({
|
||||
generateBid: fullBody,
|
||||
reportWin: fullBody + `sendReportTo('${bidderReportURL}')`
|
||||
});
|
||||
|
||||
let decisionLogicURL = createDecisionScriptURL(uuid, {
|
||||
scoreAd: fullBody,
|
||||
reportResult: fullBody + `sendReportTo('${sellerReportURL}')`
|
||||
});
|
||||
|
||||
await joinInterestGroup(test, uuid, {biddingLogicURL: biddingLogicURL});
|
||||
await runBasicFledgeAuctionAndNavigate(
|
||||
test, uuid, {decisionLogicURL: decisionLogicURL});
|
||||
await waitForObservedRequests(uuid, [sellerReportURL, bidderReportURL]);
|
||||
}
|
||||
|
||||
async function testConversionException(test, conversionBody) {
|
||||
const uuid = generateUuid(test);
|
||||
let sellerReportURL = createSellerReportURL(uuid);
|
||||
let bidderReportURL = createBidderReportURL(uuid);
|
||||
|
||||
let fullBody = `
|
||||
${helpers}
|
||||
try {
|
||||
${conversionBody};
|
||||
return -1;
|
||||
} catch (e) {
|
||||
}
|
||||
`;
|
||||
|
||||
let biddingLogicURL = createBiddingScriptURL({
|
||||
generateBid: fullBody,
|
||||
reportWin: fullBody + `sendReportTo('${bidderReportURL}')`
|
||||
});
|
||||
|
||||
let decisionLogicURL = createDecisionScriptURL(uuid, {
|
||||
scoreAd: fullBody,
|
||||
reportResult: fullBody + `sendReportTo('${sellerReportURL}')`
|
||||
});
|
||||
|
||||
await joinInterestGroup(test, uuid, {biddingLogicURL: biddingLogicURL});
|
||||
await runBasicFledgeAuctionAndNavigate(
|
||||
test, uuid, {decisionLogicURL: decisionLogicURL});
|
||||
await waitForObservedRequests(uuid, [sellerReportURL, bidderReportURL]);
|
||||
}
|
||||
|
||||
subsetTest(promise_test, async test => {
|
||||
await testConversion(
|
||||
test, `let result = browserSignals.encodeUtf8('ABC\u0490');
|
||||
assertByteArray(result, [65, 66, 67, 0xD2, 0x90])`);
|
||||
}, 'encodeUtf8 - basic');
|
||||
|
||||
subsetTest(promise_test, async test => {
|
||||
await testConversion(
|
||||
test, `let result = browserSignals.encodeUtf8('A\uD800C');
|
||||
assertByteArray(result, [65, 0xEF, 0xBF, 0xBD, 67])`);
|
||||
}, 'encodeUtf8 - mismatched surrogate gets replaced');
|
||||
|
||||
subsetTest(promise_test, async test => {
|
||||
await testConversion(
|
||||
test, `let result = browserSignals.encodeUtf8('A\uD83D\uDE02C');
|
||||
assertByteArray(result, [65, 0xF0, 0x9F, 0x98, 0x82, 67])`);
|
||||
}, 'encodeUtf8 - surrogate pair combined');
|
||||
|
||||
subsetTest(promise_test, async test => {
|
||||
const conversionBody = `
|
||||
let obj = {
|
||||
toString: () => "ABC"
|
||||
};
|
||||
let result = browserSignals.encodeUtf8(obj);
|
||||
assertByteArray(result, [65, 66, 67])
|
||||
`;
|
||||
await testConversion(test, conversionBody);
|
||||
}, 'encodeUtf8 - custom string conversion');
|
||||
|
||||
subsetTest(promise_test, async test => {
|
||||
const conversionBody = `
|
||||
let result = browserSignals.encodeUtf8();
|
||||
`;
|
||||
await testConversionException(test, conversionBody);
|
||||
}, 'encodeUtf8 - not enough arguments');
|
||||
|
||||
subsetTest(promise_test, async test => {
|
||||
const conversionBody = `
|
||||
let obj = {
|
||||
toString: () => { throw 'no go' }
|
||||
};
|
||||
let result = browserSignals.encodeUtf8(obj);
|
||||
`;
|
||||
await testConversionException(test, conversionBody);
|
||||
}, 'encodeUtf8 - custom string conversion failure');
|
||||
|
||||
subsetTest(promise_test, async test => {
|
||||
const conversionBody = `
|
||||
let input = new Uint8Array([65, 66, 0xD2, 0x90, 67]);
|
||||
let result = browserSignals.decodeUtf8(input);
|
||||
assertString(result, 'AB\u0490C');
|
||||
`;
|
||||
await testConversion(test, conversionBody);
|
||||
}, 'decodeUtf8 - basic');
|
||||
|
||||
subsetTest(promise_test, async test => {
|
||||
const conversionBody = `
|
||||
let input = new Uint8Array([65, 32, 0xD2]);
|
||||
let result = browserSignals.decodeUtf8(input);
|
||||
if (result.indexOf('\uFFFD') === -1)
|
||||
throw 'Should have replacement character';
|
||||
`;
|
||||
await testConversion(test, conversionBody);
|
||||
}, 'decodeUtf8 - broken utf-8');
|
||||
|
||||
subsetTest(promise_test, async test => {
|
||||
const conversionBody = `
|
||||
let input = new Uint8Array([65, 32, 0xED, 0xA0, 0x80, 66]);
|
||||
let result = browserSignals.decodeUtf8(input);
|
||||
if (result.indexOf('\uFFFD') === -1)
|
||||
throw 'Should have replacement character';
|
||||
`;
|
||||
await testConversion(test, conversionBody);
|
||||
}, 'decodeUtf8 - mismatched surrogate');
|
||||
|
||||
subsetTest(promise_test, async test => {
|
||||
const conversionBody = `
|
||||
let input = new Uint8Array([65, 0xF0, 0x9F, 0x98, 0x82, 67]);
|
||||
let result = browserSignals.decodeUtf8(input);
|
||||
assertString(result, 'A\uD83D\uDE02C');
|
||||
`;
|
||||
await testConversion(test, conversionBody);
|
||||
}, 'decodeUtf8 - non-BMP character');
|
||||
|
||||
subsetTest(promise_test, async test => {
|
||||
const conversionBody = `
|
||||
let buffer = new ArrayBuffer(8);
|
||||
let fullView = new Uint8Array(buffer);
|
||||
for (let i = 0; i < fullView.length; ++i)
|
||||
fullView[i] = 65 + i;
|
||||
let partialView = new Uint8Array(buffer, 2, 3);
|
||||
assertString(browserSignals.decodeUtf8(fullView),
|
||||
'ABCDEFGH');
|
||||
assertString(browserSignals.decodeUtf8(partialView),
|
||||
'CDE');
|
||||
`;
|
||||
await testConversion(test, conversionBody);
|
||||
}, 'decodeUtf8 - proper Uint8Array handling');
|
||||
|
||||
subsetTest(promise_test, async test => {
|
||||
const conversionBody = `
|
||||
let result = browserSignals.decodeUtf8();
|
||||
`;
|
||||
await testConversionException(test, conversionBody);
|
||||
}, 'decodeUtf8 - not enough arguments');
|
||||
|
||||
subsetTest(promise_test, async test => {
|
||||
const conversionBody = `
|
||||
let result = browserSignals.decodeUtf8([65, 32, 66]);
|
||||
`;
|
||||
await testConversionException(test, conversionBody);
|
||||
}, 'decodeUtf8 - wrong type');
|
Reference in New Issue
Block a user