Factor out Private Aggregation API parsing into joint utils file
This avoids duplication of complex logic and ensures that any future
changes are applied to both worklets. This new joint logic is put into a
new //content/services/ directory to allow dependencies from both
worklets while also allowing dependencies on v8 and gin. A README is
added to clarify that this directory does not represent a service in its
own right.
This cl should have no effect on behavior as the logic it consolidates
should be identical.
Bug: 1351757
Change-Id: I767c93520c1936a09c970594829e92da43d77fd8
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3879805
Reviewed-by: Qingxin Wu <qingxinwu@google.com>
Reviewed-by: Jeremy Roman <jbroman@chromium.org>
Auto-Submit: Alex Turner <alexmt@chromium.org>
Reviewed-by: Yao Xiao <yaoxia@chromium.org>
Reviewed-by: John Abd-El-Malek <jam@chromium.org>
Reviewed-by: John Delaney <johnidel@chromium.org>
Commit-Queue: Alex Turner <alexmt@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1051945}
diff --git a/content/services/auction_worklet/BUILD.gn b/content/services/auction_worklet/BUILD.gn
index 8918f57d..8e9185fd 100644
--- a/content/services/auction_worklet/BUILD.gn
+++ b/content/services/auction_worklet/BUILD.gn
@@ -87,6 +87,7 @@
"//base",
"//content:export",
"//content/common",
+ "//content/services/worklet_utils",
"//gin",
"//mojo/public/cpp/bindings",
"//net",
diff --git a/content/services/auction_worklet/private_aggregation_bindings.cc b/content/services/auction_worklet/private_aggregation_bindings.cc
index 4b363f6..559c870 100644
--- a/content/services/auction_worklet/private_aggregation_bindings.cc
+++ b/content/services/auction_worklet/private_aggregation_bindings.cc
@@ -4,8 +4,6 @@
#include "content/services/auction_worklet/private_aggregation_bindings.h"
-#include <stdint.h>
-
#include <iterator>
#include <memory>
#include <string>
@@ -13,17 +11,12 @@
#include "base/feature_list.h"
#include "base/ranges/algorithm.h"
-#include "base/strings/string_util.h"
#include "content/common/aggregatable_report.mojom.h"
#include "content/common/private_aggregation_features.h"
#include "content/services/auction_worklet/auction_v8_helper.h"
#include "content/services/auction_worklet/public/mojom/private_aggregation_request.mojom.h"
-#include "gin/converter.h"
-#include "gin/dictionary.h"
-#include "third_party/abseil-cpp/absl/numeric/int128.h"
-#include "third_party/abseil-cpp/absl/types/optional.h"
-#include "v8/include/v8-context.h"
-#include "v8/include/v8-exception.h"
+#include "content/services/worklet_utils/private_aggregation_utils.h"
+#include "gin/arguments.h"
#include "v8/include/v8-external.h"
#include "v8/include/v8-function-callback.h"
#include "v8/include/v8-local-handle.h"
@@ -32,86 +25,6 @@
namespace auction_worklet {
-namespace {
-
-// If returns `absl::nullopt`, will output an error to `error_out`.
-absl::optional<absl::uint128> ConvertBigIntToUint128(
- v8::MaybeLocal<v8::BigInt> maybe_bigint,
- std::string* error_out) {
- if (maybe_bigint.IsEmpty()) {
- *error_out = "Failed to interpret as BigInt";
- return absl::nullopt;
- }
-
- v8::Local<v8::BigInt> local_bigint = maybe_bigint.ToLocalChecked();
- if (local_bigint.IsEmpty()) {
- *error_out = "Failed to interpret as BigInt";
- return absl::nullopt;
- }
- if (local_bigint->WordCount() > 2) {
- *error_out = "BigInt is too large";
- return absl::nullopt;
- }
- // Signals the size of the `words` array to `ToWordsArray()`. The number of
- // elements actually used is then written here by the function.
- int word_count = 2;
- int sign_bit = 0;
- uint64_t words[2]; // Least significant to most significant.
- local_bigint->ToWordsArray(&sign_bit, &word_count, words);
- if (sign_bit) {
- *error_out = "BigInt must be non-negative";
- return absl::nullopt;
- }
- if (word_count == 0) {
- words[0] = 0;
- }
- if (word_count <= 1) {
- words[1] = 0;
- }
-
- return absl::MakeUint128(words[1], words[0]);
-}
-
-// In case of failure, will return `absl::nullopt` and output an error to
-// `error_out`.
-absl::optional<uint64_t> ParseDebugKey(gin::Dictionary dict,
- v8::Local<v8::Context>& context,
- std::string* error_out) {
- v8::Local<v8::Value> js_debug_key;
-
- if (!dict.Get("debug_key", &js_debug_key) || js_debug_key.IsEmpty() ||
- js_debug_key->IsNullOrUndefined()) {
- return absl::nullopt;
- }
-
- if (js_debug_key->IsUint32()) {
- v8::Maybe<uint32_t> maybe_debug_key = js_debug_key->Uint32Value(context);
- if (maybe_debug_key.IsNothing()) {
- *error_out = "Failed to interpret value as integer";
- }
- return maybe_debug_key.ToChecked();
- }
-
- if (js_debug_key->IsBigInt()) {
- absl::optional<absl::uint128> maybe_debug_key =
- ConvertBigIntToUint128(js_debug_key->ToBigInt(context), error_out);
- if (!maybe_debug_key.has_value()) {
- return absl::nullopt;
- }
- if (absl::Uint128High64(maybe_debug_key.value()) != 0) {
- *error_out = "BigInt is too large";
- return absl::nullopt;
- }
- return absl::Uint128Low64(maybe_debug_key.value());
- }
-
- *error_out =
- "debug_key must be either a non-negative integer Number or BigInt";
- return absl::nullopt;
-}
-
-} // namespace
-
PrivateAggregationBindings::PrivateAggregationBindings(
AuctionV8Helper* v8_helper)
: v8_helper_(v8_helper) {}
@@ -184,104 +97,16 @@
PrivateAggregationBindings* bindings =
static_cast<PrivateAggregationBindings*>(
v8::External::Cast(*args.Data())->Value());
- v8::Isolate* isolate = args.GetIsolate();
- v8::Local<v8::Context> context = isolate->GetCurrentContext();
- AuctionV8Helper* v8_helper = bindings->v8_helper_;
- // Any additional arguments are ignored.
- if (args.Length() == 0 || args[0].IsEmpty() || !args[0]->IsObject()) {
- isolate->ThrowException(
- v8::Exception::TypeError(v8_helper->CreateStringFromLiteral(
- "sendHistogramReport requires 1 object parameter")));
- return;
- }
-
- gin::Dictionary dict(isolate);
-
- if (!gin::ConvertFromV8(isolate, args[0], &dict)) {
- isolate->ThrowException(
- v8::Exception::TypeError(v8_helper->CreateStringFromLiteral(
- "Invalid argument in sendHistogramReport")));
- return;
- }
-
- v8::Local<v8::Value> js_bucket;
- v8::Local<v8::Value> js_value;
-
- if (!dict.Get("bucket", &js_bucket) || js_bucket.IsEmpty() ||
- js_bucket->IsNullOrUndefined()) {
- isolate->ThrowException(
- v8::Exception::TypeError(v8_helper->CreateStringFromLiteral(
- "Invalid or missing bucket in sendHistogramReport argument")));
- return;
- }
-
- if (!dict.Get("value", &js_value) || js_value.IsEmpty() ||
- js_value->IsNullOrUndefined()) {
- isolate->ThrowException(
- v8::Exception::TypeError(v8_helper->CreateStringFromLiteral(
- "Invalid or missing value in sendHistogramReport argument")));
- return;
- }
-
- absl::uint128 bucket;
- int value;
-
- if (js_bucket->IsUint32()) {
- v8::Maybe<uint32_t> maybe_bucket = js_bucket->Uint32Value(context);
- if (maybe_bucket.IsNothing()) {
- isolate->ThrowException(
- v8::Exception::TypeError(v8_helper->CreateStringFromLiteral(
- "Failed to interpret value as integer")));
- return;
- }
- bucket = maybe_bucket.ToChecked();
- } else if (js_bucket->IsBigInt()) {
- std::string error;
- absl::optional<absl::uint128> maybe_bucket =
- ConvertBigIntToUint128(js_bucket->ToBigInt(context), &error);
- if (!maybe_bucket.has_value()) {
- DCHECK(base::IsStringUTF8(error));
- isolate->ThrowException(v8::Exception::TypeError(
- v8_helper->CreateUtf8String(error).ToLocalChecked()));
- return;
- }
- bucket = maybe_bucket.value();
- } else {
- isolate->ThrowException(
- v8::Exception::TypeError(v8_helper->CreateStringFromLiteral(
- "Bucket must be either an integer Number or BigInt")));
- return;
- }
-
- if (js_value->IsInt32()) {
- v8::Maybe<int32_t> maybe_value = js_value->Int32Value(context);
- if (maybe_value.IsNothing()) {
- isolate->ThrowException(
- v8::Exception::TypeError(v8_helper->CreateStringFromLiteral(
- "Failed to interpret value as integer")));
- return;
- }
- value = maybe_value.ToChecked();
- } else if (js_value->IsBigInt()) {
- isolate->ThrowException(v8::Exception::TypeError(
- v8_helper->CreateStringFromLiteral("Value cannot be a BigInt")));
- return;
- } else {
- isolate->ThrowException(v8::Exception::TypeError(
- v8_helper->CreateStringFromLiteral("Value must be an integer Number")));
- return;
- }
-
- if (value < 0) {
- isolate->ThrowException(v8::Exception::TypeError(
- v8_helper->CreateStringFromLiteral("Value must be non-negative")));
+ content::mojom::AggregatableReportHistogramContributionPtr contribution =
+ worklet_utils::ParseSendHistogramReportArguments(gin::Arguments(args));
+ if (contribution.is_null()) {
+ // Indicates an exception was thrown.
return;
}
bindings->private_aggregation_contributions_.push_back(
- content::mojom::AggregatableReportHistogramContribution::New(bucket,
- value));
+ std::move(contribution));
}
void PrivateAggregationBindings::EnableDebugMode(
@@ -289,43 +114,9 @@
PrivateAggregationBindings* bindings =
static_cast<PrivateAggregationBindings*>(
v8::External::Cast(*args.Data())->Value());
- v8::Isolate* isolate = args.GetIsolate();
- v8::Local<v8::Context> context = isolate->GetCurrentContext();
- AuctionV8Helper* v8_helper = bindings->v8_helper_;
- if (bindings->debug_mode_details_.is_enabled) {
- isolate->ThrowException(
- v8::Exception::TypeError(v8_helper->CreateStringFromLiteral(
- "enableDebugMode may be called at most once")));
- return;
- }
-
- // If no arguments are provided, no debug key is set.
- if (args.Length() >= 1 && !args[0].IsEmpty()) {
- gin::Dictionary dict(isolate);
-
- if (!gin::ConvertFromV8(isolate, args[0], &dict)) {
- isolate->ThrowException(
- v8::Exception::TypeError(v8_helper->CreateStringFromLiteral(
- "Invalid argument in enableDebugMode")));
- return;
- }
-
- std::string error;
- absl::optional<uint64_t> maybe_debug_key =
- ParseDebugKey(dict, context, &error);
- if (!maybe_debug_key.has_value()) {
- DCHECK(base::IsStringUTF8(error));
- isolate->ThrowException(v8::Exception::TypeError(
- v8_helper->CreateUtf8String(error).ToLocalChecked()));
- return;
- }
-
- bindings->debug_mode_details_.debug_key =
- content::mojom::DebugKey::New(maybe_debug_key.value());
- }
-
- bindings->debug_mode_details_.is_enabled = true;
+ worklet_utils::ParseAndApplyEnableDebugModeArguments(
+ gin::Arguments(args), bindings->debug_mode_details_);
}
} // namespace auction_worklet
diff --git a/content/services/shared_storage_worklet/BUILD.gn b/content/services/shared_storage_worklet/BUILD.gn
index 388d3ac..95017ec5 100644
--- a/content/services/shared_storage_worklet/BUILD.gn
+++ b/content/services/shared_storage_worklet/BUILD.gn
@@ -37,6 +37,7 @@
deps = [
"//base",
"//content:export",
+ "//content/services/worklet_utils",
"//gin",
"//mojo/public/cpp/bindings",
"//services/network/public/cpp",
diff --git a/content/services/shared_storage_worklet/private_aggregation.cc b/content/services/shared_storage_worklet/private_aggregation.cc
index ebb52803..26f3f637 100644
--- a/content/services/shared_storage_worklet/private_aggregation.cc
+++ b/content/services/shared_storage_worklet/private_aggregation.cc
@@ -4,131 +4,18 @@
#include "content/services/shared_storage_worklet/private_aggregation.h"
-#include <stdint.h>
-
#include <string>
#include <utility>
#include "content/common/aggregatable_report.mojom.h"
#include "content/common/private_aggregation_host.mojom.h"
-#include "content/services/shared_storage_worklet/worklet_v8_helper.h"
+#include "content/services/worklet_utils/private_aggregation_utils.h"
#include "gin/arguments.h"
-#include "gin/converter.h"
-#include "gin/dictionary.h"
-#include "third_party/abseil-cpp/absl/numeric/int128.h"
-#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom-shared.h"
-#include "v8/include/v8-context.h"
-#include "v8/include/v8-exception.h"
-#include "v8/include/v8-external.h"
-#include "v8/include/v8-function-callback.h"
-#include "v8/include/v8-local-handle.h"
-#include "v8/include/v8-object.h"
-#include "v8/include/v8-template.h"
+#include "v8/include/v8-isolate.h"
namespace shared_storage_worklet {
-// TODO(crbug.com/1351757): The argument parsing logic in this file should be
-// factored out into a joint utils file and shared with the FLEDGE worklet.
-
-namespace {
-
-v8::Local<v8::String> CreateStringFromLiteral(v8::Isolate* isolate,
- const char* ascii_string) {
- DCHECK(base::IsStringASCII(ascii_string));
- return v8::String::NewFromUtf8(isolate, ascii_string,
- v8::NewStringType::kNormal,
- strlen(ascii_string))
- .ToLocalChecked();
-}
-
-v8::MaybeLocal<v8::String> CreateUtf8String(v8::Isolate* isolate,
- base::StringPiece utf8_string) {
- if (!base::IsStringUTF8(utf8_string))
- return v8::MaybeLocal<v8::String>();
- return v8::String::NewFromUtf8(isolate, utf8_string.data(),
- v8::NewStringType::kNormal,
- utf8_string.length());
-}
-
-// If returns `absl::nullopt`, will output an error to `error_out`.
-absl::optional<absl::uint128> ConvertBigIntToUint128(
- v8::MaybeLocal<v8::BigInt> maybe_bigint,
- std::string* error_out) {
- DCHECK(error_out);
-
- if (maybe_bigint.IsEmpty()) {
- *error_out = "Failed to interpret as BigInt";
- return absl::nullopt;
- }
-
- v8::Local<v8::BigInt> local_bigint = maybe_bigint.ToLocalChecked();
- if (local_bigint.IsEmpty()) {
- *error_out = "Failed to interpret as BigInt";
- return absl::nullopt;
- }
- if (local_bigint->WordCount() > 2) {
- *error_out = "BigInt is too large";
- return absl::nullopt;
- }
- int sign_bit;
- int word_count = 2;
- uint64_t words[2]; // Least significant to most significant.
- local_bigint->ToWordsArray(&sign_bit, &word_count, words);
- if (sign_bit) {
- *error_out = "BigInt must be non-negative";
- return absl::nullopt;
- }
- if (word_count == 0) {
- words[0] = 0;
- }
- if (word_count <= 1) {
- words[1] = 0;
- }
-
- return absl::MakeUint128(words[1], words[0]);
-}
-
-// In case of failure, will return `absl::nullopt` and output an error to
-// `error_out`.
-absl::optional<uint64_t> ParseDebugKey(gin::Dictionary dict,
- v8::Local<v8::Context>& context,
- std::string* error_out) {
- v8::Local<v8::Value> js_debug_key;
-
- if (!dict.Get("debug_key", &js_debug_key) || js_debug_key.IsEmpty() ||
- js_debug_key->IsNullOrUndefined()) {
- return absl::nullopt;
- }
-
- if (js_debug_key->IsUint32()) {
- v8::Maybe<uint32_t> maybe_debug_key = js_debug_key->Uint32Value(context);
- if (maybe_debug_key.IsNothing()) {
- *error_out = "Failed to interpret value as integer";
- }
- return maybe_debug_key.ToChecked();
- }
-
- if (js_debug_key->IsBigInt()) {
- absl::optional<absl::uint128> maybe_debug_key =
- ConvertBigIntToUint128(js_debug_key->ToBigInt(context), error_out);
- if (!maybe_debug_key.has_value()) {
- return absl::nullopt;
- }
- if (absl::Uint128High64(maybe_debug_key.value()) != 0) {
- *error_out = "BigInt is too large";
- return absl::nullopt;
- }
- return absl::Uint128Low64(maybe_debug_key.value());
- }
-
- *error_out =
- "debug_key must be either a non-negative integer Number or BigInt";
- return absl::nullopt;
-}
-
-} // namespace
-
PrivateAggregation::PrivateAggregation(
mojom::SharedStorageWorkletServiceClient& client,
content::mojom::PrivateAggregationHost& private_aggregation_host)
@@ -153,101 +40,16 @@
void PrivateAggregation::SendHistogramReport(gin::Arguments* args) {
EnsureUseCountersAreRecorded();
- v8::Isolate* isolate = args->isolate();
- v8::Local<v8::Context> context = isolate->GetCurrentContext();
-
- std::vector<v8::Local<v8::Value>> argument_list = args->GetAll();
-
- // Any additional arguments are ignored.
- if (argument_list.size() == 0 || argument_list[0].IsEmpty() ||
- !argument_list[0]->IsObject()) {
- isolate->ThrowException(v8::Exception::TypeError(CreateStringFromLiteral(
- isolate, "sendHistogramReport requires 1 object parameter")));
- return;
- }
-
- gin::Dictionary dict(isolate);
-
- if (!gin::ConvertFromV8(isolate, argument_list[0], &dict)) {
- isolate->ThrowException(v8::Exception::TypeError(CreateStringFromLiteral(
- isolate, "Invalid argument in sendHistogramReport")));
- return;
- }
-
- v8::Local<v8::Value> js_bucket;
- v8::Local<v8::Value> js_value;
-
- if (!dict.Get("bucket", &js_bucket) || js_bucket.IsEmpty() ||
- js_bucket->IsNullOrUndefined()) {
- isolate->ThrowException(v8::Exception::TypeError(CreateStringFromLiteral(
- isolate, "Invalid or missing bucket in sendHistogramReport argument")));
- return;
- }
-
- if (!dict.Get("value", &js_value) || js_value.IsEmpty() ||
- js_value->IsNullOrUndefined()) {
- isolate->ThrowException(v8::Exception::TypeError(CreateStringFromLiteral(
- isolate, "Invalid or missing value in sendHistogramReport argument")));
- return;
- }
-
- absl::uint128 bucket;
- int value;
-
- if (js_bucket->IsUint32()) {
- v8::Maybe<uint32_t> maybe_bucket = js_bucket->Uint32Value(context);
- if (maybe_bucket.IsNothing()) {
- isolate->ThrowException(v8::Exception::TypeError(CreateStringFromLiteral(
- isolate, "Failed to interpret value as integer")));
- return;
- }
- bucket = maybe_bucket.ToChecked();
- } else if (js_bucket->IsBigInt()) {
- std::string error;
- absl::optional<absl::uint128> maybe_bucket =
- ConvertBigIntToUint128(js_bucket->ToBigInt(context), &error);
- if (!maybe_bucket.has_value()) {
- DCHECK(base::IsStringUTF8(error));
- isolate->ThrowException(v8::Exception::TypeError(
- CreateUtf8String(isolate, error).ToLocalChecked()));
- return;
- }
- bucket = maybe_bucket.value();
- } else {
- isolate->ThrowException(v8::Exception::TypeError(CreateStringFromLiteral(
- isolate, "Bucket must be either an integer Number or BigInt")));
- return;
- }
-
- if (js_value->IsInt32()) {
- v8::Maybe<int32_t> maybe_value = js_value->Int32Value(context);
- if (maybe_value.IsNothing()) {
- isolate->ThrowException(v8::Exception::TypeError(CreateStringFromLiteral(
- isolate, "Failed to interpret value as integer")));
- return;
- }
- value = maybe_value.ToChecked();
- } else if (js_value->IsBigInt()) {
- isolate->ThrowException(v8::Exception::TypeError(
- CreateStringFromLiteral(isolate, "Value cannot be a BigInt")));
- return;
- } else {
- isolate->ThrowException(v8::Exception::TypeError(
- CreateStringFromLiteral(isolate, "Value must be an integer Number")));
- return;
- }
-
- if (value < 0) {
- isolate->ThrowException(v8::Exception::TypeError(
- CreateStringFromLiteral(isolate, "Value must be non-negative")));
+ content::mojom::AggregatableReportHistogramContributionPtr contribution =
+ worklet_utils::ParseSendHistogramReportArguments(*args);
+ if (contribution.is_null()) {
+ // Indicates an exception was thrown.
return;
}
std::vector<content::mojom::AggregatableReportHistogramContributionPtr>
contributions;
- contributions.push_back(
- content::mojom::AggregatableReportHistogramContribution::New(bucket,
- value));
+ contributions.push_back(std::move(contribution));
private_aggregation_host_->SendHistogramReport(
std::move(contributions),
@@ -259,42 +61,8 @@
void PrivateAggregation::EnableDebugMode(gin::Arguments* args) {
EnsureUseCountersAreRecorded();
- v8::Isolate* isolate = args->isolate();
- v8::Local<v8::Context> context = isolate->GetCurrentContext();
-
- std::vector<v8::Local<v8::Value>> argument_list = args->GetAll();
-
- if (debug_mode_details_.is_enabled) {
- isolate->ThrowException(v8::Exception::TypeError(CreateStringFromLiteral(
- isolate, "enableDebugMode may be called at most once")));
- return;
- }
-
- // If no arguments are provided, no debug key is set.
- if (argument_list.size() >= 1 && !argument_list[0].IsEmpty()) {
- gin::Dictionary dict(isolate);
-
- if (!gin::ConvertFromV8(isolate, argument_list[0], &dict)) {
- isolate->ThrowException(v8::Exception::TypeError(CreateStringFromLiteral(
- isolate, "Invalid argument in enableDebugMode")));
- return;
- }
-
- std::string error;
- absl::optional<uint64_t> maybe_debug_key =
- ParseDebugKey(dict, context, &error);
- if (!maybe_debug_key.has_value()) {
- DCHECK(base::IsStringUTF8(error));
- isolate->ThrowException(v8::Exception::TypeError(
- CreateUtf8String(isolate, error).ToLocalChecked()));
- return;
- }
-
- debug_mode_details_.debug_key =
- content::mojom::DebugKey::New(maybe_debug_key.value());
- }
-
- debug_mode_details_.is_enabled = true;
+ worklet_utils::ParseAndApplyEnableDebugModeArguments(*args,
+ debug_mode_details_);
}
void PrivateAggregation::EnsureUseCountersAreRecorded() {
diff --git a/content/services/worklet_utils/BUILD.gn b/content/services/worklet_utils/BUILD.gn
new file mode 100644
index 0000000..cc320b0b
--- /dev/null
+++ b/content/services/worklet_utils/BUILD.gn
@@ -0,0 +1,22 @@
+# Copyright 2022 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+source_set("worklet_utils") {
+ sources = [
+ "private_aggregation_utils.cc",
+ "private_aggregation_utils.h",
+ ]
+
+ configs += [ "//content:content_implementation" ]
+
+ deps = [
+ "//base",
+ "//content:export",
+ "//gin",
+ "//mojo/public/cpp/bindings",
+ "//v8",
+ ]
+
+ public_deps = [ "//content/common:mojo_bindings" ]
+}
diff --git a/content/services/worklet_utils/DEPS b/content/services/worklet_utils/DEPS
new file mode 100644
index 0000000..a06c04d
--- /dev/null
+++ b/content/services/worklet_utils/DEPS
@@ -0,0 +1,7 @@
+# Before adding any new dependencies, please check that they are already
+# included in *both* auction_worklet *and* shared_storage_worklet's DEPS or else
+# get approval from those OWNERS.
+include_rules = [
+ "+gin",
+ "+v8",
+]
diff --git a/content/services/worklet_utils/OWNERS b/content/services/worklet_utils/OWNERS
new file mode 100644
index 0000000..ee633588
--- /dev/null
+++ b/content/services/worklet_utils/OWNERS
@@ -0,0 +1,2 @@
+# This directory only contains Private Aggregation API utils for now.
+file://content/browser/private_aggregation/OWNERS
diff --git a/content/services/worklet_utils/README.md b/content/services/worklet_utils/README.md
new file mode 100644
index 0000000..c2ba630
--- /dev/null
+++ b/content/services/worklet_utils/README.md
@@ -0,0 +1,6 @@
+This directory holds utilities that are shared between the two worklet
+implementations in //content/services/, i.e.
+[auction worklets](./auction_worklet) and
+[shared storage worklets](./shared_storage_worklet)
+
+This directory is *not* a service itself.
diff --git a/content/services/worklet_utils/private_aggregation_utils.cc b/content/services/worklet_utils/private_aggregation_utils.cc
new file mode 100644
index 0000000..15e3158
--- /dev/null
+++ b/content/services/worklet_utils/private_aggregation_utils.cc
@@ -0,0 +1,263 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/services/worklet_utils/private_aggregation_utils.h"
+
+#include <stdint.h>
+
+#include <string>
+
+#include "base/check.h"
+#include "base/strings/string_util.h"
+#include "content/common/aggregatable_report.mojom.h"
+#include "content/common/private_aggregation_host.mojom.h"
+#include "gin/arguments.h"
+#include "gin/converter.h"
+#include "gin/dictionary.h"
+#include "third_party/abseil-cpp/absl/numeric/int128.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "v8/include/v8-context.h"
+#include "v8/include/v8-exception.h"
+#include "v8/include/v8-isolate.h"
+#include "v8/include/v8-local-handle.h"
+#include "v8/include/v8-primitive.h"
+
+namespace worklet_utils {
+
+namespace {
+
+v8::Local<v8::String> CreateStringFromLiteral(v8::Isolate* isolate,
+ const char* ascii_string) {
+ DCHECK(base::IsStringASCII(ascii_string));
+ return v8::String::NewFromUtf8(isolate, ascii_string,
+ v8::NewStringType::kNormal,
+ strlen(ascii_string))
+ .ToLocalChecked();
+}
+
+v8::MaybeLocal<v8::String> CreateUtf8String(v8::Isolate* isolate,
+ base::StringPiece utf8_string) {
+ if (!base::IsStringUTF8(utf8_string))
+ return v8::MaybeLocal<v8::String>();
+ return v8::String::NewFromUtf8(isolate, utf8_string.data(),
+ v8::NewStringType::kNormal,
+ utf8_string.length());
+}
+
+// If returns `absl::nullopt`, will output an error to `error_out`.
+absl::optional<absl::uint128> ConvertBigIntToUint128(
+ v8::MaybeLocal<v8::BigInt> maybe_bigint,
+ std::string* error_out) {
+ if (maybe_bigint.IsEmpty()) {
+ *error_out = "Failed to interpret as BigInt";
+ return absl::nullopt;
+ }
+
+ v8::Local<v8::BigInt> local_bigint = maybe_bigint.ToLocalChecked();
+ if (local_bigint.IsEmpty()) {
+ *error_out = "Failed to interpret as BigInt";
+ return absl::nullopt;
+ }
+ if (local_bigint->WordCount() > 2) {
+ *error_out = "BigInt is too large";
+ return absl::nullopt;
+ }
+ // Signals the size of the `words` array to `ToWordsArray()`. The number of
+ // elements actually used is then written here by the function.
+ int word_count = 2;
+ int sign_bit = 0;
+ uint64_t words[2]; // Least significant to most significant.
+ local_bigint->ToWordsArray(&sign_bit, &word_count, words);
+ if (sign_bit) {
+ *error_out = "BigInt must be non-negative";
+ return absl::nullopt;
+ }
+ if (word_count == 0) {
+ words[0] = 0;
+ }
+ if (word_count <= 1) {
+ words[1] = 0;
+ }
+
+ return absl::MakeUint128(words[1], words[0]);
+}
+
+// In case of failure, will return `absl::nullopt` and output an error to
+// `error_out`.
+absl::optional<uint64_t> ParseDebugKey(gin::Dictionary dict,
+ v8::Local<v8::Context>& context,
+ std::string* error_out) {
+ v8::Local<v8::Value> js_debug_key;
+
+ if (!dict.Get("debug_key", &js_debug_key) || js_debug_key.IsEmpty() ||
+ js_debug_key->IsNullOrUndefined()) {
+ return absl::nullopt;
+ }
+
+ if (js_debug_key->IsUint32()) {
+ v8::Maybe<uint32_t> maybe_debug_key = js_debug_key->Uint32Value(context);
+ if (maybe_debug_key.IsNothing()) {
+ *error_out = "Failed to interpret value as integer";
+ }
+ return maybe_debug_key.ToChecked();
+ }
+
+ if (js_debug_key->IsBigInt()) {
+ absl::optional<absl::uint128> maybe_debug_key =
+ ConvertBigIntToUint128(js_debug_key->ToBigInt(context), error_out);
+ if (!maybe_debug_key.has_value()) {
+ return absl::nullopt;
+ }
+ if (absl::Uint128High64(maybe_debug_key.value()) != 0) {
+ *error_out = "BigInt is too large";
+ return absl::nullopt;
+ }
+ return absl::Uint128Low64(maybe_debug_key.value());
+ }
+
+ *error_out =
+ "debug_key must be either a non-negative integer Number or BigInt";
+ return absl::nullopt;
+}
+
+} // namespace
+
+content::mojom::AggregatableReportHistogramContributionPtr
+ParseSendHistogramReportArguments(const gin::Arguments& args) {
+ v8::Isolate* isolate = args.isolate();
+ v8::Local<v8::Context> context = isolate->GetCurrentContext();
+
+ std::vector<v8::Local<v8::Value>> argument_list = args.GetAll();
+
+ // Any additional arguments are ignored.
+ if (argument_list.size() == 0 || argument_list[0].IsEmpty() ||
+ !argument_list[0]->IsObject()) {
+ isolate->ThrowException(v8::Exception::TypeError(CreateStringFromLiteral(
+ isolate, "sendHistogramReport requires 1 object parameter")));
+ return nullptr;
+ }
+
+ gin::Dictionary dict(isolate);
+
+ if (!gin::ConvertFromV8(isolate, argument_list[0], &dict)) {
+ isolate->ThrowException(v8::Exception::TypeError(CreateStringFromLiteral(
+ isolate, "Invalid argument in sendHistogramReport")));
+ return nullptr;
+ }
+
+ v8::Local<v8::Value> js_bucket;
+ v8::Local<v8::Value> js_value;
+
+ if (!dict.Get("bucket", &js_bucket) || js_bucket.IsEmpty() ||
+ js_bucket->IsNullOrUndefined()) {
+ isolate->ThrowException(v8::Exception::TypeError(CreateStringFromLiteral(
+ isolate, "Invalid or missing bucket in sendHistogramReport argument")));
+ return nullptr;
+ }
+
+ if (!dict.Get("value", &js_value) || js_value.IsEmpty() ||
+ js_value->IsNullOrUndefined()) {
+ isolate->ThrowException(v8::Exception::TypeError(CreateStringFromLiteral(
+ isolate, "Invalid or missing value in sendHistogramReport argument")));
+ return nullptr;
+ }
+
+ absl::uint128 bucket;
+ int value;
+
+ if (js_bucket->IsUint32()) {
+ v8::Maybe<uint32_t> maybe_bucket = js_bucket->Uint32Value(context);
+ if (maybe_bucket.IsNothing()) {
+ isolate->ThrowException(v8::Exception::TypeError(CreateStringFromLiteral(
+ isolate, "Failed to interpret value as integer")));
+ return nullptr;
+ }
+ bucket = maybe_bucket.ToChecked();
+ } else if (js_bucket->IsBigInt()) {
+ std::string error;
+ absl::optional<absl::uint128> maybe_bucket =
+ ConvertBigIntToUint128(js_bucket->ToBigInt(context), &error);
+ if (!maybe_bucket.has_value()) {
+ DCHECK(base::IsStringUTF8(error));
+ isolate->ThrowException(v8::Exception::TypeError(
+ CreateUtf8String(isolate, error).ToLocalChecked()));
+ return nullptr;
+ }
+ bucket = maybe_bucket.value();
+ } else {
+ isolate->ThrowException(v8::Exception::TypeError(CreateStringFromLiteral(
+ isolate, "Bucket must be either an integer Number or BigInt")));
+ return nullptr;
+ }
+
+ if (js_value->IsInt32()) {
+ v8::Maybe<int32_t> maybe_value = js_value->Int32Value(context);
+ if (maybe_value.IsNothing()) {
+ isolate->ThrowException(v8::Exception::TypeError(CreateStringFromLiteral(
+ isolate, "Failed to interpret value as integer")));
+ return nullptr;
+ }
+ value = maybe_value.ToChecked();
+ } else if (js_value->IsBigInt()) {
+ isolate->ThrowException(v8::Exception::TypeError(
+ CreateStringFromLiteral(isolate, "Value cannot be a BigInt")));
+ return nullptr;
+ } else {
+ isolate->ThrowException(v8::Exception::TypeError(
+ CreateStringFromLiteral(isolate, "Value must be an integer Number")));
+ return nullptr;
+ }
+
+ if (value < 0) {
+ isolate->ThrowException(v8::Exception::TypeError(
+ CreateStringFromLiteral(isolate, "Value must be non-negative")));
+ return nullptr;
+ }
+
+ return content::mojom::AggregatableReportHistogramContribution::New(bucket,
+ value);
+}
+
+void ParseAndApplyEnableDebugModeArguments(
+ const gin::Arguments& args,
+ content::mojom::DebugModeDetails& debug_mode_details) {
+ v8::Isolate* isolate = args.isolate();
+ v8::Local<v8::Context> context = isolate->GetCurrentContext();
+
+ std::vector<v8::Local<v8::Value>> argument_list = args.GetAll();
+
+ if (debug_mode_details.is_enabled) {
+ isolate->ThrowException(v8::Exception::TypeError(CreateStringFromLiteral(
+ isolate, "enableDebugMode may be called at most once")));
+ return;
+ }
+
+ // If no arguments are provided, no debug key is set.
+ if (argument_list.size() >= 1 && !argument_list[0].IsEmpty()) {
+ gin::Dictionary dict(isolate);
+
+ if (!gin::ConvertFromV8(isolate, argument_list[0], &dict)) {
+ isolate->ThrowException(v8::Exception::TypeError(CreateStringFromLiteral(
+ isolate, "Invalid argument in enableDebugMode")));
+ return;
+ }
+
+ std::string error;
+ absl::optional<uint64_t> maybe_debug_key =
+ ParseDebugKey(dict, context, &error);
+ if (!maybe_debug_key.has_value()) {
+ DCHECK(base::IsStringUTF8(error));
+ isolate->ThrowException(v8::Exception::TypeError(
+ CreateUtf8String(isolate, error).ToLocalChecked()));
+ return;
+ }
+
+ debug_mode_details.debug_key =
+ content::mojom::DebugKey::New(maybe_debug_key.value());
+ }
+
+ debug_mode_details.is_enabled = true;
+}
+
+} // namespace worklet_utils
diff --git a/content/services/worklet_utils/private_aggregation_utils.h b/content/services/worklet_utils/private_aggregation_utils.h
new file mode 100644
index 0000000..1b73a45
--- /dev/null
+++ b/content/services/worklet_utils/private_aggregation_utils.h
@@ -0,0 +1,33 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_SERVICES_WORKLET_UTILS_PRIVATE_AGGREGATION_UTILS_H_
+#define CONTENT_SERVICES_WORKLET_UTILS_PRIVATE_AGGREGATION_UTILS_H_
+
+#include "content/common/aggregatable_report.mojom-forward.h"
+#include "content/common/private_aggregation_host.mojom-forward.h"
+
+namespace gin {
+class Arguments;
+}
+
+namespace worklet_utils {
+
+// Parses arguments provided to `sendHistogramReport()` and returns the
+// corresponding contribution. In case of an error, throws an exception and
+// returns `nullptr`.
+content::mojom::AggregatableReportHistogramContributionPtr
+ParseSendHistogramReportArguments(const gin::Arguments& args);
+
+// Parses arguments provided to `enableDebugMode()` and updates
+// `debug_mode_details` as appropriate. `debug_mode_details` must be passed a
+// reference to the existing (likely default) details. In case of an error,
+// throws an exception and does not update `debug_mode_details`.
+void ParseAndApplyEnableDebugModeArguments(
+ const gin::Arguments& args,
+ content::mojom::DebugModeDetails& debug_mode_details);
+
+} // namespace worklet_utils
+
+#endif // CONTENT_SERVICES_WORKLET_UTILS_PRIVATE_AGGREGATION_UTILS_H_