| // Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "third_party/blink/renderer/modules/ad_auction/navigator_auction.h" |
| |
| #include <stdint.h> |
| |
| #include <utility> |
| |
| #include "base/check.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/time/time.h" |
| #include "base/types/expected.h" |
| #include "base/types/pass_key.h" |
| #include "base/unguessable_token.h" |
| #include "mojo/public/cpp/bindings/map_traits_wtf_hash_map.h" |
| #include "third_party/abseil-cpp/absl/numeric/int128.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| #include "third_party/blink/public/common/browser_interface_broker_proxy.h" |
| #include "third_party/blink/public/common/features.h" |
| #include "third_party/blink/public/common/fenced_frame/fenced_frame_utils.h" |
| #include "third_party/blink/public/common/frame/fenced_frame_sandbox_flags.h" |
| #include "third_party/blink/public/common/interest_group/ad_auction_constants.h" |
| #include "third_party/blink/public/common/interest_group/ad_auction_currencies.h" |
| #include "third_party/blink/public/common/interest_group/ad_display_size_utils.h" |
| #include "third_party/blink/public/mojom/interest_group/interest_group_types.mojom-blink.h" |
| #include "third_party/blink/public/mojom/parakeet/ad_request.mojom-blink.h" |
| #include "third_party/blink/public/mojom/permissions_policy/permissions_policy.mojom-blink.h" |
| #include "third_party/blink/public/web/web_console_message.h" |
| #include "third_party/blink/renderer/bindings/core/v8/native_value_traits_impl.h" |
| #include "third_party/blink/renderer/bindings/core/v8/script_function.h" |
| #include "third_party/blink/renderer/bindings/core/v8/script_promise.h" |
| #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_throw_dom_exception.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_union_fencedframeconfig_usvstring.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_union_usvstring_usvstringsequence.h" |
| #include "third_party/blink/renderer/bindings/modules/v8/v8_ad_auction_data_config.h" |
| #include "third_party/blink/renderer/bindings/modules/v8/v8_ad_properties.h" |
| #include "third_party/blink/renderer/bindings/modules/v8/v8_ad_request_config.h" |
| #include "third_party/blink/renderer/bindings/modules/v8/v8_ad_targeting.h" |
| #include "third_party/blink/renderer/bindings/modules/v8/v8_ads.h" |
| #include "third_party/blink/renderer/bindings/modules/v8/v8_auction_ad.h" |
| #include "third_party/blink/renderer/bindings/modules/v8/v8_auction_ad_config.h" |
| #include "third_party/blink/renderer/bindings/modules/v8/v8_auction_ad_interest_group.h" |
| #include "third_party/blink/renderer/bindings/modules/v8/v8_auction_ad_interest_group_key.h" |
| #include "third_party/blink/renderer/bindings/modules/v8/v8_auction_ad_interest_group_size.h" |
| #include "third_party/blink/renderer/bindings/modules/v8/v8_auction_report_buyers_config.h" |
| #include "third_party/blink/renderer/bindings/modules/v8/v8_union_adproperties_adpropertiessequence.h" |
| #include "third_party/blink/renderer/core/dom/abort_signal.h" |
| #include "third_party/blink/renderer/core/dom/document.h" |
| #include "third_party/blink/renderer/core/dom/dom_exception.h" |
| #include "third_party/blink/renderer/core/dom/scoped_abort_state.h" |
| #include "third_party/blink/renderer/core/frame/csp/content_security_policy.h" |
| #include "third_party/blink/renderer/core/frame/csp/csp_directive_list.h" |
| #include "third_party/blink/renderer/core/frame/local_dom_window.h" |
| #include "third_party/blink/renderer/core/frame/local_frame.h" |
| #include "third_party/blink/renderer/core/frame/navigator.h" |
| #include "third_party/blink/renderer/core/frame/web_local_frame_impl.h" |
| #include "third_party/blink/renderer/core/html/fenced_frame/fenced_frame_config.h" |
| #include "third_party/blink/renderer/core/loader/document_loader.h" |
| #include "third_party/blink/renderer/core/page/page.h" |
| #include "third_party/blink/renderer/modules/ad_auction/ads.h" |
| #include "third_party/blink/renderer/modules/ad_auction/join_leave_queue.h" |
| #include "third_party/blink/renderer/modules/ad_auction/validate_blink_interest_group.h" |
| #include "third_party/blink/renderer/modules/geolocation/geolocation_coordinates.h" |
| #include "third_party/blink/renderer/platform/bindings/exception_state.h" |
| #include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h" |
| #include "third_party/blink/renderer/platform/weborigin/security_origin.h" |
| #include "third_party/blink/renderer/platform/wtf/text/string_operators.h" |
| #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" |
| #include "third_party/blink/renderer/platform/wtf/vector.h" |
| #include "v8/include/v8-primitive.h" |
| #include "v8/include/v8-value.h" |
| |
| namespace blink { |
| |
| // Helper to manage runtime of abort + promise resolution pipe. |
| // Can interface to AbortController itself, and has helper classes that can be |
| // connected to promises via Then and ScriptFunction. |
| class NavigatorAuction::AuctionHandle final : public AbortSignal::Algorithm { |
| public: |
| class JsonResolved : public ScriptFunction::Callable { |
| public: |
| // `field_name` is expected to point to a literal. |
| JsonResolved(AuctionHandle* auction_handle, |
| mojom::blink::AuctionAdConfigAuctionIdPtr auction_id, |
| mojom::blink::AuctionAdConfigField field, |
| const String& seller_name, |
| const char* field_name); |
| |
| ScriptValue Call(ScriptState* script_state, ScriptValue value) override; |
| void Trace(Visitor* visitor) const override; |
| |
| private: |
| Member<AuctionHandle> auction_handle_; |
| const mojom::blink::AuctionAdConfigAuctionIdPtr auction_id_; |
| const mojom::blink::AuctionAdConfigField field_; |
| const String seller_name_; |
| const char* const field_name_; |
| }; |
| |
| class PerBuyerSignalsResolved : public ScriptFunction::Callable { |
| public: |
| PerBuyerSignalsResolved( |
| AuctionHandle* auction_handle, |
| mojom::blink::AuctionAdConfigAuctionIdPtr auction_id, |
| const String& seller_name); |
| |
| ScriptValue Call(ScriptState* script_state, ScriptValue value) override; |
| void Trace(Visitor* visitor) const override; |
| |
| private: |
| Member<AuctionHandle> auction_handle_; |
| const mojom::blink::AuctionAdConfigAuctionIdPtr auction_id_; |
| const String seller_name_; |
| }; |
| |
| // This is used for perBuyerTimeouts and perBuyerCumulativeTimeouts, with |
| // `field` indicating which of the two fields an object is being used for. |
| class BuyerTimeoutsResolved : public ScriptFunction::Callable { |
| public: |
| BuyerTimeoutsResolved(AuctionHandle* auction_handle, |
| mojom::blink::AuctionAdConfigAuctionIdPtr auction_id, |
| mojom::blink::AuctionAdConfigBuyerTimeoutField field, |
| const String& seller_name); |
| |
| ScriptValue Call(ScriptState* script_state, ScriptValue value) override; |
| void Trace(Visitor* visitor) const override; |
| |
| private: |
| Member<AuctionHandle> auction_handle_; |
| const mojom::blink::AuctionAdConfigAuctionIdPtr auction_id_; |
| const mojom::blink::AuctionAdConfigBuyerTimeoutField field_; |
| const String seller_name_; |
| }; |
| |
| class BuyerCurrenciesResolved : public ScriptFunction::Callable { |
| public: |
| BuyerCurrenciesResolved( |
| AuctionHandle* auction_handle, |
| mojom::blink::AuctionAdConfigAuctionIdPtr auction_id, |
| const String& seller_name); |
| |
| ScriptValue Call(ScriptState* script_state, ScriptValue value) override; |
| void Trace(Visitor* visitor) const override; |
| |
| private: |
| Member<AuctionHandle> auction_handle_; |
| const mojom::blink::AuctionAdConfigAuctionIdPtr auction_id_; |
| const String seller_name_; |
| }; |
| |
| class DirectFromSellerSignalsResolved : public ScriptFunction::Callable { |
| public: |
| DirectFromSellerSignalsResolved( |
| AuctionHandle* auction_handle, |
| mojom::blink::AuctionAdConfigAuctionIdPtr auction_id, |
| const String& seller_name, |
| const scoped_refptr<const SecurityOrigin>& seller_origin, |
| const absl::optional<Vector<scoped_refptr<const SecurityOrigin>>>& |
| interest_group_buyers); |
| |
| ScriptValue Call(ScriptState* script_state, ScriptValue value) override; |
| void Trace(Visitor* visitor) const override; |
| |
| private: |
| Member<AuctionHandle> auction_handle_; |
| const mojom::blink::AuctionAdConfigAuctionIdPtr auction_id_; |
| const String seller_name_; |
| const scoped_refptr<const SecurityOrigin> seller_origin_; |
| absl::optional<Vector<scoped_refptr<const SecurityOrigin>>> |
| interest_group_buyers_; |
| }; |
| |
| class ResolveToConfigResolved : public ScriptFunction::Callable { |
| public: |
| ResolveToConfigResolved(AuctionHandle* auction_handle); |
| |
| ScriptValue Call(ScriptState* script_state, ScriptValue value) override; |
| void Trace(Visitor* visitor) const override; |
| |
| private: |
| Member<AuctionHandle> auction_handle_; |
| }; |
| |
| class Rejected : public ScriptFunction::Callable { |
| public: |
| explicit Rejected(AuctionHandle* auction_handle); |
| |
| ScriptValue Call(ScriptState*, ScriptValue) override; |
| void Trace(Visitor* visitor) const override; |
| |
| private: |
| Member<AuctionHandle> auction_handle_; |
| }; |
| |
| AuctionHandle(ExecutionContext* context, |
| mojo::PendingRemote<mojom::blink::AbortableAdAuction> remote) |
| : abortable_ad_auction_(context) { |
| abortable_ad_auction_.Bind( |
| std::move(remote), context->GetTaskRunner(TaskType::kMiscPlatformAPI)); |
| } |
| |
| ~AuctionHandle() override = default; |
| |
| void AttachPromiseHandler(ScriptState& script_state, |
| ScriptPromise& promise, |
| ScriptFunction::Callable* success_helper) { |
| promise.Then( |
| MakeGarbageCollected<ScriptFunction>(&script_state, success_helper), |
| MakeGarbageCollected<ScriptFunction>( |
| &script_state, |
| MakeGarbageCollected<NavigatorAuction::AuctionHandle::Rejected>( |
| this))); |
| } |
| |
| void Abort() { abortable_ad_auction_->Abort(); } |
| |
| void ResolvedPromiseParam(mojom::blink::AuctionAdConfigAuctionIdPtr auction, |
| mojom::blink::AuctionAdConfigField field, |
| const String& json_value) { |
| abortable_ad_auction_->ResolvedPromiseParam(std::move(auction), field, |
| json_value); |
| } |
| |
| void ResolvedPerBuyerSignalsPromise( |
| mojom::blink::AuctionAdConfigAuctionIdPtr auction, |
| const absl::optional<WTF::HashMap<scoped_refptr<const SecurityOrigin>, |
| String>>& per_buyer_signals) { |
| abortable_ad_auction_->ResolvedPerBuyerSignalsPromise(std::move(auction), |
| per_buyer_signals); |
| } |
| |
| void ResolvedBuyerTimeoutsPromise( |
| mojom::blink::AuctionAdConfigAuctionIdPtr auction, |
| mojom::blink::AuctionAdConfigBuyerTimeoutField field, |
| mojom::blink::AuctionAdConfigBuyerTimeoutsPtr buyer_timeouts) { |
| abortable_ad_auction_->ResolvedBuyerTimeoutsPromise( |
| std::move(auction), field, std::move(buyer_timeouts)); |
| } |
| |
| void ResolvedBuyerCurrencies( |
| mojom::blink::AuctionAdConfigAuctionIdPtr auction, |
| mojom::blink::AuctionAdConfigBuyerCurrenciesPtr buyer_currencies) { |
| abortable_ad_auction_->ResolvedBuyerCurrenciesPromise( |
| std::move(auction), std::move(buyer_currencies)); |
| } |
| |
| void ResolvedDirectFromSellerSignalsPromise( |
| mojom::blink::AuctionAdConfigAuctionIdPtr auction, |
| mojom::blink::DirectFromSellerSignalsPtr direct_from_seller_signals) { |
| abortable_ad_auction_->ResolvedDirectFromSellerSignalsPromise( |
| std::move(auction), std::move(direct_from_seller_signals)); |
| } |
| |
| // AbortSignal::Algorithm implementation: |
| void Run() override { Abort(); } |
| |
| void Trace(Visitor* visitor) const override { |
| visitor->Trace(abortable_ad_auction_); |
| visitor->Trace(auction_resolver_); |
| AbortSignal::Algorithm::Trace(visitor); |
| } |
| |
| void AuctionComplete( |
| ScriptPromiseResolver*, |
| std::unique_ptr<ScopedAbortState>, |
| bool manually_aborted, |
| const absl::optional<FencedFrame::RedactedFencedFrameConfig>&); |
| |
| void MaybeResolveAuction(); |
| |
| void SetResolveToConfig(bool value) { resolve_to_config_ = value; } |
| |
| private: |
| HeapMojoRemote<mojom::blink::AbortableAdAuction> abortable_ad_auction_; |
| |
| absl::optional<bool> resolve_to_config_; |
| Member<ScriptPromiseResolver> auction_resolver_; |
| absl::optional<FencedFrame::RedactedFencedFrameConfig> auction_config_; |
| }; |
| |
| namespace { |
| |
| // The maximum number of active cross-site joins and leaves. Once these are hit, |
| // cross-site joins/leaves are queued until they drop below this number. Queued |
| // pending operations are dropped on destruction / navigation away. |
| const int kMaxActiveCrossSiteJoins = 20; |
| const int kMaxActiveCrossSiteLeaves = 20; |
| |
| // Error string builders. |
| |
| String ErrorInvalidInterestGroup(const AuctionAdInterestGroup& group, |
| const String& field_name, |
| const String& field_value, |
| const String& error) { |
| return String::Format( |
| "%s '%s' for AuctionAdInterestGroup with owner '%s' and name '%s' %s", |
| field_name.Utf8().c_str(), field_value.Utf8().c_str(), |
| group.owner().Utf8().c_str(), group.name().Utf8().c_str(), |
| error.Utf8().c_str()); |
| } |
| |
| String ErrorInvalidInterestGroupJson(const AuctionAdInterestGroup& group, |
| const String& field_name) { |
| return String::Format( |
| "%s for AuctionAdInterestGroup with owner '%s' and name '%s' must be a " |
| "JSON-serializable object.", |
| field_name.Utf8().c_str(), group.owner().Utf8().c_str(), |
| group.name().Utf8().c_str()); |
| } |
| |
| String ErrorInvalidAuctionConfigSeller(const String& seller_name, |
| const String& field_name, |
| const String& field_value, |
| const String& error) { |
| return String::Format("%s '%s' for AuctionAdConfig with seller '%s' %s", |
| field_name.Utf8().c_str(), field_value.Utf8().c_str(), |
| seller_name.Utf8().c_str(), error.Utf8().c_str()); |
| } |
| |
| String ErrorInvalidAuctionConfig(const AuctionAdConfig& config, |
| const String& field_name, |
| const String& field_value, |
| const String& error) { |
| return ErrorInvalidAuctionConfigSeller(config.seller(), field_name, |
| field_value, error); |
| } |
| |
| String ErrorInvalidAuctionConfigSellerJson(const String& seller_name, |
| const String& field_name) { |
| return String::Format( |
| "%s for AuctionAdConfig with seller '%s' must be a JSON-serializable " |
| "object.", |
| field_name.Utf8().c_str(), seller_name.Utf8().c_str()); |
| } |
| |
| String ErrorInvalidAuctionConfigJson(const AuctionAdConfig& config, |
| const String& field_name) { |
| return ErrorInvalidAuctionConfigSellerJson(config.seller(), field_name); |
| } |
| |
| String ErrorInvalidAdRequestConfig(const AdRequestConfig& config, |
| const String& field_name, |
| const String& field_value, |
| const String& error) { |
| return String::Format("%s '%s' for AdRequestConfig with URL '%s' %s", |
| field_name.Utf8().c_str(), field_value.Utf8().c_str(), |
| config.adRequestUrl().Utf8().c_str(), |
| error.Utf8().c_str()); |
| } |
| |
| String ErrorInvalidAuctionConfigUint128(const AuctionAdConfig& config, |
| const String& field_name, |
| const String& error) { |
| return String::Format("%s for AuctionAdConfig with seller '%s': %s", |
| field_name.Utf8().c_str(), |
| config.seller().Utf8().c_str(), error.Utf8().c_str()); |
| } |
| |
| String ErrorRenameMismatch(const String& old_field_name, |
| const String& old_field_value, |
| const String& new_field_name, |
| const String& new_field_value) { |
| return String::Format( |
| "%s doesn't have the same value as %s ('%s' vs '%s')", |
| old_field_name.Utf8().c_str(), new_field_name.Utf8().c_str(), |
| old_field_value.Utf8().c_str(), new_field_value.Utf8().c_str()); |
| } |
| |
| String ErrorMissingRequired(const String& required_field_name) { |
| return String::Format("Missing required field %s", |
| required_field_name.Utf8().c_str()); |
| } |
| |
| String WarningPermissionsPolicy(const String& feature, const String& api) { |
| return String::Format( |
| "In the future, Permissions Policy feature %s will not be enabled by " |
| "default in cross-origin iframes or same-origin iframes nested in " |
| "cross-origin iframes. Calling %s will be rejected with NotAllowedError " |
| "if it is not explicitly enabled", |
| feature.Utf8().c_str(), api.Utf8().c_str()); |
| } |
| |
| // Console warnings. |
| |
| void AddWarningMessageToConsole(ScriptState* script_state, |
| const String& feature, |
| const String& api) { |
| auto* window = To<LocalDOMWindow>(ExecutionContext::From(script_state)); |
| WebLocalFrameImpl::FromFrame(window->GetFrame()) |
| ->AddMessageToConsole( |
| WebConsoleMessage(mojom::blink::ConsoleMessageLevel::kWarning, |
| WarningPermissionsPolicy(feature, api)), |
| /*discard_duplicates=*/true); |
| } |
| |
| void ConsoleWarnDeprecatedEnum(const ExecutionContext& execution_context, |
| String enum_name, |
| String deprecated_value) { |
| auto* window = To<LocalDOMWindow>(&execution_context); |
| WebLocalFrameImpl::FromFrame(window->GetFrame()) |
| ->AddMessageToConsole( |
| WebConsoleMessage( |
| mojom::blink::ConsoleMessageLevel::kWarning, |
| String::Format( |
| "Enum %s used deprecated value %s -- \"dashed-naming\" " |
| "should be used instead of \"camelCase\".", |
| enum_name.Utf8().c_str(), deprecated_value.Utf8().c_str())), |
| /*discard_duplicates=*/true); |
| } |
| |
| // JSON and Origin conversion helpers. |
| |
| bool Jsonify(const ScriptState& script_state, |
| const v8::Local<v8::Value>& value, |
| String& output) { |
| v8::Local<v8::String> v8_string; |
| // v8::JSON throws on certain inputs that can't be converted to JSON (like |
| // recursive structures). Use TryCatch to consume them. Otherwise, they'd take |
| // precedence over the returned ExtensionState for methods that return |
| // ScriptPromises, since ExceptionState is used to generate a rejected |
| // promise, which V8 exceptions take precedence over. |
| v8::TryCatch try_catch(script_state.GetIsolate()); |
| if (!v8::JSON::Stringify(script_state.GetContext(), value) |
| .ToLocal(&v8_string) || |
| try_catch.HasCaught()) { |
| return false; |
| } |
| |
| output = ToCoreString(v8_string); |
| // JSON.stringify can fail to produce a string value in one of two ways: it |
| // can throw an exception (as with unserializable objects), or it can return |
| // `undefined` (as with e.g. passing a function). If JSON.stringify returns |
| // `undefined`, the v8 API then coerces it to the string value "undefined". |
| // Check for this, and consider it a failure (since we didn't properly |
| // serialize a value, and v8::JSON::Parse() rejects "undefined"). |
| return output != "undefined"; |
| } |
| |
| base::expected<absl::uint128, String> CopyBigIntToUint128( |
| const BigInt& bigint) { |
| if (!bigint.FitsIn128Bits()) { |
| return base::unexpected("Too large BigInt; Must fit in 128 bits"); |
| } |
| if (bigint.IsNegative()) { |
| return base::unexpected("Negative BigInt cannot be converted to uint128"); |
| } |
| return *bigint.ToUInt128(); |
| } |
| |
| // Returns nullptr if |origin_string| couldn't be parsed into an acceptable |
| // origin. |
| scoped_refptr<const SecurityOrigin> ParseOrigin(const String& origin_string) { |
| scoped_refptr<const SecurityOrigin> origin = |
| SecurityOrigin::CreateFromString(origin_string); |
| if (origin->Protocol() != url::kHttpsScheme) |
| return nullptr; |
| return origin; |
| } |
| |
| // WebIDL -> Mojom copy functions -- each return true if successful (including |
| // the not present, nothing to copy case), returns false and throws JS exception |
| // for invalid input. |
| |
| // joinAdInterestGroup() copy functions. |
| |
| bool CopyOwnerFromIdlToMojo(const ExecutionContext& execution_context, |
| ExceptionState& exception_state, |
| const AuctionAdInterestGroup& input, |
| mojom::blink::InterestGroup& output) { |
| scoped_refptr<const SecurityOrigin> owner = ParseOrigin(input.owner()); |
| if (!owner) { |
| exception_state.ThrowTypeError(String::Format( |
| "owner '%s' for AuctionAdInterestGroup with name '%s' must be a valid " |
| "https origin.", |
| input.owner().Utf8().c_str(), input.name().Utf8().c_str())); |
| return false; |
| } |
| |
| output.owner = std::move(owner); |
| return true; |
| } |
| |
| // Converts a sparse vector used in `priority_vector` and |
| // `priority_signals_overrides` to a WTF::HashMap, as is used in mojom structs. |
| // Has no failure cases. |
| WTF::HashMap<WTF::String, double> ConvertSparseVectorIdlToMojo( |
| const Vector<std::pair<WTF::String, double>>& priority_signals_in) { |
| WTF::HashMap<WTF::String, double> priority_signals_out; |
| for (const auto& key_value_pair : priority_signals_in) { |
| priority_signals_out.insert(key_value_pair.first, key_value_pair.second); |
| } |
| return priority_signals_out; |
| } |
| |
| mojom::blink::SellerCapabilitiesPtr ConvertSellerCapabilitiesTypeFromIdlToMojo( |
| const ExecutionContext& execution_context, |
| const Vector<String>& capabilities_vector) { |
| auto seller_capabilities = mojom::blink::SellerCapabilities::New(); |
| for (const String& capability_str : capabilities_vector) { |
| const bool used_deprecated_names = |
| capability_str == "interestGroupCounts" || |
| capability_str == "latencyStats"; |
| base::UmaHistogramBoolean( |
| "Ads.InterestGroup.EnumNaming.Renderer.SellerCapabilities", |
| used_deprecated_names); |
| if (used_deprecated_names) { |
| ConsoleWarnDeprecatedEnum(execution_context, "SellerCapabilities", |
| capability_str); |
| } |
| if (capability_str == "interest-group-counts" || |
| capability_str == "interestGroupCounts") { |
| seller_capabilities->allows_interest_group_counts = true; |
| } else if (capability_str == "latency-stats" || |
| capability_str == "latencyStats") { |
| seller_capabilities->allows_latency_stats = true; |
| } else { |
| // For forward compatibility with new values, don't throw. |
| continue; |
| } |
| } |
| return seller_capabilities; |
| } |
| |
| bool CopySellerCapabilitiesFromIdlToMojo( |
| const ExecutionContext& execution_context, |
| ExceptionState& exception_state, |
| const AuctionAdInterestGroup& input, |
| mojom::blink::InterestGroup& output) { |
| output.all_sellers_capabilities = mojom::blink::SellerCapabilities::New(); |
| if (!input.hasSellerCapabilities()) |
| return true; |
| |
| for (const auto& [origin_string, capabilities_vector] : |
| input.sellerCapabilities()) { |
| mojom::blink::SellerCapabilitiesPtr seller_capabilities = |
| ConvertSellerCapabilitiesTypeFromIdlToMojo(execution_context, |
| capabilities_vector); |
| if (origin_string == "*") { |
| output.all_sellers_capabilities = std::move(seller_capabilities); |
| } else { |
| if (!output.seller_capabilities) |
| output.seller_capabilities.emplace(); |
| output.seller_capabilities->insert( |
| SecurityOrigin::CreateFromString(origin_string), |
| std::move(seller_capabilities)); |
| } |
| } |
| |
| return true; |
| } |
| |
| bool CopyExecutionModeFromIdlToMojo(const ExecutionContext& execution_context, |
| ExceptionState& exception_state, |
| const AuctionAdInterestGroup& input, |
| mojom::blink::InterestGroup& output) { |
| if (!input.hasExecutionMode()) |
| return true; |
| const bool used_deprecated_names = input.executionMode() == "groupByOrigin"; |
| base::UmaHistogramBoolean( |
| "Ads.InterestGroup.EnumNaming.Renderer.WorkletExecutionMode", |
| used_deprecated_names); |
| if (used_deprecated_names) { |
| ConsoleWarnDeprecatedEnum(execution_context, "executionMode", |
| input.executionMode()); |
| } |
| |
| // TODO(crbug.com/1330341): Support "frozen-context". |
| if (input.executionMode() == "compatibility") { |
| output.execution_mode = |
| mojom::blink::InterestGroup::ExecutionMode::kCompatibilityMode; |
| } else if (input.executionMode() == "group-by-origin" || |
| input.executionMode() == "groupByOrigin") { |
| output.execution_mode = |
| mojom::blink::InterestGroup::ExecutionMode::kGroupedByOriginMode; |
| } else if (input.executionMode() == "frozen-context") { |
| output.execution_mode = |
| mojom::blink::InterestGroup::ExecutionMode::kFrozenContext; |
| } |
| // For forward compatibility with new values, don't throw if unrecognized enum |
| // values encountered. |
| return true; |
| } |
| |
| bool CopyBiddingLogicUrlFromIdlToMojo(const ExecutionContext& context, |
| ExceptionState& exception_state, |
| const AuctionAdInterestGroup& input, |
| mojom::blink::InterestGroup& output) { |
| if (!input.hasBiddingLogicURL()) { |
| return true; |
| } |
| KURL bidding_url = context.CompleteURL(input.biddingLogicURL()); |
| if (!bidding_url.IsValid()) { |
| exception_state.ThrowTypeError(ErrorInvalidInterestGroup( |
| input, "biddingLogicURL", input.biddingLogicURL(), |
| "cannot be resolved to a valid URL.")); |
| return false; |
| } |
| output.bidding_url = bidding_url; |
| return true; |
| } |
| |
| bool CopyWasmHelperUrlFromIdlToMojo(const ExecutionContext& context, |
| ExceptionState& exception_state, |
| const AuctionAdInterestGroup& input, |
| mojom::blink::InterestGroup& output) { |
| if (!input.hasBiddingWasmHelperURL()) { |
| return true; |
| } |
| KURL wasm_url = context.CompleteURL(input.biddingWasmHelperURL()); |
| if (!wasm_url.IsValid()) { |
| exception_state.ThrowTypeError(ErrorInvalidInterestGroup( |
| input, "biddingWasmHelperURL", input.biddingWasmHelperURL(), |
| "cannot be resolved to a valid URL.")); |
| return false; |
| } |
| // ValidateBlinkInterestGroup will checks whether this follows all the rules. |
| output.bidding_wasm_helper_url = wasm_url; |
| return true; |
| } |
| |
| bool CopyUpdateUrlFromIdlToMojo(const ExecutionContext& context, |
| ExceptionState& exception_state, |
| const AuctionAdInterestGroup& input, |
| mojom::blink::InterestGroup& output) { |
| if (input.hasUpdateURL()) { |
| if (input.hasDailyUpdateUrl() && |
| input.updateURL() != input.dailyUpdateUrl()) { |
| exception_state.ThrowTypeError(ErrorInvalidInterestGroup( |
| input, "updateURL", input.updateURL(), |
| "must match dailyUpdateUrl, when both are present.")); |
| return false; |
| } |
| KURL update_url = context.CompleteURL(input.updateURL()); |
| if (!update_url.IsValid()) { |
| exception_state.ThrowTypeError( |
| ErrorInvalidInterestGroup(input, "updateURL", input.updateURL(), |
| "cannot be resolved to a valid URL.")); |
| return false; |
| } |
| output.update_url = update_url; |
| return true; |
| } |
| if (input.hasDailyUpdateUrl()) { |
| KURL daily_update_url = context.CompleteURL(input.dailyUpdateUrl()); |
| if (!daily_update_url.IsValid()) { |
| exception_state.ThrowTypeError(ErrorInvalidInterestGroup( |
| input, "dailyUpdateUrl", input.dailyUpdateUrl(), |
| "cannot be resolved to a valid URL.")); |
| return false; |
| } |
| output.update_url = daily_update_url; |
| } |
| return true; |
| } |
| |
| bool CopyTrustedBiddingSignalsUrlFromIdlToMojo( |
| const ExecutionContext& context, |
| ExceptionState& exception_state, |
| const AuctionAdInterestGroup& input, |
| mojom::blink::InterestGroup& output) { |
| if (!input.hasTrustedBiddingSignalsURL()) { |
| return true; |
| } |
| KURL trusted_bidding_signals_url = |
| context.CompleteURL(input.trustedBiddingSignalsURL()); |
| if (!trusted_bidding_signals_url.IsValid()) { |
| exception_state.ThrowTypeError(ErrorInvalidInterestGroup( |
| input, "trustedBiddingSignalsURL", input.trustedBiddingSignalsURL(), |
| "cannot be resolved to a valid URL.")); |
| return false; |
| } |
| output.trusted_bidding_signals_url = trusted_bidding_signals_url; |
| return true; |
| } |
| |
| bool CopyTrustedBiddingSignalsKeysFromIdlToMojo( |
| const AuctionAdInterestGroup& input, |
| mojom::blink::InterestGroup& output) { |
| if (!input.hasTrustedBiddingSignalsKeys()) |
| return true; |
| output.trusted_bidding_signals_keys.emplace(); |
| for (const auto& key : input.trustedBiddingSignalsKeys()) { |
| output.trusted_bidding_signals_keys->push_back(key); |
| } |
| return true; |
| } |
| |
| bool CopyUserBiddingSignalsFromIdlToMojo(const ScriptState& script_state, |
| ExceptionState& exception_state, |
| const AuctionAdInterestGroup& input, |
| mojom::blink::InterestGroup& output) { |
| if (!input.hasUserBiddingSignals()) |
| return true; |
| if (!Jsonify(script_state, input.userBiddingSignals().V8Value(), |
| output.user_bidding_signals)) { |
| exception_state.ThrowTypeError( |
| ErrorInvalidInterestGroupJson(input, "userBiddingSignals")); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool CopyAdsFromIdlToMojo(const ExecutionContext& context, |
| const ScriptState& script_state, |
| ExceptionState& exception_state, |
| const AuctionAdInterestGroup& input, |
| mojom::blink::InterestGroup& output) { |
| if (!input.hasAds()) |
| return true; |
| output.ads.emplace(); |
| for (const auto& ad : input.ads()) { |
| auto mojo_ad = mojom::blink::InterestGroupAd::New(); |
| KURL render_url = context.CompleteURL(ad->renderURL()); |
| if (!render_url.IsValid()) { |
| exception_state.ThrowTypeError( |
| ErrorInvalidInterestGroup(input, "ad renderURL", ad->renderURL(), |
| "cannot be resolved to a valid URL.")); |
| return false; |
| } |
| mojo_ad->render_url = render_url; |
| if (ad->hasSizeGroup()) { |
| mojo_ad->size_group = ad->sizeGroup(); |
| } |
| if (ad->hasBuyerReportingId()) { |
| mojo_ad->buyer_reporting_id = ad->buyerReportingId(); |
| } |
| if (ad->hasBuyerAndSellerReportingId()) { |
| mojo_ad->buyer_and_seller_reporting_id = ad->buyerAndSellerReportingId(); |
| } |
| if (ad->hasMetadata()) { |
| if (!Jsonify(script_state, ad->metadata().V8Value(), mojo_ad->metadata)) { |
| exception_state.ThrowTypeError( |
| ErrorInvalidInterestGroupJson(input, "ad metadata")); |
| return false; |
| } |
| } |
| if (ad->hasAdRenderId()) { |
| mojo_ad->ad_render_id = ad->adRenderId(); |
| } |
| output.ads->push_back(std::move(mojo_ad)); |
| } |
| return true; |
| } |
| |
| bool CopyAdComponentsFromIdlToMojo(const ExecutionContext& context, |
| const ScriptState& script_state, |
| ExceptionState& exception_state, |
| const AuctionAdInterestGroup& input, |
| mojom::blink::InterestGroup& output) { |
| if (!input.hasAdComponents()) |
| return true; |
| output.ad_components.emplace(); |
| for (const auto& ad : input.adComponents()) { |
| auto mojo_ad = mojom::blink::InterestGroupAd::New(); |
| KURL render_url = context.CompleteURL(ad->renderURL()); |
| if (!render_url.IsValid()) { |
| exception_state.ThrowTypeError( |
| ErrorInvalidInterestGroup(input, "ad renderURL", ad->renderURL(), |
| "cannot be resolved to a valid URL.")); |
| return false; |
| } |
| mojo_ad->render_url = render_url; |
| if (ad->hasSizeGroup()) { |
| mojo_ad->size_group = ad->sizeGroup(); |
| } |
| if (ad->hasMetadata()) { |
| if (!Jsonify(script_state, ad->metadata().V8Value(), mojo_ad->metadata)) { |
| exception_state.ThrowTypeError( |
| ErrorInvalidInterestGroupJson(input, "ad metadata")); |
| return false; |
| } |
| } |
| if (ad->hasAdRenderId()) { |
| mojo_ad->ad_render_id = ad->adRenderId(); |
| } |
| output.ad_components->push_back(std::move(mojo_ad)); |
| } |
| return true; |
| } |
| |
| bool CopyAdSizesFromIdlToMojo(const ExecutionContext& context, |
| const ScriptState& script_state, |
| ExceptionState& exception_state, |
| const AuctionAdInterestGroup& input, |
| mojom::blink::InterestGroup& output) { |
| if (!input.hasAdSizes()) { |
| return true; |
| } |
| output.ad_sizes.emplace(); |
| for (const auto& [name, size] : input.adSizes()) { |
| auto [width_val, width_units] = |
| blink::ParseAdSizeString(size->width().Ascii()); |
| auto [height_val, height_units] = |
| blink::ParseAdSizeString(size->height().Ascii()); |
| |
| output.ad_sizes->insert( |
| name, mojom::blink::AdSize::New(width_val, width_units, height_val, |
| height_units)); |
| } |
| return true; |
| } |
| |
| bool CopySizeGroupsFromIdlToMojo(const ExecutionContext& context, |
| const ScriptState& script_state, |
| ExceptionState& exception_state, |
| const AuctionAdInterestGroup& input, |
| mojom::blink::InterestGroup& output) { |
| if (!input.hasSizeGroups()) { |
| return true; |
| } |
| output.size_groups.emplace(); |
| for (const auto& group : input.sizeGroups()) { |
| output.size_groups->insert(group.first, group.second); |
| } |
| return true; |
| } |
| |
| // createAdRequest copy functions. |
| bool CopyAdRequestUrlFromIdlToMojo(const ExecutionContext& context, |
| ExceptionState& exception_state, |
| const AdRequestConfig& input, |
| mojom::blink::AdRequestConfig& output) { |
| KURL ad_request_url = context.CompleteURL(input.adRequestUrl()); |
| if (!ad_request_url.IsValid() || |
| (ad_request_url.Protocol() != url::kHttpsScheme)) { |
| exception_state.ThrowTypeError( |
| String::Format("adRequestUrl '%s' for AdRequestConfig must " |
| "be a valid https origin.", |
| input.adRequestUrl().Utf8().c_str())); |
| return false; |
| } |
| output.ad_request_url = ad_request_url; |
| return true; |
| } |
| |
| bool CopyAdPropertiesFromIdlToMojo(const ExecutionContext& context, |
| ExceptionState& exception_state, |
| const AdRequestConfig& input, |
| mojom::blink::AdRequestConfig& output) { |
| if (!input.hasAdProperties()) { |
| exception_state.ThrowTypeError( |
| ErrorInvalidAdRequestConfig(input, "adProperties", input.adRequestUrl(), |
| "must be provided to createAdRequest.")); |
| return false; |
| } |
| |
| // output.ad_properties = mojom::blink::AdProperties::New(); |
| switch (input.adProperties()->GetContentType()) { |
| case V8UnionAdPropertiesOrAdPropertiesSequence::ContentType:: |
| kAdProperties: { |
| const auto* ad_properties = input.adProperties()->GetAsAdProperties(); |
| auto mojo_ad_properties = mojom::blink::AdProperties::New(); |
| mojo_ad_properties->width = |
| ad_properties->hasWidth() ? ad_properties->width() : ""; |
| mojo_ad_properties->height = |
| ad_properties->hasHeight() ? ad_properties->height() : ""; |
| mojo_ad_properties->slot = |
| ad_properties->hasSlot() ? ad_properties->slot() : ""; |
| mojo_ad_properties->lang = |
| ad_properties->hasLang() ? ad_properties->lang() : ""; |
| mojo_ad_properties->ad_type = |
| ad_properties->hasAdtype() ? ad_properties->adtype() : ""; |
| mojo_ad_properties->bid_floor = |
| ad_properties->hasBidFloor() ? ad_properties->bidFloor() : 0.0; |
| |
| output.ad_properties.push_back(std::move(mojo_ad_properties)); |
| break; |
| } |
| case V8UnionAdPropertiesOrAdPropertiesSequence::ContentType:: |
| kAdPropertiesSequence: { |
| if (input.adProperties()->GetAsAdPropertiesSequence().size() <= 0) { |
| exception_state.ThrowTypeError(ErrorInvalidAdRequestConfig( |
| input, "adProperties", input.adRequestUrl(), |
| "must be non-empty to createAdRequest.")); |
| return false; |
| } |
| |
| for (const auto& ad_properties : |
| input.adProperties()->GetAsAdPropertiesSequence()) { |
| auto mojo_ad_properties = mojom::blink::AdProperties::New(); |
| mojo_ad_properties->width = |
| ad_properties->hasWidth() ? ad_properties->width() : ""; |
| mojo_ad_properties->height = |
| ad_properties->hasHeight() ? ad_properties->height() : ""; |
| mojo_ad_properties->slot = |
| ad_properties->hasSlot() ? ad_properties->slot() : ""; |
| mojo_ad_properties->lang = |
| ad_properties->hasLang() ? ad_properties->lang() : ""; |
| mojo_ad_properties->ad_type = |
| ad_properties->hasAdtype() ? ad_properties->adtype() : ""; |
| mojo_ad_properties->bid_floor = |
| ad_properties->hasBidFloor() ? ad_properties->bidFloor() : 0.0; |
| |
| output.ad_properties.push_back(std::move(mojo_ad_properties)); |
| } |
| break; |
| } |
| } |
| return true; |
| } |
| |
| bool CopyTargetingFromIdlToMojo(const ExecutionContext& context, |
| ExceptionState& exception_state, |
| const AdRequestConfig& input, |
| mojom::blink::AdRequestConfig& output) { |
| if (!input.hasTargeting()) { |
| // Targeting information is not required. |
| return true; |
| } |
| |
| output.targeting = mojom::blink::AdTargeting::New(); |
| |
| if (input.targeting()->hasInterests()) { |
| output.targeting->interests.emplace(); |
| for (const auto& interest : input.targeting()->interests()) { |
| output.targeting->interests->push_back(interest); |
| } |
| } |
| |
| if (input.targeting()->hasGeolocation()) { |
| output.targeting->geolocation = mojom::blink::AdGeolocation::New(); |
| output.targeting->geolocation->latitude = |
| input.targeting()->geolocation()->latitude(); |
| output.targeting->geolocation->longitude = |
| input.targeting()->geolocation()->longitude(); |
| } |
| |
| return true; |
| } |
| |
| bool CopyAdSignalsFromIdlToMojo(const ExecutionContext& context, |
| ExceptionState& exception_state, |
| const AdRequestConfig& input, |
| mojom::blink::AdRequestConfig& output) { |
| if (!input.hasAnonymizedProxiedSignals()) { |
| // AdSignals information is not required. |
| return true; |
| } |
| |
| output.anonymized_proxied_signals.emplace(); |
| |
| for (const auto& signal : input.anonymizedProxiedSignals()) { |
| if (signal == "coarse-geolocation") { |
| output.anonymized_proxied_signals->push_back( |
| blink::mojom::AdSignals::kCourseGeolocation); |
| } else if (signal == "coarse-ua") { |
| output.anonymized_proxied_signals->push_back( |
| blink::mojom::AdSignals::kCourseUserAgent); |
| } else if (signal == "targeting") { |
| output.anonymized_proxied_signals->push_back( |
| blink::mojom::AdSignals::kTargeting); |
| } else if (signal == "user-ad-interests") { |
| output.anonymized_proxied_signals->push_back( |
| blink::mojom::AdSignals::kUserAdInterests); |
| } |
| } |
| return true; |
| } |
| |
| bool CopyFallbackSourceFromIdlToMojo(const ExecutionContext& context, |
| ExceptionState& exception_state, |
| const AdRequestConfig& input, |
| mojom::blink::AdRequestConfig& output) { |
| if (!input.hasFallbackSource()) { |
| // FallbackSource information is not required. |
| return true; |
| } |
| |
| KURL fallback_source = context.CompleteURL(input.fallbackSource()); |
| if (!fallback_source.IsValid() || |
| (fallback_source.Protocol() != url::kHttpsScheme)) { |
| exception_state.ThrowTypeError( |
| String::Format("fallbackSource '%s' for AdRequestConfig must " |
| "be a valid https origin.", |
| input.fallbackSource().Utf8().c_str())); |
| return false; |
| } |
| output.fallback_source = fallback_source; |
| return true; |
| } |
| |
| // runAdAuction() copy functions. |
| |
| bool CopySellerFromIdlToMojo(ExceptionState& exception_state, |
| const AuctionAdConfig& input, |
| mojom::blink::AuctionAdConfig& output) { |
| scoped_refptr<const SecurityOrigin> seller = ParseOrigin(input.seller()); |
| if (!seller) { |
| exception_state.ThrowTypeError(String::Format( |
| "seller '%s' for AuctionAdConfig must be a valid https origin.", |
| input.seller().Utf8().c_str())); |
| return false; |
| } |
| output.seller = seller; |
| return true; |
| } |
| |
| bool CopyDecisionLogicUrlFromIdlToMojo(const ExecutionContext& context, |
| ExceptionState& exception_state, |
| const AuctionAdConfig& input, |
| mojom::blink::AuctionAdConfig& output) { |
| KURL decision_logic_url = context.CompleteURL(input.decisionLogicURL()); |
| if (!decision_logic_url.IsValid()) { |
| exception_state.ThrowTypeError(ErrorInvalidAuctionConfig( |
| input, "decisionLogicURL", input.decisionLogicURL(), |
| "cannot be resolved to a valid URL.")); |
| return false; |
| } |
| |
| // Need to check scheme of the URL in addition to comparing origins because |
| // FLEDGE currently only supports HTTPS URLs, and some non-HTTPS URLs can have |
| // HTTPS origins. |
| if (decision_logic_url.Protocol() != url::kHttpsScheme || |
| !output.seller->IsSameOriginWith( |
| SecurityOrigin::Create(decision_logic_url).get())) { |
| exception_state.ThrowTypeError(ErrorInvalidAuctionConfig( |
| input, "decisionLogicURL", input.decisionLogicURL(), |
| "must match seller origin.")); |
| return false; |
| } |
| |
| output.decision_logic_url = decision_logic_url; |
| return true; |
| } |
| |
| bool CopyTrustedScoringSignalsFromIdlToMojo( |
| const ExecutionContext& context, |
| ExceptionState& exception_state, |
| const AuctionAdConfig& input, |
| mojom::blink::AuctionAdConfig& output) { |
| if (!input.hasTrustedScoringSignalsURL()) { |
| return true; |
| } |
| KURL trusted_scoring_signals_url = |
| context.CompleteURL(input.trustedScoringSignalsURL()); |
| if (!trusted_scoring_signals_url.IsValid()) { |
| exception_state.ThrowTypeError(ErrorInvalidAuctionConfig( |
| input, "trustedScoringSignalsURL", input.trustedScoringSignalsURL(), |
| "cannot be resolved to a valid URL.")); |
| return false; |
| } |
| |
| // Need to check scheme of the URL in addition to comparing origins because |
| // FLEDGE currently only supports HTTPS URLs, and some non-HTTPS URLs can have |
| // HTTPS origins. |
| if (trusted_scoring_signals_url.Protocol() != url::kHttpsScheme || |
| !output.seller->IsSameOriginWith( |
| SecurityOrigin::Create(trusted_scoring_signals_url).get())) { |
| exception_state.ThrowTypeError(ErrorInvalidAuctionConfig( |
| input, "trustedScoringSignalsURL", input.trustedScoringSignalsURL(), |
| "must match seller origin.")); |
| return false; |
| } |
| |
| output.trusted_scoring_signals_url = trusted_scoring_signals_url; |
| return true; |
| } |
| |
| bool CopyInterestGroupBuyersFromIdlToMojo( |
| ExceptionState& exception_state, |
| const AuctionAdConfig& input, |
| mojom::blink::AuctionAdConfig& output) { |
| DCHECK(!output.auction_ad_config_non_shared_params->interest_group_buyers); |
| |
| if (!input.hasInterestGroupBuyers()) |
| return true; |
| |
| Vector<scoped_refptr<const SecurityOrigin>> buyers; |
| for (const auto& buyer_str : input.interestGroupBuyers()) { |
| scoped_refptr<const SecurityOrigin> buyer = ParseOrigin(buyer_str); |
| if (!buyer) { |
| exception_state.ThrowTypeError(ErrorInvalidAuctionConfig( |
| input, "interestGroupBuyers buyer", buyer_str, |
| "must be a valid https origin.")); |
| return false; |
| } |
| buyers.push_back(buyer); |
| } |
| output.auction_ad_config_non_shared_params->interest_group_buyers = |
| std::move(buyers); |
| return true; |
| } |
| |
| mojom::blink::AuctionAdConfigMaybePromiseJsonPtr |
| ConvertJsonPromiseFromIdlToMojo( |
| NavigatorAuction::AuctionHandle* auction_handle, |
| mojom::blink::AuctionAdConfigAuctionId* auction_id, |
| ScriptState& script_state, |
| ExceptionState& exception_state, |
| const AuctionAdConfig& input, |
| const ScriptValue& input_value, |
| mojom::blink::AuctionAdConfigField field, |
| const char* field_name) { |
| v8::Local<v8::Value> value = input_value.V8Value(); |
| |
| if (auction_handle && value->IsPromise()) { |
| ScriptPromise promise(&script_state, value); |
| auction_handle->AttachPromiseHandler( |
| script_state, promise, |
| MakeGarbageCollected<NavigatorAuction::AuctionHandle::JsonResolved>( |
| auction_handle, auction_id->Clone(), field, input.seller(), |
| field_name)); |
| return mojom::blink::AuctionAdConfigMaybePromiseJson::NewPromise(0); |
| } else { |
| String json_payload; |
| if (!Jsonify(script_state, value, json_payload)) { |
| exception_state.ThrowTypeError( |
| ErrorInvalidAuctionConfigJson(input, field_name)); |
| return nullptr; |
| } |
| |
| return mojom::blink::AuctionAdConfigMaybePromiseJson::NewValue( |
| json_payload); |
| } |
| } |
| |
| // null `auction_handle` disables promise handling. |
| // `auction_id` should be null iff `auction_handle` is. |
| bool CopyAuctionSignalsFromIdlToMojo( |
| NavigatorAuction::AuctionHandle* auction_handle, |
| mojom::blink::AuctionAdConfigAuctionId* auction_id, |
| ScriptState& script_state, |
| ExceptionState& exception_state, |
| const AuctionAdConfig& input, |
| mojom::blink::AuctionAdConfig& output) { |
| DCHECK_EQ(auction_id == nullptr, auction_handle == nullptr); |
| |
| if (!input.hasAuctionSignals()) { |
| output.auction_ad_config_non_shared_params->auction_signals = |
| mojom::blink::AuctionAdConfigMaybePromiseJson::NewValue(String()); |
| return true; |
| } |
| |
| output.auction_ad_config_non_shared_params->auction_signals = |
| ConvertJsonPromiseFromIdlToMojo( |
| auction_handle, auction_id, script_state, exception_state, input, |
| input.auctionSignals(), |
| mojom::blink::AuctionAdConfigField::kAuctionSignals, |
| "auctionSignals"); |
| return !output.auction_ad_config_non_shared_params->auction_signals.is_null(); |
| } |
| |
| bool CopySellerSignalsFromIdlToMojo( |
| NavigatorAuction::AuctionHandle* auction_handle, |
| mojom::blink::AuctionAdConfigAuctionId* auction_id, |
| ScriptState& script_state, |
| ExceptionState& exception_state, |
| const AuctionAdConfig& input, |
| mojom::blink::AuctionAdConfig& output) { |
| if (!input.hasSellerSignals()) { |
| output.auction_ad_config_non_shared_params->seller_signals = |
| mojom::blink::AuctionAdConfigMaybePromiseJson::NewValue(String()); |
| return true; |
| } |
| |
| output.auction_ad_config_non_shared_params->seller_signals = |
| ConvertJsonPromiseFromIdlToMojo( |
| auction_handle, auction_id, script_state, exception_state, input, |
| input.sellerSignals(), |
| mojom::blink::AuctionAdConfigField::kSellerSignals, "sellerSignals"); |
| return !output.auction_ad_config_non_shared_params->seller_signals.is_null(); |
| } |
| |
| // Attempts to build a DirectFromSellerSignalsSubresource. If there is no |
| // registered subresource URL `subresource_url` returns nullptr -- processing |
| // may continue with the next `subresource_url`. |
| mojom::blink::DirectFromSellerSignalsSubresourcePtr |
| TryToBuildDirectFromSellerSignalsSubresource( |
| const KURL& subresource_url, |
| const SecurityOrigin& seller, |
| ExceptionState& exception_state, |
| const ResourceFetcher& resource_fetcher) { |
| DCHECK(subresource_url.IsValid()); |
| DCHECK( |
| subresource_url.ProtocolIs(url::kHttpsScheme) && |
| seller.IsSameOriginWith(SecurityOrigin::Create(subresource_url).get())); |
| // NOTE: If subresource bundles are disabled, GetSubresourceBundleToken() will |
| // always return absl::nullopt. |
| absl::optional<base::UnguessableToken> token = |
| resource_fetcher.GetSubresourceBundleToken(subresource_url); |
| if (!token) |
| return nullptr; |
| absl::optional<KURL> bundle_url = |
| resource_fetcher.GetSubresourceBundleSourceUrl(subresource_url); |
| DCHECK(bundle_url->ProtocolIs(url::kHttpsScheme)); |
| DCHECK(seller.IsSameOriginWith(SecurityOrigin::Create(*bundle_url).get())); |
| auto mojo_bundle = mojom::blink::DirectFromSellerSignalsSubresource::New(); |
| mojo_bundle->token = *token; |
| mojo_bundle->bundle_url = *bundle_url; |
| return mojo_bundle; |
| } |
| |
| mojom::blink::DirectFromSellerSignalsPtr |
| ConvertDirectFromSellerSignalsFromV8ToMojo( |
| const ScriptState& script_state, |
| const ExecutionContext& context, |
| ExceptionState& exception_state, |
| const ResourceFetcher& resource_fetcher, |
| const String& seller_name, |
| const SecurityOrigin& seller_origin, |
| const absl::optional<Vector<scoped_refptr<const SecurityOrigin>>>& |
| interest_group_buyers, |
| v8::Local<v8::Value> value) { |
| String prefix_string = NativeValueTraits<IDLUSVString>::NativeValue( |
| script_state.GetIsolate(), value, exception_state); |
| if (exception_state.HadException()) { |
| return nullptr; |
| } |
| |
| const KURL direct_from_seller_signals_prefix = |
| context.CompleteURL(prefix_string); |
| if (!direct_from_seller_signals_prefix.IsValid()) { |
| exception_state.ThrowTypeError(ErrorInvalidAuctionConfigSeller( |
| seller_name, "directFromSellerSignals", prefix_string, |
| "cannot be resolved to a valid URL.")); |
| return nullptr; |
| } |
| if (!direct_from_seller_signals_prefix.ProtocolIs(url::kHttpsScheme) || |
| !seller_origin.IsSameOriginWith( |
| SecurityOrigin::Create(direct_from_seller_signals_prefix).get())) { |
| exception_state.ThrowTypeError(ErrorInvalidAuctionConfigSeller( |
| seller_name, "directFromSellerSignals", prefix_string, |
| "must match seller origin; only https scheme is supported.")); |
| return nullptr; |
| } |
| if (!direct_from_seller_signals_prefix.Query().empty()) { |
| exception_state.ThrowTypeError(ErrorInvalidAuctionConfigSeller( |
| seller_name, "directFromSellerSignals", prefix_string, |
| "URL prefix must not have a query string.")); |
| return nullptr; |
| } |
| auto mojo_direct_from_seller_signals = |
| mojom::blink::DirectFromSellerSignals::New(); |
| mojo_direct_from_seller_signals->prefix = direct_from_seller_signals_prefix; |
| |
| if (interest_group_buyers) { |
| for (scoped_refptr<const SecurityOrigin> buyer : *interest_group_buyers) { |
| // Replace "/" with "%2F" to match the behavior of |
| // base::EscapeQueryParamValue(). Also, the subresource won't be found if |
| // the URL doesn't match. |
| const KURL subresource_url( |
| direct_from_seller_signals_prefix.GetString() + "?perBuyerSignals=" + |
| EncodeWithURLEscapeSequences(buyer->ToString()).Replace("/", "%2F")); |
| mojom::blink::DirectFromSellerSignalsSubresourcePtr maybe_mojo_bundle = |
| TryToBuildDirectFromSellerSignalsSubresource( |
| subresource_url, seller_origin, exception_state, |
| resource_fetcher); |
| if (!maybe_mojo_bundle) |
| continue; // The bundle wasn't found, try the next one. |
| mojo_direct_from_seller_signals->per_buyer_signals.insert( |
| buyer, std::move(maybe_mojo_bundle)); |
| } |
| } |
| |
| { |
| const KURL subresource_url(direct_from_seller_signals_prefix.GetString() + |
| "?sellerSignals"); |
| mojom::blink::DirectFromSellerSignalsSubresourcePtr maybe_mojo_bundle = |
| TryToBuildDirectFromSellerSignalsSubresource( |
| subresource_url, seller_origin, exception_state, resource_fetcher); |
| // May be null if the signals weren't found. |
| mojo_direct_from_seller_signals->seller_signals = |
| std::move(maybe_mojo_bundle); |
| } |
| |
| { |
| const KURL subresource_url(direct_from_seller_signals_prefix.GetString() + |
| "?auctionSignals"); |
| mojom::blink::DirectFromSellerSignalsSubresourcePtr maybe_mojo_bundle = |
| TryToBuildDirectFromSellerSignalsSubresource( |
| subresource_url, seller_origin, exception_state, resource_fetcher); |
| // May be null if the signals weren't found. |
| mojo_direct_from_seller_signals->auction_signals = |
| std::move(maybe_mojo_bundle); |
| } |
| |
| return mojo_direct_from_seller_signals; |
| } |
| |
| bool CopyDirectFromSellerSignalsFromIdlToMojo( |
| NavigatorAuction::AuctionHandle* auction_handle, |
| const mojom::blink::AuctionAdConfigAuctionId* auction_id, |
| ScriptState& script_state, |
| const ExecutionContext& context, |
| ExceptionState& exception_state, |
| const AuctionAdConfig& input, |
| const ResourceFetcher& resource_fetcher, |
| mojom::blink::AuctionAdConfig& output) { |
| if (!input.hasDirectFromSellerSignals()) { |
| output.direct_from_seller_signals = mojom::blink:: |
| AuctionAdConfigMaybePromiseDirectFromSellerSignals::NewValue(nullptr); |
| return true; |
| } |
| |
| v8::Local<v8::Value> value = input.directFromSellerSignals().V8Value(); |
| if (auction_handle && value->IsPromise()) { |
| ScriptPromise promise(&script_state, value); |
| auction_handle->AttachPromiseHandler( |
| script_state, promise, |
| MakeGarbageCollected< |
| NavigatorAuction::AuctionHandle::DirectFromSellerSignalsResolved>( |
| auction_handle, auction_id->Clone(), input.seller(), output.seller, |
| output.auction_ad_config_non_shared_params->interest_group_buyers)); |
| output.direct_from_seller_signals = mojom::blink:: |
| AuctionAdConfigMaybePromiseDirectFromSellerSignals::NewPromise(0); |
| return true; |
| } |
| |
| auto direct_from_seller_signals = ConvertDirectFromSellerSignalsFromV8ToMojo( |
| script_state, context, exception_state, resource_fetcher, input.seller(), |
| *output.seller, |
| output.auction_ad_config_non_shared_params->interest_group_buyers, value); |
| if (direct_from_seller_signals) { |
| output.direct_from_seller_signals = |
| mojom::blink::AuctionAdConfigMaybePromiseDirectFromSellerSignals:: |
| NewValue(std::move(direct_from_seller_signals)); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| // Returns nullopt + sets exception on failure, or returns a concrete value. |
| absl::optional<HashMap<scoped_refptr<const SecurityOrigin>, String>> |
| ConvertNonPromisePerBuyerSignalsFromV8ToMojo(const ScriptState& script_state, |
| ExceptionState& exception_state, |
| const String& seller_name, |
| v8::Local<v8::Value> value) { |
| HeapVector<std::pair<WTF::String, blink::ScriptValue>> decoded = |
| NativeValueTraits<IDLRecord<IDLUSVString, IDLAny>>::NativeValue( |
| script_state.GetIsolate(), value, exception_state); |
| if (exception_state.HadException()) { |
| return absl::nullopt; |
| } |
| |
| absl::optional<HashMap<scoped_refptr<const SecurityOrigin>, String>> |
| per_buyer_signals; |
| |
| per_buyer_signals.emplace(); |
| for (const auto& per_buyer_signal : decoded) { |
| scoped_refptr<const SecurityOrigin> buyer = |
| ParseOrigin(per_buyer_signal.first); |
| if (!buyer) { |
| exception_state.ThrowTypeError(ErrorInvalidAuctionConfigSeller( |
| seller_name, "perBuyerSignals buyer", per_buyer_signal.first, |
| "must be a valid https origin.")); |
| return absl::nullopt; |
| } |
| String buyer_signals_str; |
| if (!Jsonify(script_state, per_buyer_signal.second.V8Value(), |
| buyer_signals_str)) { |
| exception_state.ThrowTypeError( |
| ErrorInvalidAuctionConfigSellerJson(seller_name, "perBuyerSignals")); |
| return absl::nullopt; |
| } |
| per_buyer_signals->insert(buyer, std::move(buyer_signals_str)); |
| } |
| |
| return per_buyer_signals; |
| } |
| |
| bool CopyPerBuyerSignalsFromIdlToMojo( |
| NavigatorAuction::AuctionHandle* auction_handle, |
| const mojom::blink::AuctionAdConfigAuctionId* auction_id, |
| ScriptState& script_state, |
| ExceptionState& exception_state, |
| const AuctionAdConfig& input, |
| mojom::blink::AuctionAdConfig& output) { |
| if (!input.hasPerBuyerSignals()) { |
| output.auction_ad_config_non_shared_params->per_buyer_signals = |
| mojom::blink::AuctionAdConfigMaybePromisePerBuyerSignals::NewValue( |
| absl::nullopt); |
| return true; |
| } |
| |
| v8::Local<v8::Value> value = input.perBuyerSignals().V8Value(); |
| if (auction_handle && value->IsPromise()) { |
| ScriptPromise promise(&script_state, value); |
| auction_handle->AttachPromiseHandler( |
| script_state, promise, |
| MakeGarbageCollected< |
| NavigatorAuction::AuctionHandle::PerBuyerSignalsResolved>( |
| auction_handle, auction_id->Clone(), input.seller())); |
| output.auction_ad_config_non_shared_params->per_buyer_signals = |
| mojom::blink::AuctionAdConfigMaybePromisePerBuyerSignals::NewPromise(0); |
| return true; |
| } |
| |
| auto per_buyer_signals = ConvertNonPromisePerBuyerSignalsFromV8ToMojo( |
| script_state, exception_state, input.seller(), value); |
| if (per_buyer_signals.has_value()) { |
| output.auction_ad_config_non_shared_params->per_buyer_signals = |
| mojom::blink::AuctionAdConfigMaybePromisePerBuyerSignals::NewValue( |
| std::move(per_buyer_signals)); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| // Returns nullptr + sets exception on failure, or returns a concrete value. |
| // |
| // This is shared logic for `perBuyerTimeouts` and `perBuyerCumulativeTimeouts`, |
| // with `field` indicating which name to use in error messages. The logic is |
| // identical in both cases. |
| mojom::blink::AuctionAdConfigBuyerTimeoutsPtr |
| ConvertNonPromisePerBuyerTimeoutsFromV8ToMojo( |
| const ScriptState& script_state, |
| ExceptionState& exception_state, |
| const String& seller_name, |
| v8::Local<v8::Value> value, |
| mojom::blink::AuctionAdConfigBuyerTimeoutField field) { |
| Vector<std::pair<String, uint64_t>> decoded = |
| NativeValueTraits<IDLRecord<IDLUSVString, IDLUnsignedLongLong>>:: |
| NativeValue(script_state.GetIsolate(), value, exception_state); |
| if (exception_state.HadException()) { |
| return nullptr; |
| } |
| |
| mojom::blink::AuctionAdConfigBuyerTimeoutsPtr buyer_timeouts = |
| mojom::blink::AuctionAdConfigBuyerTimeouts::New(); |
| buyer_timeouts->per_buyer_timeouts.emplace(); |
| for (const auto& per_buyer_timeout : decoded) { |
| if (per_buyer_timeout.first == "*") { |
| buyer_timeouts->all_buyers_timeout = |
| base::Milliseconds(per_buyer_timeout.second); |
| continue; |
| } |
| scoped_refptr<const SecurityOrigin> buyer = |
| ParseOrigin(per_buyer_timeout.first); |
| if (!buyer) { |
| String field_name; |
| switch (field) { |
| case mojom::blink::AuctionAdConfigBuyerTimeoutField::kPerBuyerTimeouts: |
| field_name = "perBuyerTimeouts buyer"; |
| break; |
| case mojom::blink::AuctionAdConfigBuyerTimeoutField:: |
| kPerBuyerCumulativeTimeouts: |
| field_name = "perBuyerCumulativeTimeouts buyer"; |
| break; |
| } |
| exception_state.ThrowTypeError(ErrorInvalidAuctionConfigSeller( |
| seller_name, field_name, per_buyer_timeout.first, |
| "must be \"*\" (wildcard) or a valid https origin.")); |
| return nullptr; |
| } |
| buyer_timeouts->per_buyer_timeouts->insert( |
| buyer, base::Milliseconds(per_buyer_timeout.second)); |
| } |
| |
| return buyer_timeouts; |
| } |
| |
| bool CopyPerBuyerTimeoutsFromIdlToMojo( |
| NavigatorAuction::AuctionHandle* auction_handle, |
| const mojom::blink::AuctionAdConfigAuctionId* auction_id, |
| ScriptState& script_state, |
| ExceptionState& exception_state, |
| const AuctionAdConfig& input, |
| mojom::blink::AuctionAdConfig& output) { |
| if (!input.hasPerBuyerTimeouts()) { |
| output.auction_ad_config_non_shared_params->buyer_timeouts = |
| mojom::blink::AuctionAdConfigMaybePromiseBuyerTimeouts::NewValue( |
| mojom::blink::AuctionAdConfigBuyerTimeouts::New()); |
| return true; |
| } |
| |
| v8::Local<v8::Value> value = input.perBuyerTimeouts().V8Value(); |
| if (auction_handle && value->IsPromise()) { |
| ScriptPromise promise(&script_state, value); |
| auction_handle->AttachPromiseHandler( |
| script_state, promise, |
| MakeGarbageCollected< |
| NavigatorAuction::AuctionHandle::BuyerTimeoutsResolved>( |
| auction_handle, auction_id->Clone(), |
| mojom::blink::AuctionAdConfigBuyerTimeoutField::kPerBuyerTimeouts, |
| input.seller())); |
| output.auction_ad_config_non_shared_params->buyer_timeouts = |
| mojom::blink::AuctionAdConfigMaybePromiseBuyerTimeouts::NewPromise(0); |
| return true; |
| } |
| |
| mojom::blink::AuctionAdConfigBuyerTimeoutsPtr buyer_timeouts = |
| ConvertNonPromisePerBuyerTimeoutsFromV8ToMojo( |
| script_state, exception_state, input.seller(), value, |
| mojom::blink::AuctionAdConfigBuyerTimeoutField::kPerBuyerTimeouts); |
| if (buyer_timeouts) { |
| output.auction_ad_config_non_shared_params->buyer_timeouts = |
| mojom::blink::AuctionAdConfigMaybePromiseBuyerTimeouts::NewValue( |
| std::move(buyer_timeouts)); |
| return true; |
| } |
| return false; |
| } |
| |
| bool CopyPerBuyerCumulativeTimeoutsFromIdlToMojo( |
| NavigatorAuction::AuctionHandle* auction_handle, |
| const mojom::blink::AuctionAdConfigAuctionId* auction_id, |
| ScriptState& script_state, |
| ExceptionState& exception_state, |
| const AuctionAdConfig& input, |
| mojom::blink::AuctionAdConfig& output) { |
| if (!input.hasPerBuyerCumulativeTimeouts()) { |
| output.auction_ad_config_non_shared_params->buyer_cumulative_timeouts = |
| mojom::blink::AuctionAdConfigMaybePromiseBuyerTimeouts::NewValue( |
| mojom::blink::AuctionAdConfigBuyerTimeouts::New()); |
| return true; |
| } |
| |
| v8::Local<v8::Value> value = input.perBuyerCumulativeTimeouts().V8Value(); |
| if (auction_handle && value->IsPromise()) { |
| ScriptPromise promise(&script_state, value); |
| auction_handle->AttachPromiseHandler( |
| script_state, promise, |
| MakeGarbageCollected< |
| NavigatorAuction::AuctionHandle::BuyerTimeoutsResolved>( |
| auction_handle, auction_id->Clone(), |
| mojom::blink::AuctionAdConfigBuyerTimeoutField:: |
| kPerBuyerCumulativeTimeouts, |
| input.seller())); |
| output.auction_ad_config_non_shared_params->buyer_cumulative_timeouts = |
| mojom::blink::AuctionAdConfigMaybePromiseBuyerTimeouts::NewPromise(0); |
| return true; |
| } |
| |
| mojom::blink::AuctionAdConfigBuyerTimeoutsPtr buyer_cumulative_timeouts = |
| ConvertNonPromisePerBuyerTimeoutsFromV8ToMojo( |
| script_state, exception_state, input.seller(), value, |
| mojom::blink::AuctionAdConfigBuyerTimeoutField:: |
| kPerBuyerCumulativeTimeouts); |
| if (buyer_cumulative_timeouts) { |
| output.auction_ad_config_non_shared_params->buyer_cumulative_timeouts = |
| mojom::blink::AuctionAdConfigMaybePromiseBuyerTimeouts::NewValue( |
| std::move(buyer_cumulative_timeouts)); |
| return true; |
| } |
| return false; |
| } |
| |
| // Returns nullptr + sets exception on failure, or returns a concrete value. |
| mojom::blink::AuctionAdConfigBuyerCurrenciesPtr |
| ConvertNonPromisePerBuyerCurrenciesFromV8ToMojo(const ScriptState& script_state, |
| ExceptionState& exception_state, |
| const String& seller_name, |
| v8::Local<v8::Value> value) { |
| Vector<std::pair<String, String>> decoded = |
| NativeValueTraits<IDLRecord<IDLUSVString, IDLUSVString>>::NativeValue( |
| script_state.GetIsolate(), value, exception_state); |
| if (exception_state.HadException()) { |
| return nullptr; |
| } |
| |
| mojom::blink::AuctionAdConfigBuyerCurrenciesPtr buyer_currencies = |
| mojom::blink::AuctionAdConfigBuyerCurrencies::New(); |
| buyer_currencies->per_buyer_currencies.emplace(); |
| for (const auto& per_buyer_currency : decoded) { |
| std::string per_buyer_currency_str = per_buyer_currency.second.Ascii(); |
| if (!IsValidAdCurrencyCode(per_buyer_currency_str)) { |
| exception_state.ThrowTypeError(ErrorInvalidAuctionConfigSeller( |
| seller_name, "perBuyerCurrencies currency", per_buyer_currency.second, |
| "must be a 3-letter uppercase currency code.")); |
| return nullptr; |
| } |
| |
| if (per_buyer_currency.first == "*") { |
| buyer_currencies->all_buyers_currency = |
| blink::AdCurrency::From(per_buyer_currency_str); |
| continue; |
| } |
| scoped_refptr<const SecurityOrigin> buyer = |
| ParseOrigin(per_buyer_currency.first); |
| if (!buyer) { |
| exception_state.ThrowTypeError(ErrorInvalidAuctionConfigSeller( |
| seller_name, "perBuyerCurrencies buyer", per_buyer_currency.first, |
| "must be \"*\" (wildcard) or a valid https origin.")); |
| return nullptr; |
| } |
| buyer_currencies->per_buyer_currencies->insert( |
| buyer, blink::AdCurrency::From(per_buyer_currency_str)); |
| } |
| |
| return buyer_currencies; |
| } |
| |
| bool CopyPerBuyerCurrenciesFromIdlToMojo( |
| NavigatorAuction::AuctionHandle* auction_handle, |
| const mojom::blink::AuctionAdConfigAuctionId* auction_id, |
| ScriptState& script_state, |
| ExceptionState& exception_state, |
| const AuctionAdConfig& input, |
| mojom::blink::AuctionAdConfig& output) { |
| if (!input.hasPerBuyerCurrencies()) { |
| output.auction_ad_config_non_shared_params->buyer_currencies = |
| mojom::blink::AuctionAdConfigMaybePromiseBuyerCurrencies::NewValue( |
| mojom::blink::AuctionAdConfigBuyerCurrencies::New()); |
| return true; |
| } |
| |
| v8::Local<v8::Value> value = input.perBuyerCurrencies().V8Value(); |
| if (auction_handle && value->IsPromise()) { |
| ScriptPromise promise(&script_state, value); |
| auction_handle->AttachPromiseHandler( |
| script_state, promise, |
| MakeGarbageCollected< |
| NavigatorAuction::AuctionHandle::BuyerCurrenciesResolved>( |
| auction_handle, auction_id->Clone(), input.seller())); |
| output.auction_ad_config_non_shared_params->buyer_currencies = |
| mojom::blink::AuctionAdConfigMaybePromiseBuyerCurrencies::NewPromise(0); |
| return true; |
| } |
| |
| mojom::blink::AuctionAdConfigBuyerCurrenciesPtr buyer_currencies = |
| ConvertNonPromisePerBuyerCurrenciesFromV8ToMojo( |
| script_state, exception_state, input.seller(), value); |
| if (buyer_currencies) { |
| output.auction_ad_config_non_shared_params->buyer_currencies = |
| mojom::blink::AuctionAdConfigMaybePromiseBuyerCurrencies::NewValue( |
| std::move(buyer_currencies)); |
| return true; |
| } |
| return false; |
| } |
| |
| bool CopyPerBuyerExperimentIdsFromIdlToMojo( |
| const ScriptState& script_state, |
| ExceptionState& exception_state, |
| const AuctionAdConfig& input, |
| mojom::blink::AuctionAdConfig& output) { |
| if (!input.hasPerBuyerExperimentGroupIds()) |
| return true; |
| for (const auto& per_buyer_experiment_id : |
| input.perBuyerExperimentGroupIds()) { |
| if (per_buyer_experiment_id.first == "*") { |
| output.has_all_buyer_experiment_group_id = true; |
| output.all_buyer_experiment_group_id = per_buyer_experiment_id.second; |
| continue; |
| } |
| scoped_refptr<const SecurityOrigin> buyer = |
| ParseOrigin(per_buyer_experiment_id.first); |
| if (!buyer) { |
| exception_state.ThrowTypeError(ErrorInvalidAuctionConfig( |
| input, "perBuyerExperimentGroupIds buyer", |
| per_buyer_experiment_id.first, |
| "must be \"*\" (wildcard) or a valid https origin.")); |
| return false; |
| } |
| output.per_buyer_experiment_group_ids.insert( |
| buyer, per_buyer_experiment_id.second); |
| } |
| |
| return true; |
| } |
| |
| bool CopyPerBuyerGroupLimitsFromIdlToMojo( |
| const ScriptState& script_state, |
| ExceptionState& exception_state, |
| const AuctionAdConfig& input, |
| mojom::blink::AuctionAdConfig& output) { |
| if (!input.hasPerBuyerGroupLimits()) |
| return true; |
| for (const auto& per_buyer_group_limit : input.perBuyerGroupLimits()) { |
| if (per_buyer_group_limit.second <= 0) { |
| exception_state.ThrowTypeError(ErrorInvalidAuctionConfig( |
| input, "perBuyerGroupLimits value", |
| String::Number(per_buyer_group_limit.second), |
| "must be greater than 0.")); |
| return false; |
| } |
| if (per_buyer_group_limit.first == "*") { |
| output.auction_ad_config_non_shared_params->all_buyers_group_limit = |
| per_buyer_group_limit.second; |
| continue; |
| } |
| scoped_refptr<const SecurityOrigin> buyer = |
| ParseOrigin(per_buyer_group_limit.first); |
| if (!buyer) { |
| exception_state.ThrowTypeError(ErrorInvalidAuctionConfig( |
| input, "perBuyerGroupLimits buyer", per_buyer_group_limit.first, |
| "must be \"*\" (wildcard) or a valid https origin.")); |
| return false; |
| } |
| output.auction_ad_config_non_shared_params->per_buyer_group_limits.insert( |
| buyer, per_buyer_group_limit.second); |
| } |
| |
| return true; |
| } |
| |
| bool ConvertAuctionConfigPrioritySignalsFromIdlToMojo( |
| ExceptionState& exception_state, |
| const AuctionAdConfig& input, |
| const Vector<std::pair<WTF::String, double>>& priority_signals_in, |
| WTF::HashMap<WTF::String, double>& priority_signals_out) { |
| for (const auto& key_value_pair : priority_signals_in) { |
| if (key_value_pair.first.StartsWith("browserSignals.")) { |
| exception_state.ThrowTypeError(ErrorInvalidAuctionConfig( |
| input, "perBuyerPrioritySignals key", key_value_pair.first, |
| "must not start with reserved \"browserSignals.\" prefix.")); |
| return false; |
| } |
| priority_signals_out.insert(key_value_pair.first, key_value_pair.second); |
| } |
| return true; |
| } |
| |
| bool CopyPerBuyerPrioritySignalsFromIdlToMojo( |
| ExceptionState& exception_state, |
| const AuctionAdConfig& input, |
| mojom::blink::AuctionAdConfig& output) { |
| if (!input.hasPerBuyerPrioritySignals()) |
| return true; |
| |
| output.auction_ad_config_non_shared_params->per_buyer_priority_signals |
| .emplace(); |
| for (const auto& per_buyer_priority_signals : |
| input.perBuyerPrioritySignals()) { |
| WTF::HashMap<WTF::String, double> signals; |
| if (!ConvertAuctionConfigPrioritySignalsFromIdlToMojo( |
| exception_state, input, per_buyer_priority_signals.second, |
| signals)) { |
| return false; |
| } |
| if (per_buyer_priority_signals.first == "*") { |
| output.auction_ad_config_non_shared_params->all_buyers_priority_signals = |
| std::move(signals); |
| continue; |
| } |
| scoped_refptr<const SecurityOrigin> buyer = |
| ParseOrigin(per_buyer_priority_signals.first); |
| if (!buyer) { |
| exception_state.ThrowTypeError(ErrorInvalidAuctionConfig( |
| input, "perBuyerPrioritySignals buyer", |
| per_buyer_priority_signals.first, |
| "must be \"*\" (wildcard) or a valid https origin.")); |
| return false; |
| } |
| output.auction_ad_config_non_shared_params->per_buyer_priority_signals |
| ->insert(buyer, std::move(signals)); |
| } |
| |
| return true; |
| } |
| |
| // TODO(caraitto): Consider validating keys -- no bucket base + offset |
| // conflicts, no overflow, etc. |
| bool CopyAuctionReportBuyerKeysFromIdlToMojo( |
| ExceptionState& exception_state, |
| const AuctionAdConfig& input, |
| mojom::blink::AuctionAdConfig& output) { |
| if (!input.hasAuctionReportBuyerKeys()) { |
| return true; |
| } |
| |
| output.auction_ad_config_non_shared_params->auction_report_buyer_keys |
| .emplace(); |
| for (const BigInt& value : input.auctionReportBuyerKeys()) { |
| base::expected<absl::uint128, String> maybe_bucket = |
| CopyBigIntToUint128(value); |
| if (!maybe_bucket.has_value()) { |
| exception_state.ThrowTypeError(ErrorInvalidAuctionConfigUint128( |
| input, "auctionReportBuyerKeys", maybe_bucket.error())); |
| return false; |
| } |
| output.auction_ad_config_non_shared_params->auction_report_buyer_keys |
| ->push_back(*maybe_bucket); |
| } |
| |
| return true; |
| } |
| |
| bool CopyAuctionReportBuyersFromIdlToMojo( |
| ExceptionState& exception_state, |
| const AuctionAdConfig& input, |
| mojom::blink::AuctionAdConfig& output) { |
| if (!input.hasAuctionReportBuyers()) { |
| return true; |
| } |
| |
| output.auction_ad_config_non_shared_params->auction_report_buyers.emplace(); |
| for (const auto& [report_type_string, report_config] : |
| input.auctionReportBuyers()) { |
| mojom::blink::AuctionAdConfigNonSharedParams::BuyerReportType report_type; |
| if (report_type_string == "interestGroupCount") { |
| report_type = mojom::blink::AuctionAdConfigNonSharedParams:: |
| BuyerReportType::kInterestGroupCount; |
| } else if (report_type_string == "bidCount") { |
| report_type = mojom::blink::AuctionAdConfigNonSharedParams:: |
| BuyerReportType::kBidCount; |
| } else if (report_type_string == "totalGenerateBidLatency") { |
| report_type = mojom::blink::AuctionAdConfigNonSharedParams:: |
| BuyerReportType::kTotalGenerateBidLatency; |
| } else if (report_type_string == "totalSignalsFetchLatency") { |
| report_type = mojom::blink::AuctionAdConfigNonSharedParams:: |
| BuyerReportType::kTotalSignalsFetchLatency; |
| } else { |
| // Don't throw an error if an unknown type is provided to provide forward |
| // compatibility with new fields added later. |
| continue; |
| } |
| base::expected<absl::uint128, String> maybe_bucket = |
| CopyBigIntToUint128(report_config->bucket()); |
| if (!maybe_bucket.has_value()) { |
| exception_state.ThrowTypeError(ErrorInvalidAuctionConfigUint128( |
| input, "auctionReportBuyers", maybe_bucket.error())); |
| return false; |
| } |
| output.auction_ad_config_non_shared_params->auction_report_buyers->insert( |
| report_type, mojom::blink::AuctionReportBuyersConfig::New( |
| *maybe_bucket, report_config->scale())); |
| } |
| |
| return true; |
| } |
| |
| bool CopyRequiredSellerSignalsFromIdlToMojo( |
| const ExecutionContext& execution_context, |
| ExceptionState& exception_state, |
| const AuctionAdConfig& input, |
| mojom::blink::AuctionAdConfig& output) { |
| output.auction_ad_config_non_shared_params->required_seller_capabilities = |
| mojom::blink::SellerCapabilities::New(); |
| if (!input.hasRequiredSellerCapabilities()) { |
| return true; |
| } |
| |
| output.auction_ad_config_non_shared_params->required_seller_capabilities = |
| ConvertSellerCapabilitiesTypeFromIdlToMojo( |
| execution_context, input.requiredSellerCapabilities()); |
| return true; |
| } |
| |
| bool CopyRequestedSizeFromIdlToMojo(const ExecutionContext& execution_context, |
| ExceptionState& exception_state, |
| const AuctionAdConfig& input, |
| mojom::blink::AuctionAdConfig& output) { |
| if (!input.hasRequestedSize()) { |
| return true; |
| } |
| auto [width_val, width_units] = |
| blink::ParseAdSizeString(input.requestedSize()->width().Ascii()); |
| auto [height_val, height_units] = |
| blink::ParseAdSizeString(input.requestedSize()->height().Ascii()); |
| if (width_units == blink::AdSize::LengthUnit::kInvalid) { |
| exception_state.ThrowTypeError(ErrorInvalidAuctionConfig( |
| input, "requestedSize width", input.requestedSize()->width(), |
| "must use units '', 'px', 'sw', or 'sh'.")); |
| return false; |
| } |
| if (height_units == blink::AdSize::LengthUnit::kInvalid) { |
| exception_state.ThrowTypeError(ErrorInvalidAuctionConfig( |
| input, "requestedSize height", input.requestedSize()->height(), |
| "must use units '', 'px', 'sw', or 'sh'.")); |
| return false; |
| } |
| if (width_val <= 0 || !std::isfinite(width_val)) { |
| exception_state.ThrowTypeError(ErrorInvalidAuctionConfig( |
| input, "requestedSize width", input.requestedSize()->width(), |
| "must be finite and positive.")); |
| return false; |
| } |
| if (height_val <= 0 || !std::isfinite(height_val)) { |
| exception_state.ThrowTypeError(ErrorInvalidAuctionConfig( |
| input, "requestedSize height", input.requestedSize()->height(), |
| "must be finite and positive.")); |
| return false; |
| } |
| output.auction_ad_config_non_shared_params->requested_size = |
| mojom::blink::AdSize::New(width_val, width_units, height_val, |
| height_units); |
| return true; |
| } |
| |
| // Attempts to convert the AuctionAdConfig `config`, passed in via Javascript, |
| // to a `mojom::blink::AuctionAdConfig`. Throws a Javascript exception and |
| // return null on failure. `auction_handle` is used for promise handling; |
| // if it's null, promise will not be accepted. |
| mojom::blink::AuctionAdConfigPtr IdlAuctionConfigToMojo( |
| NavigatorAuction::AuctionHandle* auction_handle, |
| bool is_top_level, |
| uint32_t nested_pos, |
| ScriptState& script_state, |
| const ExecutionContext& context, |
| ExceptionState& exception_state, |
| const ResourceFetcher& resource_fetcher, |
| const AuctionAdConfig& config) { |
| auto mojo_config = mojom::blink::AuctionAdConfig::New(); |
| mojo_config->auction_ad_config_non_shared_params = |
| mojom::blink::AuctionAdConfigNonSharedParams::New(); |
| mojom::blink::AuctionAdConfigAuctionIdPtr auction_id; |
| if (is_top_level) { |
| auction_id = mojom::blink::AuctionAdConfigAuctionId::NewMainAuction(0); |
| } else { |
| auction_id = |
| mojom::blink::AuctionAdConfigAuctionId::NewComponentAuction(nested_pos); |
| } |
| |
| if (!CopySellerFromIdlToMojo(exception_state, config, *mojo_config) || |
| !CopyDecisionLogicUrlFromIdlToMojo(context, exception_state, config, |
| *mojo_config) || |
| !CopyTrustedScoringSignalsFromIdlToMojo(context, exception_state, config, |
| *mojo_config) || |
| !CopyInterestGroupBuyersFromIdlToMojo(exception_state, config, |
| *mojo_config) || |
| !CopyAuctionSignalsFromIdlToMojo(auction_handle, auction_id.get(), |
| script_state, exception_state, config, |
| *mojo_config) || |
| !CopySellerSignalsFromIdlToMojo(auction_handle, auction_id.get(), |
| script_state, exception_state, config, |
| *mojo_config) || |
| !CopyDirectFromSellerSignalsFromIdlToMojo( |
| auction_handle, auction_id.get(), script_state, context, |
| exception_state, config, resource_fetcher, *mojo_config) || |
| !CopyPerBuyerSignalsFromIdlToMojo(auction_handle, auction_id.get(), |
| script_state, exception_state, config, |
| *mojo_config) || |
| !CopyPerBuyerTimeoutsFromIdlToMojo(auction_handle, auction_id.get(), |
| script_state, exception_state, config, |
| *mojo_config) || |
| !CopyPerBuyerCumulativeTimeoutsFromIdlToMojo( |
| auction_handle, auction_id.get(), script_state, exception_state, |
| config, *mojo_config) || |
| !CopyPerBuyerCurrenciesFromIdlToMojo(auction_handle, auction_id.get(), |
| script_state, exception_state, |
| config, *mojo_config) || |
| !CopyPerBuyerExperimentIdsFromIdlToMojo(script_state, exception_state, |
| config, *mojo_config) || |
| !CopyPerBuyerGroupLimitsFromIdlToMojo(script_state, exception_state, |
| config, *mojo_config) || |
| !CopyPerBuyerPrioritySignalsFromIdlToMojo(exception_state, config, |
| *mojo_config) || |
| !CopyAuctionReportBuyerKeysFromIdlToMojo(exception_state, config, |
| *mojo_config) || |
| !CopyAuctionReportBuyersFromIdlToMojo(exception_state, config, |
| *mojo_config) || |
| !CopyRequiredSellerSignalsFromIdlToMojo(context, exception_state, config, |
| *mojo_config) || |
| !CopyRequestedSizeFromIdlToMojo(context, exception_state, config, |
| *mojo_config)) { |
| return mojom::blink::AuctionAdConfigPtr(); |
| } |
| |
| if (config.hasSellerTimeout()) { |
| mojo_config->auction_ad_config_non_shared_params->seller_timeout = |
| base::Milliseconds(config.sellerTimeout()); |
| } |
| |
| if (config.hasSellerCurrency()) { |
| std::string seller_currency_str = config.sellerCurrency().Ascii(); |
| if (!IsValidAdCurrencyCode(seller_currency_str)) { |
| exception_state.ThrowTypeError(ErrorInvalidAuctionConfigSeller( |
| config.seller(), "sellerCurrency", config.sellerCurrency(), |
| "must be a 3-letter uppercase currency code.")); |
| return mojom::blink::AuctionAdConfigPtr(); |
| } |
| mojo_config->auction_ad_config_non_shared_params->seller_currency = |
| blink::AdCurrency::From(seller_currency_str); |
| } |
| |
| // Recursively handle component auctions, if there are any. |
| if (config.hasComponentAuctions()) { |
| if (config.componentAuctions().size() > 0 && |
| mojo_config->auction_ad_config_non_shared_params |
| ->interest_group_buyers && |
| mojo_config->auction_ad_config_non_shared_params->interest_group_buyers |
| ->size() > 0) { |
| exception_state.ThrowTypeError( |
| "Auctions may only have one of 'interestGroupBuyers' or " |
| "'componentAuctions'."); |
| return mojom::blink::AuctionAdConfigPtr(); |
| } |
| |
| for (uint32_t pos = 0; pos < config.componentAuctions().size(); ++pos) { |
| const auto& idl_component_auction = config.componentAuctions()[pos]; |
| // Component auctions may not have their own nested component auctions. |
| if (!is_top_level) { |
| exception_state.ThrowTypeError( |
| "Auctions listed in componentAuctions may not have their own " |
| "nested componentAuctions."); |
| return mojom::blink::AuctionAdConfigPtr(); |
| } |
| |
| auto mojo_component_auction = IdlAuctionConfigToMojo( |
| auction_handle, /*is_top_level=*/false, pos, script_state, context, |
| exception_state, resource_fetcher, *idl_component_auction); |
| if (!mojo_component_auction) |
| return mojom::blink::AuctionAdConfigPtr(); |
| mojo_config->auction_ad_config_non_shared_params->component_auctions |
| .emplace_back(std::move(mojo_component_auction)); |
| } |
| } |
| |
| if (config.hasSellerExperimentGroupId()) { |
| mojo_config->has_seller_experiment_group_id = true; |
| mojo_config->seller_experiment_group_id = config.sellerExperimentGroupId(); |
| } |
| |
| return mojo_config; |
| } |
| |
| // finalizeAd() validation methods |
| bool ValidateAdsObject(ExceptionState& exception_state, const Ads* ads) { |
| if (!ads || !ads->IsValid()) { |
| exception_state.ThrowTypeError( |
| "Ads used for finalizeAds() must be a valid Ads object from " |
| "navigator.createAdRequest."); |
| return false; |
| } |
| return true; |
| } |
| |
| // Modified from |
| // LocalFrame::CountUseIfFeatureWouldBeBlockedByPermissionsPolicy. |
| // |
| // Checks whether or not a policy-controlled feature would be blocked by our |
| // restricted permissions policy EnableForSelf. |
| // Under EnableForSelf policy, the features will not be available in |
| // cross-origin document unless explicitly enabled. |
| // Returns true if the frame is cross-origin relative to the top-level document, |
| // or if it is same-origin with the top level, but is embedded in any way |
| // through a cross-origin frame (A->B->A embedding). |
| bool FeatureWouldBeBlockedByRestrictedPermissionsPolicy(Navigator& navigator) { |
| const Frame* frame = navigator.DomWindow()->GetFrame(); |
| |
| // Fenced Frames block all permissions, so we shouldn't end up here because |
| // the policy is checked before this method is called. |
| DCHECK(!frame->IsInFencedFrameTree()); |
| |
| // Get the origin of the top-level document. |
| const SecurityOrigin* top_origin = |
| frame->Tree().Top().GetSecurityContext()->GetSecurityOrigin(); |
| |
| // Walk up the frame tree looking for any cross-origin embeds. Even if this |
| // frame is same-origin with the top-level, if it is embedded by a cross- |
| // origin frame (like A->B->A) it would be blocked without a permissions |
| // policy. |
| while (!frame->IsMainFrame()) { |
| if (!frame->GetSecurityContext()->GetSecurityOrigin()->CanAccess( |
| top_origin)) { |
| return true; |
| } |
| frame = frame->Tree().Parent(); |
| } |
| return false; |
| } |
| |
| void RecordCommonFledgeUseCounters(Document* document) { |
| if (!document) |
| return; |
| UseCounter::Count(document, mojom::blink::WebFeature::kFledge); |
| // Only record the ads APIs counter if enabled in that manner. |
| if (RuntimeEnabledFeatures::PrivacySandboxAdsAPIsEnabled( |
| document->GetExecutionContext())) { |
| UseCounter::Count(document, |
| mojom::blink::WebFeature::kPrivacySandboxAdsAPIs); |
| } |
| } |
| |
| // Several dictionary members are being renamed -- to maintain compatibility |
| // with existing scripts, both the new names and the old names will need to be |
| // supported for a time before support for the older names are dropped. |
| // |
| // If both names are supplied, they must have the same value (this allows |
| // scripts to be compatible with newer and older browsers). |
| // |
| // Some fields that were "required" in WebIDL also get checked -- during the |
| // rename, these fields aren't marked as required in WebIDL, but at least one |
| // of the old or new name versions must be specified. |
| bool HandleOldDictNamesJoin(AuctionAdInterestGroup* group, |
| ExceptionState& exception_state) { |
| if (group->hasAds()) { |
| for (auto& ad : group->ads()) { |
| if (ad->hasRenderUrlDeprecated()) { |
| if (ad->hasRenderURL()) { |
| if (ad->renderURL() != ad->renderUrlDeprecated()) { |
| exception_state.ThrowTypeError(ErrorRenameMismatch( |
| /*old_field_name=*/"ad renderUrl", |
| /*old_field_value=*/ad->renderUrlDeprecated(), |
| /*new_field_name=*/"ad renderURL", |
| /*new_field_value=*/ad->renderURL())); |
| return false; |
| } |
| } else { |
| ad->setRenderURL(std::move(ad->renderUrlDeprecated())); |
| } |
| } |
| if (!ad->hasRenderURL()) { |
| exception_state.ThrowTypeError(ErrorMissingRequired("ad renderURL")); |
| return false; |
| } |
| } |
| } |
| |
| if (group->hasAdComponents()) { |
| for (auto& ad : group->adComponents()) { |
| if (ad->hasRenderUrlDeprecated()) { |
| if (ad->hasRenderURL()) { |
| if (ad->renderURL() != ad->renderUrlDeprecated()) { |
| exception_state.ThrowTypeError(ErrorRenameMismatch( |
| /*old_field_name=*/"ad component renderUrl", |
| /*old_field_value=*/ad->renderUrlDeprecated(), |
| /*new_field_name=*/"ad component renderURL", |
| /*new_field_value=*/ad->renderURL())); |
| return false; |
| } |
| } else { |
| ad->setRenderURL(std::move(ad->renderUrlDeprecated())); |
| } |
| } |
| if (!ad->hasRenderURL()) { |
| exception_state.ThrowTypeError( |
| ErrorMissingRequired("ad component renderURL")); |
| return false; |
| } |
| } |
| } |
| |
| if (group->hasBiddingLogicUrlDeprecated()) { |
| if (group->hasBiddingLogicURL()) { |
| if (group->biddingLogicURL() != group->biddingLogicUrlDeprecated()) { |
| exception_state.ThrowTypeError(ErrorRenameMismatch( |
| /*old_field_name=*/"interest group biddingLogicUrl", |
| /*old_field_value=*/group->biddingLogicUrlDeprecated(), |
| /*new_field_name=*/"interest group biddingLogicURL", |
| /*new_field_value=*/group->biddingLogicURL())); |
| return false; |
| } |
| } else { |
| group->setBiddingLogicURL(std::move(group->biddingLogicUrlDeprecated())); |
| } |
| } |
| |
| if (group->hasBiddingWasmHelperUrlDeprecated()) { |
| if (group->hasBiddingWasmHelperURL()) { |
| if (group->biddingWasmHelperUrlDeprecated() != |
| group->biddingWasmHelperURL()) { |
| exception_state.ThrowTypeError(ErrorRenameMismatch( |
| /*old_field_name=*/"interest group biddingWasmHelperUrl", |
| /*old_field_value=*/group->biddingWasmHelperUrlDeprecated(), |
| /*new_field_name=*/"interest group biddingWasmHelperURL", |
| /*new_field_value=*/group->biddingWasmHelperURL())); |
| return false; |
| } |
| } else { |
| group->setBiddingWasmHelperURL( |
| std::move(group->biddingWasmHelperUrlDeprecated())); |
| } |
| } |
| |
| if (group->hasUpdateUrlDeprecated()) { |
| if (group->hasUpdateURL()) { |
| if (group->updateUrlDeprecated() != group->updateURL()) { |
| exception_state.ThrowTypeError(ErrorRenameMismatch( |
| /*old_field_name=*/"interest group updateUrl", |
| /*old_field_value=*/group->updateUrlDeprecated(), |
| /*new_field_name=*/"interest group updateURL", |
| /*new_field_value=*/group->updateURL())); |
| return false; |
| } |
| } else { |
| group->setUpdateURL(std::move(group->updateUrlDeprecated())); |
| } |
| } |
| |
| if (group->hasTrustedBiddingSignalsUrlDeprecated()) { |
| if (group->hasTrustedBiddingSignalsURL()) { |
| if (group->trustedBiddingSignalsUrlDeprecated() != |
| group->trustedBiddingSignalsURL()) { |
| exception_state.ThrowTypeError(ErrorRenameMismatch( |
| /*old_field_name=*/"interest group trustedBiddingSignalsUrl", |
| /*old_field_value=*/group->trustedBiddingSignalsUrlDeprecated(), |
| /*new_field_name=*/"interest group trustedBiddingSignalsURL", |
| /*new_field_value=*/group->trustedBiddingSignalsURL())); |
| return false; |
| } |
| } else { |
| group->setTrustedBiddingSignalsURL( |
| std::move(group->trustedBiddingSignalsUrlDeprecated())); |
| } |
| } |
| |
| return true; |
| } |
| |
| bool HandleOldDictNamesRun(AuctionAdConfig* config, |
| ExceptionState& exception_state) { |
| if (config->hasComponentAuctions()) { |
| for (AuctionAdConfig* component_auction : config->componentAuctions()) { |
| HandleOldDictNamesRun(component_auction, exception_state); |
| } |
| } |
| |
| if (config->hasDecisionLogicUrlDeprecated()) { |
| if (config->hasDecisionLogicURL()) { |
| if (config->decisionLogicURL() != config->decisionLogicUrlDeprecated()) { |
| exception_state.ThrowTypeError(ErrorRenameMismatch( |
| /*old_field_name=*/"ad auction config decisionLogicUrl", |
| /*old_field_value=*/config->decisionLogicUrlDeprecated(), |
| /*new_field_name=*/"ad auction config decisionLogicURL", |
| /*new_field_value=*/config->decisionLogicURL())); |
| return false; |
| } |
| } else { |
| config->setDecisionLogicURL(config->decisionLogicUrlDeprecated()); |
| } |
| } |
| if (!config->hasDecisionLogicURL()) { |
| exception_state.ThrowTypeError( |
| ErrorMissingRequired("ad auction config decisionLogicURL")); |
| return false; |
| } |
| |
| if (config->hasTrustedScoringSignalsUrlDeprecated()) { |
| if (config->hasTrustedScoringSignalsURL()) { |
| if (config->trustedScoringSignalsURL() != |
| config->trustedScoringSignalsUrlDeprecated()) { |
| exception_state.ThrowTypeError(ErrorRenameMismatch( |
| /*old_field_name=*/"ad auction config trustedScoringSignalsUrl", |
| /*old_field_value=*/config->trustedScoringSignalsUrlDeprecated(), |
| /*new_field_name=*/"ad auction config trustedScoringSignalsURL", |
| /*new_field_value=*/config->trustedScoringSignalsURL())); |
| return false; |
| } |
| } else { |
| config->setTrustedScoringSignalsURL( |
| config->trustedScoringSignalsUrlDeprecated()); |
| } |
| } |
| |
| return true; |
| } |
| |
| } // namespace |
| |
| NavigatorAuction::AuctionHandle::JsonResolved::JsonResolved( |
| AuctionHandle* auction_handle, |
| mojom::blink::AuctionAdConfigAuctionIdPtr auction_id, |
| mojom::blink::AuctionAdConfigField field, |
| const String& seller_name, |
| const char* field_name) |
| : auction_handle_(auction_handle), |
| auction_id_(std::move(auction_id)), |
| field_(field), |
| seller_name_(seller_name), |
| field_name_(field_name) {} |
| |
| ScriptValue NavigatorAuction::AuctionHandle::JsonResolved::Call( |
| ScriptState* script_state, |
| ScriptValue value) { |
| ExceptionState exception_state(script_state->GetIsolate(), |
| ExceptionState::kExecutionContext, |
| "NavigatorAuction", "runAdAuction"); |
| String maybe_json; |
| bool maybe_json_ok = false; |
| if (!value.IsEmpty()) { |
| v8::Local<v8::Value> v8_value = value.V8Value(); |
| if (v8_value->IsUndefined() || v8_value->IsNull()) { |
| // `maybe_json` left as the null string here; that's the blink equivalent |
| // of absl::nullopt for a string? in mojo. |
| maybe_json_ok = true; |
| } else { |
| maybe_json_ok = Jsonify(*script_state, value.V8Value(), maybe_json); |
| if (!maybe_json_ok) { |
| exception_state.ThrowTypeError( |
| ErrorInvalidAuctionConfigSellerJson(seller_name_, field_name_)); |
| } |
| } |
| } |
| |
| if (maybe_json_ok) { |
| auction_handle_->ResolvedPromiseParam(auction_id_->Clone(), field_, |
| std::move(maybe_json)); |
| } else { |
| auction_handle_->Abort(); |
| } |
| |
| return ScriptValue(); |
| } |
| |
| void NavigatorAuction::AuctionHandle::JsonResolved::Trace( |
| Visitor* visitor) const { |
| visitor->Trace(auction_handle_); |
| Callable::Trace(visitor); |
| } |
| |
| NavigatorAuction::AuctionHandle::PerBuyerSignalsResolved:: |
| PerBuyerSignalsResolved( |
| AuctionHandle* auction_handle, |
| mojom::blink::AuctionAdConfigAuctionIdPtr auction_id, |
| const String& seller_name) |
| : auction_handle_(auction_handle), |
| auction_id_(std::move(auction_id)), |
| seller_name_(seller_name) {} |
| |
| ScriptValue NavigatorAuction::AuctionHandle::PerBuyerSignalsResolved::Call( |
| ScriptState* script_state, |
| ScriptValue value) { |
| ExceptionState exception_state(script_state->GetIsolate(), |
| ExceptionState::kExecutionContext, |
| "NavigatorAuction", "runAdAuction"); |
| absl::optional<WTF::HashMap<scoped_refptr<const SecurityOrigin>, String>> |
| per_buyer_signals; |
| if (!value.IsEmpty()) { |
| v8::Local<v8::Value> v8_value = value.V8Value(); |
| if (!v8_value->IsUndefined() && !v8_value->IsNull()) { |
| per_buyer_signals = ConvertNonPromisePerBuyerSignalsFromV8ToMojo( |
| *script_state, exception_state, seller_name_, v8_value); |
| } |
| } |
| |
| if (!exception_state.HadException()) { |
| auction_handle_->ResolvedPerBuyerSignalsPromise( |
| auction_id_->Clone(), std::move(per_buyer_signals)); |
| } else { |
| auction_handle_->Abort(); |
| } |
| |
| return ScriptValue(); |
| } |
| |
| void NavigatorAuction::AuctionHandle::PerBuyerSignalsResolved::Trace( |
| Visitor* visitor) const { |
| visitor->Trace(auction_handle_); |
| Callable::Trace(visitor); |
| } |
| |
| NavigatorAuction::AuctionHandle::BuyerTimeoutsResolved::BuyerTimeoutsResolved( |
| AuctionHandle* auction_handle, |
| mojom::blink::AuctionAdConfigAuctionIdPtr auction_id, |
| mojom::blink::AuctionAdConfigBuyerTimeoutField field, |
| const String& seller_name) |
| : auction_handle_(auction_handle), |
| auction_id_(std::move(auction_id)), |
| field_(field), |
| seller_name_(seller_name) {} |
| |
| ScriptValue NavigatorAuction::AuctionHandle::BuyerTimeoutsResolved::Call( |
| ScriptState* script_state, |
| ScriptValue value) { |
| ExceptionState exception_state(script_state->GetIsolate(), |
| ExceptionState::kExecutionContext, |
| "NavigatorAuction", "runAdAuction"); |
| mojom::blink::AuctionAdConfigBuyerTimeoutsPtr buyer_timeouts; |
| if (!value.IsEmpty()) { |
| v8::Local<v8::Value> v8_value = value.V8Value(); |
| if (!v8_value->IsUndefined() && !v8_value->IsNull()) { |
| buyer_timeouts = ConvertNonPromisePerBuyerTimeoutsFromV8ToMojo( |
| *script_state, exception_state, seller_name_, v8_value, field_); |
| } |
| } |
| |
| if (!buyer_timeouts) { |
| buyer_timeouts = mojom::blink::AuctionAdConfigBuyerTimeouts::New(); |
| } |
| |
| if (!exception_state.HadException()) { |
| auction_handle_->ResolvedBuyerTimeoutsPromise(auction_id_->Clone(), field_, |
| std::move(buyer_timeouts)); |
| } else { |
| auction_handle_->Abort(); |
| } |
| |
| return ScriptValue(); |
| } |
| |
| void NavigatorAuction::AuctionHandle::BuyerTimeoutsResolved::Trace( |
| Visitor* visitor) const { |
| visitor->Trace(auction_handle_); |
| Callable::Trace(visitor); |
| } |
| |
| NavigatorAuction::AuctionHandle::BuyerCurrenciesResolved:: |
| BuyerCurrenciesResolved( |
| AuctionHandle* auction_handle, |
| mojom::blink::AuctionAdConfigAuctionIdPtr auction_id, |
| const String& seller_name) |
| : auction_handle_(auction_handle), |
| auction_id_(std::move(auction_id)), |
| seller_name_(seller_name) {} |
| |
| ScriptValue NavigatorAuction::AuctionHandle::BuyerCurrenciesResolved::Call( |
| ScriptState* script_state, |
| ScriptValue value) { |
| ExceptionState exception_state(script_state->GetIsolate(), |
| ExceptionState::kExecutionContext, |
| "NavigatorAuction", "runAdAuction"); |
| mojom::blink::AuctionAdConfigBuyerCurrenciesPtr buyer_currencies; |
| if (!value.IsEmpty()) { |
| v8::Local<v8::Value> v8_value = value.V8Value(); |
| if (!v8_value->IsUndefined() && !v8_value->IsNull()) { |
| buyer_currencies = ConvertNonPromisePerBuyerCurrenciesFromV8ToMojo( |
| *script_state, exception_state, seller_name_, v8_value); |
| } |
| } |
| |
| if (!buyer_currencies) { |
| buyer_currencies = mojom::blink::AuctionAdConfigBuyerCurrencies::New(); |
| } |
| |
| if (!exception_state.HadException()) { |
| auction_handle_->ResolvedBuyerCurrencies(auction_id_->Clone(), |
| std::move(buyer_currencies)); |
| } else { |
| auction_handle_->Abort(); |
| } |
| |
| return ScriptValue(); |
| } |
| |
| void NavigatorAuction::AuctionHandle::BuyerCurrenciesResolved::Trace( |
| Visitor* visitor) const { |
| visitor->Trace(auction_handle_); |
| Callable::Trace(visitor); |
| } |
| |
| NavigatorAuction::AuctionHandle::DirectFromSellerSignalsResolved:: |
| DirectFromSellerSignalsResolved( |
| AuctionHandle* auction_handle, |
| mojom::blink::AuctionAdConfigAuctionIdPtr auction_id, |
| const String& seller_name, |
| const scoped_refptr<const SecurityOrigin>& seller_origin, |
| const absl::optional<Vector<scoped_refptr<const SecurityOrigin>>>& |
| interest_group_buyers) |
| : auction_handle_(auction_handle), |
| auction_id_(std::move(auction_id)), |
| seller_name_(seller_name), |
| seller_origin_(seller_origin), |
| interest_group_buyers_(interest_group_buyers) {} |
| |
| ScriptValue |
| NavigatorAuction::AuctionHandle::DirectFromSellerSignalsResolved::Call( |
| ScriptState* script_state, |
| ScriptValue value) { |
| ExecutionContext* context = ExecutionContext::From(script_state); |
| if (!context) { |
| return ScriptValue(); |
| } |
| |
| ExceptionState exception_state(script_state->GetIsolate(), |
| ExceptionState::kExecutionContext, |
| "NavigatorAuction", "runAdAuction"); |
| mojom::blink::DirectFromSellerSignalsPtr direct_from_seller_signals; |
| if (!value.IsEmpty()) { |
| v8::Local<v8::Value> v8_value = value.V8Value(); |
| if (!v8_value->IsUndefined() && !v8_value->IsNull()) { |
| direct_from_seller_signals = ConvertDirectFromSellerSignalsFromV8ToMojo( |
| *script_state, *context, exception_state, *context->Fetcher(), |
| seller_name_, *seller_origin_, interest_group_buyers_, v8_value); |
| } |
| } |
| |
| if (!exception_state.HadException()) { |
| auction_handle_->ResolvedDirectFromSellerSignalsPromise( |
| auction_id_->Clone(), std::move(direct_from_seller_signals)); |
| } else { |
| auction_handle_->Abort(); |
| } |
| |
| return ScriptValue(); |
| } |
| |
| void NavigatorAuction::AuctionHandle::DirectFromSellerSignalsResolved::Trace( |
| Visitor* visitor) const { |
| visitor->Trace(auction_handle_); |
| Callable::Trace(visitor); |
| } |
| |
| NavigatorAuction::AuctionHandle::ResolveToConfigResolved:: |
| ResolveToConfigResolved(AuctionHandle* auction_handle) |
| : auction_handle_(auction_handle) {} |
| ScriptValue NavigatorAuction::AuctionHandle::ResolveToConfigResolved::Call( |
| ScriptState* script_state, |
| ScriptValue value) { |
| v8::Local<v8::Value> v8_value = value.V8Value(); |
| |
| ExecutionContext* context = ExecutionContext::From(script_state); |
| if (!context) { |
| return ScriptValue(); |
| } |
| |
| if (!v8_value->IsBoolean()) { |
| auction_handle_->SetResolveToConfig(false); |
| } else { |
| auction_handle_->SetResolveToConfig( |
| v8_value->BooleanValue(script_state->GetIsolate())); |
| } |
| |
| auction_handle_->MaybeResolveAuction(); |
| return ScriptValue(); |
| } |
| |
| void NavigatorAuction::AuctionHandle::ResolveToConfigResolved::Trace( |
| Visitor* visitor) const { |
| visitor->Trace(auction_handle_); |
| Callable::Trace(visitor); |
| } |
| |
| NavigatorAuction::AuctionHandle::Rejected::Rejected( |
| AuctionHandle* auction_handle) |
| : auction_handle_(auction_handle) {} |
| |
| ScriptValue NavigatorAuction::AuctionHandle::Rejected::Call(ScriptState*, |
| ScriptValue) { |
| // Abort the auction if any input promise rejects |
| auction_handle_->Abort(); |
| return ScriptValue(); |
| } |
| |
| void NavigatorAuction::AuctionHandle::Rejected::Trace(Visitor* visitor) const { |
| visitor->Trace(auction_handle_); |
| Callable::Trace(visitor); |
| } |
| |
| NavigatorAuction::NavigatorAuction(Navigator& navigator) |
| : Supplement(navigator), |
| queued_cross_site_joins_(kMaxActiveCrossSiteJoins, |
| WTF::BindRepeating(&NavigatorAuction::StartJoin, |
| WrapWeakPersistent(this))), |
| queued_cross_site_leaves_( |
| kMaxActiveCrossSiteLeaves, |
| WTF::BindRepeating(&NavigatorAuction::StartLeave, |
| WrapWeakPersistent(this))), |
| ad_auction_service_(navigator.GetExecutionContext()) { |
| navigator.GetExecutionContext()->GetBrowserInterfaceBroker().GetInterface( |
| ad_auction_service_.BindNewPipeAndPassReceiver( |
| navigator.GetExecutionContext()->GetTaskRunner( |
| TaskType::kMiscPlatformAPI))); |
| } |
| |
| NavigatorAuction& NavigatorAuction::From(ExecutionContext* context, |
| Navigator& navigator) { |
| NavigatorAuction* supplement = |
| Supplement<Navigator>::From<NavigatorAuction>(navigator); |
| if (!supplement) { |
| supplement = MakeGarbageCollected<NavigatorAuction>(navigator); |
| ProvideTo(navigator, supplement); |
| } |
| return *supplement; |
| } |
| |
| const char NavigatorAuction::kSupplementName[] = "NavigatorAuction"; |
| |
| ScriptPromise NavigatorAuction::joinAdInterestGroup( |
| ScriptState* script_state, |
| AuctionAdInterestGroup* mutable_group, |
| double duration_seconds, |
| ExceptionState& exception_state) { |
| const ExecutionContext* context = ExecutionContext::From(script_state); |
| |
| // TODO(crbug.com/1441988): Remove this code after rename is complete. |
| if (!HandleOldDictNamesJoin(mutable_group, exception_state)) { |
| return ScriptPromise(); |
| } |
| const AuctionAdInterestGroup* group = mutable_group; |
| |
| auto mojo_group = mojom::blink::InterestGroup::New(); |
| mojo_group->expiry = base::Time::Now() + base::Seconds(duration_seconds); |
| if (!CopyOwnerFromIdlToMojo(*context, exception_state, *group, *mojo_group)) |
| return ScriptPromise(); |
| mojo_group->name = group->name(); |
| mojo_group->priority = (group->hasPriority()) ? group->priority() : 0.0; |
| |
| mojo_group->enable_bidding_signals_prioritization = |
| group->hasEnableBiddingSignalsPrioritization() |
| ? group->enableBiddingSignalsPrioritization() |
| : false; |
| if (group->hasPriorityVector()) { |
| mojo_group->priority_vector = |
| ConvertSparseVectorIdlToMojo(group->priorityVector()); |
| } |
| if (group->hasPrioritySignalsOverrides()) { |
| mojo_group->priority_signals_overrides = |
| ConvertSparseVectorIdlToMojo(group->prioritySignalsOverrides()); |
| } |
| |
| if (!CopySellerCapabilitiesFromIdlToMojo(*context, exception_state, *group, |
| *mojo_group)) { |
| return ScriptPromise(); |
| } |
| if (!CopyExecutionModeFromIdlToMojo(*context, exception_state, *group, |
| *mojo_group)) { |
| return ScriptPromise(); |
| } |
| if (!CopyBiddingLogicUrlFromIdlToMojo(*context, exception_state, *group, |
| *mojo_group)) { |
| return ScriptPromise(); |
| } |
| if (!CopyWasmHelperUrlFromIdlToMojo(*context, exception_state, *group, |
| *mojo_group)) { |
| return ScriptPromise(); |
| } |
| if (!CopyUpdateUrlFromIdlToMojo(*context, exception_state, *group, |
| *mojo_group)) { |
| return ScriptPromise(); |
| } |
| if (!CopyTrustedBiddingSignalsUrlFromIdlToMojo(*context, exception_state, |
| *group, *mojo_group)) { |
| return ScriptPromise(); |
| } |
| if (!CopyTrustedBiddingSignalsKeysFromIdlToMojo(*group, *mojo_group)) |
| return ScriptPromise(); |
| if (!CopyUserBiddingSignalsFromIdlToMojo(*script_state, exception_state, |
| *group, *mojo_group)) { |
| return ScriptPromise(); |
| } |
| if (!CopyAdsFromIdlToMojo(*context, *script_state, exception_state, *group, |
| *mojo_group)) { |
| return ScriptPromise(); |
| } |
| if (!CopyAdComponentsFromIdlToMojo(*context, *script_state, exception_state, |
| *group, *mojo_group)) { |
| return ScriptPromise(); |
| } |
| if (!CopyAdSizesFromIdlToMojo(*context, *script_state, exception_state, |
| *group, *mojo_group)) { |
| return ScriptPromise(); |
| } |
| if (!CopySizeGroupsFromIdlToMojo(*context, *script_state, exception_state, |
| *group, *mojo_group)) { |
| return ScriptPromise(); |
| } |
| |
| String error_field_name; |
| String error_field_value; |
| String error; |
| if (!ValidateBlinkInterestGroup(*mojo_group, error_field_name, |
| error_field_value, error)) { |
| exception_state.ThrowTypeError(ErrorInvalidInterestGroup( |
| *group, error_field_name, error_field_value, error)); |
| return ScriptPromise(); |
| } |
| |
| bool is_cross_origin = |
| !context->GetSecurityOrigin()->IsSameOriginWith(mojo_group->owner.get()); |
| |
| auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>( |
| script_state, exception_state.GetContext()); |
| ScriptPromise promise = resolver->Promise(); |
| mojom::blink::AdAuctionService::JoinInterestGroupCallback callback = |
| resolver->WrapCallbackInScriptScope( |
| WTF::BindOnce(&NavigatorAuction::JoinComplete, |
| WrapWeakPersistent(this), is_cross_origin)); |
| |
| PendingJoin pending_join{std::move(mojo_group), std::move(callback)}; |
| if (is_cross_origin) { |
| queued_cross_site_joins_.Enqueue(std::move(pending_join)); |
| } else { |
| StartJoin(std::move(pending_join)); |
| } |
| |
| return promise; |
| } |
| |
| /* static */ |
| ScriptPromise NavigatorAuction::joinAdInterestGroup( |
| ScriptState* script_state, |
| Navigator& navigator, |
| AuctionAdInterestGroup* group, |
| double duration_seconds, |
| ExceptionState& exception_state) { |
| if (!navigator.DomWindow()) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kInvalidAccessError, |
| "The document has no window associated."); |
| return ScriptPromise(); |
| } |
| RecordCommonFledgeUseCounters(navigator.DomWindow()->document()); |
| const ExecutionContext* context = ExecutionContext::From(script_state); |
| if (!context->IsFeatureEnabled( |
| blink::mojom::PermissionsPolicyFeature::kJoinAdInterestGroup)) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kNotAllowedError, |
| "Feature join-ad-interest-group is not enabled by Permissions Policy"); |
| return ScriptPromise(); |
| } |
| if (!base::FeatureList::IsEnabled( |
| blink::features::kAdInterestGroupAPIRestrictedPolicyByDefault) && |
| FeatureWouldBeBlockedByRestrictedPermissionsPolicy(navigator)) { |
| AddWarningMessageToConsole(script_state, "join-ad-interest-group", |
| "joinAdInterestGroup"); |
| } |
| |
| return From(ExecutionContext::From(script_state), navigator) |
| .joinAdInterestGroup(script_state, group, duration_seconds, |
| exception_state); |
| } |
| |
| ScriptPromise NavigatorAuction::leaveAdInterestGroup( |
| ScriptState* script_state, |
| const AuctionAdInterestGroupKey* group_key, |
| ExceptionState& exception_state) { |
| scoped_refptr<const SecurityOrigin> owner = ParseOrigin(group_key->owner()); |
| if (!owner) { |
| exception_state.ThrowTypeError("owner '" + group_key->owner() + |
| "' for AuctionAdInterestGroup with name '" + |
| group_key->name() + |
| "' must be a valid https origin."); |
| return ScriptPromise(); |
| } |
| |
| bool is_cross_origin = !ExecutionContext::From(script_state) |
| ->GetSecurityOrigin() |
| ->IsSameOriginWith(owner.get()); |
| |
| auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>( |
| script_state, exception_state.GetContext()); |
| ScriptPromise promise = resolver->Promise(); |
| mojom::blink::AdAuctionService::LeaveInterestGroupCallback callback = |
| resolver->WrapCallbackInScriptScope( |
| WTF::BindOnce(&NavigatorAuction::LeaveComplete, |
| WrapWeakPersistent(this), is_cross_origin)); |
| |
| PendingLeave pending_leave{std::move(owner), std::move(group_key->name()), |
| std::move(callback)}; |
| if (is_cross_origin) { |
| queued_cross_site_leaves_.Enqueue(std::move(pending_leave)); |
| } else { |
| StartLeave(std::move(pending_leave)); |
| } |
| |
| return promise; |
| } |
| |
| ScriptPromise NavigatorAuction::leaveAdInterestGroupForDocument( |
| ScriptState* script_state, |
| ExceptionState& exception_state) { |
| LocalDOMWindow* window = GetSupplementable()->DomWindow(); |
| |
| if (!window) { |
| exception_state.ThrowSecurityError( |
| "May not leaveAdInterestGroup from a Document that is not fully " |
| "active"); |
| return ScriptPromise(); |
| } |
| if (!window->GetFrame()->IsInFencedFrameTree()) { |
| exception_state.ThrowTypeError( |
| "owner and name are required outside of a fenced frame."); |
| return ScriptPromise(); |
| } |
| // The renderer does not have enough information to verify that this document |
| // is the result of a FLEDGE auction. The browser will silently ignore |
| // this request if this document is not the result of a FLEDGE auction. |
| ad_auction_service_->LeaveInterestGroupForDocument(); |
| |
| // Return resolved promise. The browser-side code doesn't do anything |
| // meaningful in this case (no .well-known fetches), and if it ever does do |
| // them, likely don't want to expose timing information to the fenced frame, |
| // anyways. |
| return ScriptPromise::CastUndefined(script_state); |
| } |
| |
| /* static */ |
| ScriptPromise NavigatorAuction::leaveAdInterestGroup( |
| ScriptState* script_state, |
| Navigator& navigator, |
| const AuctionAdInterestGroupKey* group_key, |
| ExceptionState& exception_state) { |
| if (!navigator.DomWindow()) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kInvalidAccessError, |
| "The document has no window associated."); |
| return ScriptPromise(); |
| } |
| RecordCommonFledgeUseCounters(navigator.DomWindow()->document()); |
| ExecutionContext* context = ExecutionContext::From(script_state); |
| if (!context->IsFeatureEnabled( |
| blink::mojom::PermissionsPolicyFeature::kJoinAdInterestGroup)) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kNotAllowedError, |
| "Feature join-ad-interest-group is not enabled by Permissions Policy"); |
| return ScriptPromise(); |
| } |
| if (!base::FeatureList::IsEnabled( |
| blink::features::kAdInterestGroupAPIRestrictedPolicyByDefault) && |
| FeatureWouldBeBlockedByRestrictedPermissionsPolicy(navigator)) { |
| AddWarningMessageToConsole(script_state, "join-ad-interest-group", |
| "leaveAdInterestGroup"); |
| } |
| |
| return From(context, navigator) |
| .leaveAdInterestGroup(script_state, group_key, exception_state); |
| } |
| |
| /* static */ |
| ScriptPromise NavigatorAuction::leaveAdInterestGroup( |
| ScriptState* script_state, |
| Navigator& navigator, |
| ExceptionState& exception_state) { |
| if (!navigator.DomWindow()) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kInvalidAccessError, |
| "The document has no window associated."); |
| return ScriptPromise(); |
| } |
| ExecutionContext* context = ExecutionContext::From(script_state); |
| // According to the spec, implicit leave bypasses permission policy. |
| return From(context, navigator) |
| .leaveAdInterestGroupForDocument(script_state, exception_state); |
| } |
| |
| void NavigatorAuction::updateAdInterestGroups() { |
| ad_auction_service_->UpdateAdInterestGroups(); |
| } |
| |
| /* static */ |
| void NavigatorAuction::updateAdInterestGroups(ScriptState* script_state, |
| Navigator& navigator, |
| ExceptionState& exception_state) { |
| if (!navigator.DomWindow()) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kInvalidAccessError, |
| "The document has no window associated."); |
| return; |
| } |
| RecordCommonFledgeUseCounters(navigator.DomWindow()->document()); |
| ExecutionContext* context = ExecutionContext::From(script_state); |
| if (!context->IsFeatureEnabled( |
| blink::mojom::PermissionsPolicyFeature::kJoinAdInterestGroup)) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kNotAllowedError, |
| "Feature join-ad-interest-group is not enabled by Permissions Policy"); |
| return; |
| } |
| if (!base::FeatureList::IsEnabled( |
| blink::features::kAdInterestGroupAPIRestrictedPolicyByDefault) && |
| FeatureWouldBeBlockedByRestrictedPermissionsPolicy(navigator)) { |
| AddWarningMessageToConsole(script_state, "join-ad-interest-group", |
| "updateAdInterestGroups"); |
| } |
| |
| return From(context, navigator).updateAdInterestGroups(); |
| } |
| |
| ScriptPromise NavigatorAuction::runAdAuction(ScriptState* script_state, |
| AuctionAdConfig* mutable_config, |
| ExceptionState& exception_state) { |
| ExecutionContext* context = ExecutionContext::From(script_state); |
| |
| if (!HandleOldDictNamesRun(mutable_config, exception_state)) { |
| return ScriptPromise(); |
| } |
| const AuctionAdConfig* config = mutable_config; |
| |
| mojo::PendingReceiver<mojom::blink::AbortableAdAuction> abort_receiver; |
| auto* auction_handle = MakeGarbageCollected<AuctionHandle>( |
| context, abort_receiver.InitWithNewPipeAndPassRemote()); |
| auto mojo_config = IdlAuctionConfigToMojo( |
| auction_handle, |
| /*is_top_level=*/true, /*nested_pos=*/0, *script_state, *context, |
| exception_state, |
| /*resource_fetcher=*/ |
| *GetSupplementable()->DomWindow()->document()->Fetcher(), *config); |
| if (!mojo_config) |
| return ScriptPromise(); |
| |
| auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>( |
| script_state, exception_state.GetContext()); |
| ScriptPromise promise = resolver->Promise(); |
| std::unique_ptr<ScopedAbortState> scoped_abort_state = nullptr; |
| if (auto* signal = config->getSignalOr(nullptr)) { |
| if (signal->aborted()) { |
| resolver->Reject(signal->reason(script_state)); |
| return promise; |
| } |
| auto* abort_handle = signal->AddAlgorithm(auction_handle); |
| scoped_abort_state = |
| std::make_unique<ScopedAbortState>(signal, abort_handle); |
| } |
| |
| if (config->hasResolveToConfig() && |
| config->resolveToConfig().V8Value()->IsPromise()) { |
| ScriptPromise resolve_to_config_promise( |
| script_state, config->resolveToConfig().V8Value()); |
| auction_handle->AttachPromiseHandler( |
| *script_state, resolve_to_config_promise, |
| MakeGarbageCollected< |
| NavigatorAuction::AuctionHandle::ResolveToConfigResolved>( |
| auction_handle)); |
| } else { |
| bool resolve_val = false; |
| |
| if (config->hasResolveToConfig() && |
| config->resolveToConfig().V8Value()->IsBoolean()) { |
| resolve_val = config->resolveToConfig().V8Value()->BooleanValue( |
| script_state->GetIsolate()); |
| } |
| |
| auction_handle->SetResolveToConfig(resolve_val); |
| } |
| |
| ad_auction_service_->RunAdAuction( |
| std::move(mojo_config), std::move(abort_receiver), |
| WTF::BindOnce(&NavigatorAuction::AuctionHandle::AuctionComplete, |
| WrapPersistent(auction_handle), WrapPersistent(resolver), |
| std::move(scoped_abort_state))); |
| return promise; |
| } |
| |
| /* static */ |
| ScriptPromise NavigatorAuction::runAdAuction(ScriptState* script_state, |
| Navigator& navigator, |
| AuctionAdConfig* config, |
| ExceptionState& exception_state) { |
| if (!navigator.DomWindow()) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kInvalidAccessError, |
| "The document has no window associated."); |
| return ScriptPromise(); |
| } |
| RecordCommonFledgeUseCounters(navigator.DomWindow()->document()); |
| const ExecutionContext* context = ExecutionContext::From(script_state); |
| if (!context->IsFeatureEnabled( |
| blink::mojom::PermissionsPolicyFeature::kRunAdAuction)) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kNotAllowedError, |
| "Feature run-ad-auction is not enabled by Permissions Policy"); |
| return ScriptPromise(); |
| } |
| if (!base::FeatureList::IsEnabled( |
| blink::features::kAdInterestGroupAPIRestrictedPolicyByDefault) && |
| FeatureWouldBeBlockedByRestrictedPermissionsPolicy(navigator)) { |
| AddWarningMessageToConsole(script_state, "run-ad-auction", "runAdAuction"); |
| } |
| |
| return From(ExecutionContext::From(script_state), navigator) |
| .runAdAuction(script_state, config, exception_state); |
| } |
| |
| /* static */ |
| Vector<String> NavigatorAuction::adAuctionComponents( |
| ScriptState* script_state, |
| Navigator& navigator, |
| uint16_t num_ad_components, |
| ExceptionState& exception_state) { |
| Vector<String> out; |
| if (!navigator.DomWindow()) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kInvalidAccessError, |
| "The document has no window associated."); |
| return out; |
| } |
| RecordCommonFledgeUseCounters(navigator.DomWindow()->document()); |
| const auto& ad_auction_components = |
| navigator.DomWindow()->document()->Loader()->AdAuctionComponents(); |
| if (!ad_auction_components) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError, |
| "This frame was not loaded with the " |
| "result of an interest group auction."); |
| return out; |
| } |
| |
| // Clamp the number of ad components at blink::kMaxAdAuctionAdComponents. |
| if (num_ad_components > |
| static_cast<int16_t>(blink::kMaxAdAuctionAdComponents)) { |
| num_ad_components = blink::kMaxAdAuctionAdComponents; |
| } |
| |
| DCHECK_EQ(kMaxAdAuctionAdComponents, ad_auction_components->size()); |
| |
| for (int i = 0; i < num_ad_components; ++i) { |
| out.push_back((*ad_auction_components)[i].GetString()); |
| } |
| return out; |
| } |
| |
| ScriptPromise NavigatorAuction::deprecatedURNToURL( |
| ScriptState* script_state, |
| const String& uuid_url_string, |
| bool send_reports, |
| ExceptionState& exception_state) { |
| KURL uuid_url(uuid_url_string); |
| if (!blink::IsValidUrnUuidURL(GURL(uuid_url))) { |
| exception_state.ThrowTypeError("Passed URL must be a valid URN URL."); |
| return ScriptPromise(); |
| } |
| |
| auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>( |
| script_state, exception_state.GetContext()); |
| ScriptPromise promise = resolver->Promise(); |
| ad_auction_service_->DeprecatedGetURLFromURN( |
| std::move(uuid_url), send_reports, |
| resolver->WrapCallbackInScriptScope(WTF::BindOnce( |
| &NavigatorAuction::GetURLFromURNComplete, WrapPersistent(this)))); |
| return promise; |
| } |
| |
| ScriptPromise NavigatorAuction::deprecatedURNToURL( |
| ScriptState* script_state, |
| Navigator& navigator, |
| const V8UnionFencedFrameConfigOrUSVString* urn_or_config, |
| bool send_reports, |
| ExceptionState& exception_state) { |
| if (!navigator.DomWindow()) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kInvalidAccessError, |
| "The document has no window associated."); |
| return ScriptPromise(); |
| } |
| String uuid_url_string; |
| switch (urn_or_config->GetContentType()) { |
| case V8UnionFencedFrameConfigOrUSVString::ContentType::kUSVString: |
| uuid_url_string = urn_or_config->GetAsUSVString(); |
| break; |
| case V8UnionFencedFrameConfigOrUSVString::ContentType::kFencedFrameConfig: |
| absl::optional<KURL> uuid_url_opt = |
| urn_or_config->GetAsFencedFrameConfig()->urn_uuid( |
| base::PassKey<NavigatorAuction>()); |
| if (!uuid_url_opt.has_value()) { |
| exception_state.ThrowTypeError("Passed config must have a mapped URL."); |
| return ScriptPromise(); |
| } |
| uuid_url_string = uuid_url_opt->GetString(); |
| break; |
| } |
| return From(ExecutionContext::From(script_state), navigator) |
| .deprecatedURNToURL(script_state, uuid_url_string, send_reports, |
| exception_state); |
| } |
| |
| ScriptPromise NavigatorAuction::deprecatedReplaceInURN( |
| ScriptState* script_state, |
| const String& uuid_url_string, |
| const Vector<std::pair<String, String>>& replacements, |
| ExceptionState& exception_state) { |
| KURL uuid_url(uuid_url_string); |
| if (!blink::IsValidUrnUuidURL(GURL(uuid_url))) { |
| exception_state.ThrowTypeError("Passed URL must be a valid URN URL."); |
| return ScriptPromise(); |
| } |
| Vector<mojom::blink::AdKeywordReplacementPtr> replacements_list; |
| for (const auto& replacement : replacements) { |
| if (!(replacement.first.StartsWith("${") && |
| replacement.first.EndsWith("}")) && |
| !(replacement.first.StartsWith("%%") && |
| replacement.first.EndsWith("%%"))) { |
| exception_state.ThrowTypeError( |
| "Replacements must be of the form '${...}' or '%%...%%'"); |
| return ScriptPromise(); |
| } |
| replacements_list.push_back(mojom::blink::AdKeywordReplacement::New( |
| replacement.first, replacement.second)); |
| } |
| auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>( |
| script_state, exception_state.GetContext()); |
| ScriptPromise promise = resolver->Promise(); |
| ad_auction_service_->DeprecatedReplaceInURN( |
| std::move(uuid_url), std::move(replacements_list), |
| resolver->WrapCallbackInScriptScope(WTF::BindOnce( |
| &NavigatorAuction::ReplaceInURNComplete, WrapPersistent(this)))); |
| return promise; |
| } |
| |
| ScriptPromise NavigatorAuction::deprecatedReplaceInURN( |
| ScriptState* script_state, |
| Navigator& navigator, |
| const V8UnionFencedFrameConfigOrUSVString* urn_or_config, |
| const Vector<std::pair<String, String>>& replacements, |
| ExceptionState& exception_state) { |
| if (!navigator.DomWindow()) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kInvalidAccessError, |
| "The document has no window associated."); |
| return ScriptPromise(); |
| } |
| String uuid_url_string; |
| switch (urn_or_config->GetContentType()) { |
| case V8UnionFencedFrameConfigOrUSVString::ContentType::kUSVString: |
| uuid_url_string = urn_or_config->GetAsUSVString(); |
| break; |
| case V8UnionFencedFrameConfigOrUSVString::ContentType::kFencedFrameConfig: |
| absl::optional<KURL> uuid_url_opt = |
| urn_or_config->GetAsFencedFrameConfig()->urn_uuid( |
| base::PassKey<NavigatorAuction>()); |
| if (!uuid_url_opt.has_value()) { |
| exception_state.ThrowTypeError("Passed config must have a mapped URL."); |
| return ScriptPromise(); |
| } |
| uuid_url_string = uuid_url_opt->GetString(); |
| break; |
| } |
| return From(ExecutionContext::From(script_state), navigator) |
| .deprecatedReplaceInURN(script_state, uuid_url_string, |
| std::move(replacements), exception_state); |
| } |
| |
| ScriptPromise NavigatorAuction::createAdRequest( |
| ScriptState* script_state, |
| const AdRequestConfig* config, |
| ExceptionState& exception_state) { |
| const ExecutionContext* context = ExecutionContext::From(script_state); |
| auto mojo_config = mojom::blink::AdRequestConfig::New(); |
| |
| if (!CopyAdRequestUrlFromIdlToMojo(*context, exception_state, *config, |
| *mojo_config)) |
| return ScriptPromise(); |
| |
| if (!CopyAdPropertiesFromIdlToMojo(*context, exception_state, *config, |
| *mojo_config)) |
| return ScriptPromise(); |
| |
| if (config->hasPublisherCode()) { |
| mojo_config->publisher_code = config->publisherCode(); |
| } |
| |
| if (!CopyTargetingFromIdlToMojo(*context, exception_state, *config, |
| *mojo_config)) |
| return ScriptPromise(); |
| |
| if (!CopyAdSignalsFromIdlToMojo(*context, exception_state, *config, |
| *mojo_config)) |
| return ScriptPromise(); |
| |
| if (!CopyFallbackSourceFromIdlToMojo(*context, exception_state, *config, |
| *mojo_config)) |
| return ScriptPromise(); |
| |
| auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>( |
| script_state, exception_state.GetContext()); |
| ScriptPromise promise = resolver->Promise(); |
| ad_auction_service_->CreateAdRequest( |
| std::move(mojo_config), |
| resolver->WrapCallbackInScriptScope(WTF::BindOnce( |
| &NavigatorAuction::AdsRequested, WrapPersistent(this)))); |
| return promise; |
| } |
| |
| /* static */ |
| ScriptPromise NavigatorAuction::createAdRequest( |
| ScriptState* script_state, |
| Navigator& navigator, |
| const AdRequestConfig* config, |
| ExceptionState& exception_state) { |
| if (!navigator.DomWindow()) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kInvalidAccessError, |
| "The document has no window associated."); |
| return ScriptPromise(); |
| } |
| return From(ExecutionContext::From(script_state), navigator) |
| .createAdRequest(script_state, config, exception_state); |
| } |
| |
| void NavigatorAuction::AdsRequested(ScriptPromiseResolver* resolver, |
| const WTF::String&) { |
| // TODO(https://crbug.com/1249186): Add full impl of methods. |
| resolver->Reject(V8ThrowDOMException::CreateOrEmpty( |
| resolver->GetScriptState()->GetIsolate(), |
| DOMExceptionCode::kNotSupportedError, |
| "createAdRequest API not yet implemented")); |
| } |
| |
| ScriptPromise NavigatorAuction::finalizeAd(ScriptState* script_state, |
| const Ads* ads, |
| const AuctionAdConfig* config, |
| ExceptionState& exception_state) { |
| const ExecutionContext* context = ExecutionContext::From(script_state); |
| auto mojo_config = mojom::blink::AuctionAdConfig::New(); |
| |
| // For finalizing an Ad PARAKEET only really cares about the decisionLogicURL, |
| // auctionSignals, sellerSignals, and perBuyerSignals. Also need seller, since |
| // it's used to validate the decision logic URL. We can ignore |
| // copying/validating other fields on AuctionAdConfig. |
| if (!CopySellerFromIdlToMojo(exception_state, *config, *mojo_config) || |
| !CopyDecisionLogicUrlFromIdlToMojo(*context, exception_state, *config, |
| *mojo_config) || |
| !CopyAuctionSignalsFromIdlToMojo(/*auction_handle=*/nullptr, |
| /*auction_id=*/nullptr, *script_state, |
| exception_state, *config, |
| *mojo_config) || |
| !CopySellerSignalsFromIdlToMojo(/*auction_handle=*/nullptr, |
| /*auction_id=*/nullptr, *script_state, |
| exception_state, *config, *mojo_config) || |
| !CopyPerBuyerSignalsFromIdlToMojo(/*auction_handle=*/nullptr, |
| /*auction_id=*/nullptr, *script_state, |
| exception_state, *config, |
| *mojo_config)) { |
| return ScriptPromise(); |
| } |
| |
| if (!ValidateAdsObject(exception_state, ads)) |
| return ScriptPromise(); |
| |
| auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>( |
| script_state, exception_state.GetContext()); |
| ScriptPromise promise = resolver->Promise(); |
| ad_auction_service_->FinalizeAd( |
| ads->GetGuid(), std::move(mojo_config), |
| resolver->WrapCallbackInScriptScope(WTF::BindOnce( |
| &NavigatorAuction::FinalizeAdComplete, WrapPersistent(this)))); |
| return promise; |
| } |
| |
| /* static */ |
| ScriptPromise NavigatorAuction::finalizeAd(ScriptState* script_state, |
| Navigator& navigator, |
| const Ads* ads, |
| const AuctionAdConfig* config, |
| ExceptionState& exception_state) { |
| if (!navigator.DomWindow()) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kInvalidAccessError, |
| "The document has no window associated."); |
| return ScriptPromise(); |
| } |
| return From(ExecutionContext::From(script_state), navigator) |
| .finalizeAd(script_state, ads, config, exception_state); |
| } |
| |
| void NavigatorAuction::FinalizeAdComplete( |
| ScriptPromiseResolver* resolver, |
| const absl::optional<KURL>& creative_url) { |
| if (creative_url) { |
| resolver->Resolve(creative_url); |
| } else { |
| // TODO(https://crbug.com/1249186): Add full impl of methods. |
| resolver->Reject(V8ThrowDOMException::CreateOrEmpty( |
| resolver->GetScriptState()->GetIsolate(), |
| DOMExceptionCode::kNotSupportedError, |
| "finalizeAd API not yet implemented")); |
| } |
| } |
| |
| void NavigatorAuction::StartJoin(PendingJoin&& pending_join) { |
| ad_auction_service_->JoinInterestGroup(std::move(pending_join.interest_group), |
| std::move(pending_join.callback)); |
| } |
| |
| void NavigatorAuction::JoinComplete(bool is_cross_origin, |
| ScriptPromiseResolver* resolver, |
| bool failed_well_known_check) { |
| if (is_cross_origin) |
| queued_cross_site_joins_.OnComplete(); |
| |
| if (failed_well_known_check) { |
| resolver->Reject(V8ThrowDOMException::CreateOrEmpty( |
| resolver->GetScriptState()->GetIsolate(), |
| DOMExceptionCode::kNotAllowedError, |
| "Permission to join interest group denied.")); |
| return; |
| } |
| resolver->Resolve(); |
| } |
| |
| void NavigatorAuction::StartLeave(PendingLeave&& pending_leave) { |
| ad_auction_service_->LeaveInterestGroup(pending_leave.owner, |
| pending_leave.name, |
| std::move(pending_leave.callback)); |
| } |
| |
| void NavigatorAuction::LeaveComplete(bool is_cross_origin, |
| ScriptPromiseResolver* resolver, |
| bool failed_well_known_check) { |
| if (is_cross_origin) |
| queued_cross_site_leaves_.OnComplete(); |
| |
| if (failed_well_known_check) { |
| resolver->Reject(V8ThrowDOMException::CreateOrEmpty( |
| resolver->GetScriptState()->GetIsolate(), |
| DOMExceptionCode::kNotAllowedError, |
| "Permission to leave interest group denied.")); |
| return; |
| } |
| resolver->Resolve(); |
| } |
| |
| void NavigatorAuction::AuctionHandle::AuctionComplete( |
| ScriptPromiseResolver* resolver, |
| std::unique_ptr<ScopedAbortState> scoped_abort_state, |
| bool manually_aborted, |
| const absl::optional<FencedFrame::RedactedFencedFrameConfig>& |
| result_config) { |
| if (!resolver->GetExecutionContext() || |
| resolver->GetExecutionContext()->IsContextDestroyed()) |
| return; |
| AbortSignal* abort_signal = |
| scoped_abort_state ? scoped_abort_state->Signal() : nullptr; |
| ScriptState* script_state = resolver->GetScriptState(); |
| ScriptState::Scope script_state_scope(script_state); |
| if (manually_aborted) { |
| if (abort_signal && abort_signal->aborted()) { |
| resolver->Reject(abort_signal->reason(script_state)); |
| } else { |
| // TODO(morlovich): It would probably be better to wire something more |
| // precise. |
| resolver->Reject( |
| "Promise argument rejected or resolved to invalid value."); |
| } |
| } else if (result_config) { |
| DCHECK(result_config->mapped_url().has_value()); |
| DCHECK(!result_config->mapped_url()->potentially_opaque_value.has_value()); |
| |
| auction_resolver_ = resolver; |
| auction_config_ = result_config; |
| |
| MaybeResolveAuction(); |
| } else { |
| resolver->Resolve(v8::Null(script_state->GetIsolate())); |
| } |
| } |
| |
| void NavigatorAuction::AuctionHandle::MaybeResolveAuction() { |
| if (!resolve_to_config_.has_value() || !auction_resolver_ || |
| !auction_config_.has_value()) { |
| // Once both the resolveToConfig promise is resolved and the auction is |
| // completed, this function will be called again to actually |
| // complete the auction. |
| return; |
| } |
| |
| if (resolve_to_config_.value() == true) { |
| auction_resolver_->Resolve( |
| FencedFrameConfig::From(auction_config_.value())); |
| } else { |
| auction_resolver_->Resolve(KURL(auction_config_->urn_uuid().value())); |
| } |
| } |
| |
| void NavigatorAuction::GetURLFromURNComplete( |
| ScriptPromiseResolver* resolver, |
| const absl::optional<KURL>& decoded_url) { |
| if (decoded_url) { |
| resolver->Resolve(*decoded_url); |
| } else { |
| resolver->Resolve(v8::Null(resolver->GetScriptState()->GetIsolate())); |
| } |
| } |
| |
| void NavigatorAuction::ReplaceInURNComplete(ScriptPromiseResolver* resolver) { |
| resolver->Resolve(); |
| } |
| |
| bool NavigatorAuction::canLoadAdAuctionFencedFrame(ScriptState* script_state) { |
| if (!script_state->ContextIsValid()) { |
| return false; |
| } |
| |
| LocalFrame* frame_to_check = LocalDOMWindow::From(script_state)->GetFrame(); |
| ExecutionContext* context = ExecutionContext::From(script_state); |
| DCHECK(frame_to_check && context); |
| |
| ContentSecurityPolicy* csp = context->GetContentSecurityPolicy(); |
| DCHECK(csp); |
| |
| // "A fenced frame tree of one mode cannot contain a child fenced frame of |
| // another mode." |
| // See: https://github.com/WICG/fenced-frame/blob/master/explainer/modes.md |
| if (frame_to_check->GetPage()->IsMainFrameFencedFrameRoot() && |
| frame_to_check->GetPage()->DeprecatedFencedFrameMode() != |
| blink::FencedFrame::DeprecatedFencedFrameMode::kOpaqueAds) { |
| return false; |
| } |
| |
| if (!context->IsSecureContext()) { |
| return false; |
| } |
| |
| // Check that the flags specified in kFencedFrameMandatoryUnsandboxedFlags |
| // are not set in this context. Fenced frames loaded in a sandboxed document |
| // require these flags to remain unsandboxed. |
| if (context->IsSandboxed(kFencedFrameMandatoryUnsandboxedFlags)) { |
| return false; |
| } |
| |
| // Check the results of the browser checks for the current frame. |
| // If the embedding frame is an iframe with CSPEE set, or any ancestor |
| // iframes has CSPEE set, the fenced frame will not be allowed to load. |
| // The renderer has no knowledge of CSPEE up the ancestor chain, so we defer |
| // to the browser to determine the existence of CSPEE outside of the scope |
| // we can see here. |
| if (frame_to_check->AncestorOrSelfHasCSPEE()) { |
| return false; |
| } |
| |
| // Ensure that if any CSP headers are set that will affect a fenced frame, |
| // they allow all https urls to load. Opaque-ads fenced frames do not support |
| // allowing/disallowing specific hosts, as that could reveal information to |
| // a fenced frame about its embedding page. See design doc for more info: |
| // https://github.com/WICG/fenced-frame/blob/master/explainer/interaction_with_content_security_policy.md |
| // This is being checked in the renderer because processing of <meta> tags |
| // (including CSP) happen in the renderer after navigation commit, so we can't |
| // piggy-back off of the ancestor_or_self_has_cspee bit being sent from the |
| // browser (which is sent at commit time) since it doesn't know about all the |
| // CSP headers yet. |
| for (const auto& policy : csp->GetParsedPolicies()) { |
| CSPOperativeDirective directive = CSPDirectiveListOperativeDirective( |
| *policy, network::mojom::CSPDirectiveName::FencedFrameSrc); |
| if (directive.type != network::mojom::CSPDirectiveName::Unknown) { |
| // "*" urls will cause the allow_star flag to set |
| if (directive.source_list->allow_star) { |
| continue; |
| } |
| |
| // Check for "https:" or "https://*:*" |
| bool found_matching_source = false; |
| for (const auto& source : directive.source_list->sources) { |
| if (source->scheme == url::kHttpsScheme && source->host == "") { |
| found_matching_source = true; |
| break; |
| } |
| } |
| if (!found_matching_source) { |
| return false; |
| } |
| } |
| } |
| |
| return true; |
| } |
| |
| /* static */ |
| bool NavigatorAuction::canLoadAdAuctionFencedFrame(ScriptState* script_state, |
| Navigator& navigator) { |
| if (!navigator.DomWindow()) { |
| return false; |
| } |
| return From(ExecutionContext::From(script_state), navigator) |
| .canLoadAdAuctionFencedFrame(script_state); |
| } |
| |
| ScriptPromise NavigatorAuction::getInterestGroupAdAuctionData( |
| ScriptState* script_state, |
| const AdAuctionDataConfig* config, |
| ExceptionState& exception_state) { |
| CHECK(config); |
| if (!script_state->ContextIsValid()) { |
| return ScriptPromise(); |
| } |
| |
| scoped_refptr<const SecurityOrigin> seller = ParseOrigin(config->seller()); |
| if (!seller) { |
| exception_state.ThrowTypeError(String::Format( |
| "seller '%s' for AdAuctionDataConfig must be a valid https origin.", |
| config->seller().Utf8().c_str())); |
| return ScriptPromise(); |
| } |
| |
| auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>( |
| script_state, exception_state.GetContext()); |
| |
| ScriptPromise promise = resolver->Promise(); |
| |
| ad_auction_service_->GetInterestGroupAdAuctionData( |
| seller, resolver->WrapCallbackInScriptScope(WTF::BindOnce( |
| &NavigatorAuction::GetInterestGroupAdAuctionDataComplete, |
| WrapPersistent(this)))); |
| return promise; |
| } |
| |
| void NavigatorAuction::GetInterestGroupAdAuctionDataComplete( |
| ScriptPromiseResolver* resolver, |
| mojo_base::BigBuffer data) { |
| ScriptState* script_state = resolver->GetScriptState(); |
| v8::Isolate* isolate = script_state->GetIsolate(); |
| v8::Local<v8::ArrayBuffer> array_buffer = |
| v8::ArrayBuffer::New(isolate, data.size()); |
| if (data.size() > 0) { |
| CHECK(array_buffer->Data()); |
| memcpy(array_buffer->Data(), data.data(), data.size()); |
| } |
| v8::Local<v8::Uint8Array> uint8_array = |
| v8::Uint8Array::New(array_buffer, 0, data.size()); |
| resolver->Resolve(uint8_array); |
| } |
| |
| /* static */ |
| ScriptPromise NavigatorAuction::getInterestGroupAdAuctionData( |
| ScriptState* script_state, |
| Navigator& navigator, |
| const AdAuctionDataConfig* config, |
| ExceptionState& exception_state) { |
| if (!navigator.DomWindow()) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kInvalidAccessError, |
| "The document has no window associated."); |
| return ScriptPromise(); |
| } |
| RecordCommonFledgeUseCounters(navigator.DomWindow()->document()); |
| const ExecutionContext* context = ExecutionContext::From(script_state); |
| if (!context->IsFeatureEnabled( |
| blink::mojom::PermissionsPolicyFeature::kRunAdAuction)) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kNotAllowedError, |
| "Feature run-ad-auction is not enabled by Permissions Policy"); |
| return ScriptPromise(); |
| } |
| if (!base::FeatureList::IsEnabled( |
| blink::features::kAdInterestGroupAPIRestrictedPolicyByDefault) && |
| FeatureWouldBeBlockedByRestrictedPermissionsPolicy(navigator)) { |
| AddWarningMessageToConsole(script_state, "run-ad-auction", |
| "getInterestGroupAdAuctionData"); |
| } |
| |
| return From(ExecutionContext::From(script_state), navigator) |
| .getInterestGroupAdAuctionData(script_state, config, exception_state); |
| } |
| |
| } // namespace blink |