910e62b5创建于 1月15日历史提交
// 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 "content/services/auction_worklet/bidder_worklet.h"

#include <stdint.h>

#include <algorithm>
#include <cmath>
#include <cstddef>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>

#include "base/check.h"
#include "base/containers/contains.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/i18n/number_formatting.h"
#include "base/memory/scoped_refptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/time.h"
#include "base/timer/elapsed_timer.h"
#include "base/trace_event/trace_event.h"
#include "base/trace_event/trace_id_helper.h"
#include "base/types/optional_ref.h"
#include "base/types/optional_util.h"
#include "content/public/common/content_features.h"
#include "content/services/auction_worklet/auction_v8_helper.h"
#include "content/services/auction_worklet/auction_v8_logger.h"
#include "content/services/auction_worklet/auction_worklet_util.h"
#include "content/services/auction_worklet/bidder_lazy_filler.h"
#include "content/services/auction_worklet/bidder_worklet_thread_selector.h"
#include "content/services/auction_worklet/deprecated_url_lazy_filler.h"
#include "content/services/auction_worklet/direct_from_seller_signals_requester.h"
#include "content/services/auction_worklet/execution_mode_util.h"
#include "content/services/auction_worklet/for_debugging_only_bindings.h"
#include "content/services/auction_worklet/private_aggregation_bindings.h"
#include "content/services/auction_worklet/private_model_training_bindings.h"
#include "content/services/auction_worklet/public/cpp/auction_network_events_delegate.h"
#include "content/services/auction_worklet/public/cpp/auction_worklet_features.h"
#include "content/services/auction_worklet/public/cpp/private_aggregation_reporting.h"
#include "content/services/auction_worklet/public/cpp/private_model_training_reporting.h"
#include "content/services/auction_worklet/public/mojom/auction_worklet_service.mojom.h"
#include "content/services/auction_worklet/public/mojom/bidder_worklet.mojom.h"
#include "content/services/auction_worklet/public/mojom/in_progress_auction_download.mojom.h"
#include "content/services/auction_worklet/public/mojom/private_aggregation_request.mojom.h"
#include "content/services/auction_worklet/public/mojom/real_time_reporting.mojom.h"
#include "content/services/auction_worklet/public/mojom/trusted_signals_cache.mojom.h"
#include "content/services/auction_worklet/real_time_reporting_bindings.h"
#include "content/services/auction_worklet/register_ad_beacon_bindings.h"
#include "content/services/auction_worklet/register_ad_macro_bindings.h"
#include "content/services/auction_worklet/report_bindings.h"
#include "content/services/auction_worklet/report_win_browser_signals_lazy_filler.h"
#include "content/services/auction_worklet/set_bid_bindings.h"
#include "content/services/auction_worklet/set_priority_bindings.h"
#include "content/services/auction_worklet/set_priority_signals_override_bindings.h"
#include "content/services/auction_worklet/shared_storage_bindings.h"
#include "content/services/auction_worklet/trusted_signals.h"
#include "content/services/auction_worklet/trusted_signals_kvv2_manager.h"
#include "content/services/auction_worklet/trusted_signals_request_manager.h"
#include "content/services/auction_worklet/worklet_loader.h"
#include "content/services/auction_worklet/worklet_util.h"
#include "context_recycler.h"
#include "gin/converter.h"
#include "gin/dictionary.h"
#include "mojo/public/cpp/base/big_buffer.h"
#include "mojo/public/cpp/bindings/associated_remote.h"
#include "mojo/public/cpp/bindings/pending_associated_remote.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/bindings/struct_ptr.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/mojom/url_loader_factory.mojom.h"
#include "third_party/blink/public/common/features.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.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.h"
#include "third_party/perfetto/include/perfetto/tracing/track.h"
#include "url/gurl.h"
#include "url/origin.h"
#include "v8-statistics.h"
#include "v8/include/v8-container.h"
#include "v8/include/v8-context.h"
#include "v8/include/v8-exception.h"
#include "v8/include/v8-forward.h"
#include "v8/include/v8-object.h"
#include "v8/include/v8-primitive.h"
#include "v8/include/v8-template.h"
#include "v8/include/v8-wasm.h"

namespace auction_worklet {

namespace {

bool AppendJsonValueOrNull(AuctionV8Helper* const v8_helper,
                           v8::Local<v8::Context> context,
                           const std::string* maybe_json,
                           v8::LocalVector<v8::Value>* args) {
  v8::Isolate* isolate = v8_helper->isolate();
  if (maybe_json) {
    if (!v8_helper->AppendJsonValue(context, *maybe_json, args)) {
      return false;
    }
  } else {
    args->push_back(v8::Null(isolate));
  }
  return true;
}

// Checks both types of DirectFromSellerSignals results (subresource bundle
// based and header based) -- at most one of these should be non-null.
//
// Returns the V8 conversion of the in-use version of DirectFromSellerSignals,
// or v8::Null() if both types of DirectFromSellerSignals are null.
v8::Local<v8::Value> GetDirectFromSellerSignals(
    const DirectFromSellerSignalsRequester::Result& subresource_bundle_result,
    const std::optional<std::string>& header_result,
    AuctionV8Helper& v8_helper,
    v8::Local<v8::Context> context,
    std::vector<std::string>& errors) {
  CHECK(subresource_bundle_result.IsNull() || !header_result);

  if (header_result) {
    // `header_result` JSON was validated, parsed and reconstructed into a
    // string by the browser process, so CHECK it is valid JSON.
    return v8_helper.CreateValueFromJson(context, *header_result)
        .ToLocalChecked();
  }

  return subresource_bundle_result.GetSignals(v8_helper, context, errors);
}

std::optional<base::TimeDelta> NullOptIfZero(base::TimeDelta delta) {
  if (delta.is_zero()) {
    return std::nullopt;
  }
  return delta;
}

// Adjust `bid` to meet `target_num_ad_components`, if any.
void TrimExtraAdComponents(mojom::BidderWorkletBid& bid,
                           std::optional<size_t> target_num_ad_components) {
  if (!target_num_ad_components.has_value()) {
    return;
  }
  // SetBidBindings should have enforced that there are enough adComponents,
  // as should have HandleComponentsKAnon() when bifurcating bids,
  // which also implies they exist.
  DCHECK(bid.ad_component_descriptors.has_value());
  DCHECK_LE(*target_num_ad_components, bid.ad_component_descriptors->size());
  bid.ad_component_descriptors->resize(*target_num_ad_components);
}

// Applies `target_num_ad_components` to `bid` and appends it to `out`.
void TrimAndCollectBid(mojom::BidderWorkletBidPtr bid,
                       std::optional<size_t> target_num_ad_components,
                       std::vector<mojom::BidderWorkletBidPtr>& out) {
  TrimExtraAdComponents(*bid, target_num_ad_components);
  out.push_back(std::move(bid));
}

// Given `bid` that has k-anonymous main ad and some component ads, handles the
// k-anon check on its components, possibly dropping some if allowed. Appends
// the resulting bid (and perhaps a non-k-anon alternative) to `out`.
void HandleComponentsKAnon(
    const mojom::BidderWorkletNonSharedParams* bidder_worklet_non_shared_params,
    mojom::BidderWorkletBidPtr bid,
    std::optional<size_t> target_num_ad_components,
    size_t num_mandatory_ad_components,
    std::vector<mojom::BidderWorkletBidPtr>& out) {
  size_t num_required_components =
      target_num_ad_components.value_or(bid->ad_component_descriptors->size());
  // Go through the ad component list and try to collect
  // `num_required_components` k-anonymous ones into
  // `usable_ad_component_indices`. Gives up if a mandatory ad component isn't
  // k-anonymous.  Sets `saw_non_k_anon` to true if it needed to skip over any
  // non-k-anonymous ones.
  std::vector<size_t> usable_ad_component_indices;
  usable_ad_component_indices.reserve(num_required_components);
  bool saw_non_k_anon = false;
  for (size_t i = 0; i < bid->ad_component_descriptors->size(); ++i) {
    const blink::AdDescriptor& ad_component_descriptor =
        bid->ad_component_descriptors.value()[i];
    if (BidderWorklet::IsComponentAdKAnon(bidder_worklet_non_shared_params,
                                          ad_component_descriptor)) {
      usable_ad_component_indices.push_back(i);
      if (usable_ad_component_indices.size() == num_required_components) {
        break;
      }
    } else {
      saw_non_k_anon = true;
      if (i < num_mandatory_ad_components) {
        // One of the required component ads is not k-anon, so have to give up
        // on this.
        break;
      }
    }
  }

  DCHECK_LE(usable_ad_component_indices.size(), num_required_components);
  if (usable_ad_component_indices.size() == num_required_components) {
    if (!saw_non_k_anon) {
      // The bid was actually completely fine without getting fancy.
      bid->bid_role = mojom::BidRole::kBothKAnonModes;
      TrimAndCollectBid(std::move(bid), target_num_ad_components, out);
    } else {
      // Split the bid into two, with one having just the usable ad components.
      mojom::BidderWorkletBidPtr non_kanon_alternative = bid->Clone();
      DCHECK_EQ(non_kanon_alternative->bid_role,
                mojom::BidRole::kUnenforcedKAnon);

      bid->bid_role = mojom::BidRole::kEnforcedKAnon;
      std::vector<blink::AdDescriptor> usable_ad_components;
      usable_ad_components.reserve(num_required_components);
      for (size_t index : usable_ad_component_indices) {
        usable_ad_components.push_back(
            std::move(bid->ad_component_descriptors.value()[index]));
      }
      bid->ad_component_descriptors = std::move(usable_ad_components);

      TrimAndCollectBid(std::move(bid), target_num_ad_components, out);
      TrimAndCollectBid(std::move(non_kanon_alternative),
                        target_num_ad_components, out);
    }
  } else {
    // Could not salvage the bid; just drop any extra component ads and mark it
    // as non-k-anon.
    DCHECK_EQ(bid->bid_role, mojom::BidRole::kUnenforcedKAnon);
    TrimAndCollectBid(std::move(bid), target_num_ad_components, out);
  }
}

std::vector<mojom::BidderWorkletBidPtr> ClassifyBidsAndApplyComponentAdLimits(
    mojom::KAnonymityBidMode kanon_mode,
    const mojom::BidderWorkletNonSharedParams* bidder_worklet_non_shared_params,
    const GURL& script_source_url,
    std::vector<SetBidBindings::BidAndWorkletOnlyMetadata> bid_info) {
  std::vector<mojom::BidderWorkletBidPtr> bids;
  for (auto& candidate : bid_info) {
    if (kanon_mode == mojom::KAnonymityBidMode::kNone ||
        !BidderWorklet::IsMainAdKAnon(bidder_worklet_non_shared_params,
                                      script_source_url, candidate)) {
      DCHECK_EQ(candidate.bid->bid_role, mojom::BidRole::kUnenforcedKAnon);
      TrimAndCollectBid(std::move(candidate.bid),
                        candidate.target_num_ad_components, bids);
    } else {
      // We care about k-anonymity, and whether the bid is k-anonymous or not
      // depends on whether component ads are k-anonymous; we also may be able
      // to salvage the bid by throwing out some components while trying to get
      // it to the target.
      if (!candidate.bid->ad_component_descriptors.has_value() ||
          candidate.bid->ad_component_descriptors->empty()) {
        // There are no components to worry about, so it's k-anon.
        candidate.bid->bid_role = mojom::BidRole::kBothKAnonModes;
        bids.push_back(std::move(candidate.bid));
      } else {
        HandleComponentsKAnon(bidder_worklet_non_shared_params,
                              std::move(candidate.bid),
                              candidate.target_num_ad_components,
                              candidate.num_mandatory_ad_components, bids);
      }
    }
  }
  return bids;
}

size_t CalculateNumberOfContextsToPreparePerThread(
    size_t max_expected_required_contexts,
    size_t threads) {
  return fmin(
      features::kFledgeMaxBidderContextsPerThreadInAdvance.Get(),
      fmax(features::kFledgeMinBidderContextsPerThreadInAdvance.Get(),
           (max_expected_required_contexts *
            features::kFledgeBidderContextsMultiplier.Get()) /
               (threads * features::kFledgeBidderContextsDivisor.Get())));
}

// Check if trusted bidding signals, if any, are same-origin or cross-origin.
BidderWorklet::SignalsOriginRelation ClassifyTrustedSignals(
    const GURL& script_source_url,
    const std::optional<url::Origin>& trusted_bidding_signals_origin) {
  if (!trusted_bidding_signals_origin) {
    return BidderWorklet::SignalsOriginRelation::kNoTrustedSignals;
  }
  if (trusted_bidding_signals_origin->IsSameOriginWith(script_source_url)) {
    return BidderWorklet::SignalsOriginRelation::kSameOriginSignals;
  }

  return BidderWorklet::SignalsOriginRelation::kCrossOriginSignals;
}

// Sets the appropriate field (if any) of `browser_signals_dict` to data
// version. Returns success/failure.
bool SetDataVersion(
    BidderWorklet::SignalsOriginRelation trusted_signals_relation,
    std::optional<uint32_t> bidding_signals_data_version,
    gin::Dictionary& browser_signals_dict) {
  if (!bidding_signals_data_version.has_value()) {
    return true;
  }

  switch (trusted_signals_relation) {
    case BidderWorklet::SignalsOriginRelation::kNoTrustedSignals:
      return true;

    case BidderWorklet::SignalsOriginRelation::kSameOriginSignals:
      return browser_signals_dict.Set("dataVersion",
                                      bidding_signals_data_version.value());

    case BidderWorklet::SignalsOriginRelation::kCrossOriginSignals:
      return browser_signals_dict.Set("crossOriginDataVersion",
                                      bidding_signals_data_version.value());
  }
}

// Sets `view_or_click_counts` on the `member` member of `top_level_dict`.
bool MaybeSetViewOrClickCounts(
    v8::Isolate* isolate,
    gin::Dictionary& top_level_dict,
    const std::string& member,
    const blink::mojom::ViewOrClickCountsPtr& view_or_click_counts) {
  gin::Dictionary result = gin::Dictionary::CreateEmpty(isolate);
  return result.Set("pastHour", view_or_click_counts->past_hour) &&
         result.Set("pastDay", view_or_click_counts->past_day) &&
         result.Set("pastWeek", view_or_click_counts->past_week) &&
         result.Set("past30Days", view_or_click_counts->past_30_days) &&
         result.Set("past90Days", view_or_click_counts->past_90_days) &&
         top_level_dict.Set(member, result);
}

}  // namespace

BidderWorklet::BidderWorklet(
    std::vector<scoped_refptr<AuctionV8Helper>> v8_helpers,
    std::vector<mojo::PendingRemote<mojom::AuctionSharedStorageHost>>
        shared_storage_hosts,
    bool pause_for_debugger_on_start,
    mojo::PendingRemote<network::mojom::URLLoaderFactory>
        pending_url_loader_factory,
    mojo::PendingRemote<auction_worklet::mojom::AuctionNetworkEventsHandler>
        auction_network_events_handler,
    TrustedSignalsKVv2Manager* trusted_signals_kvv2_manager,
    mojom::InProgressAuctionDownloadPtr script_source_load,
    mojom::InProgressAuctionDownloadPtr wasm_helper_load,
    const std::optional<GURL>& trusted_bidding_signals_url,
    const std::string& trusted_bidding_signals_slot_size_param,
    const url::Origin& top_window_origin,
    mojom::AuctionWorkletPermissionsPolicyStatePtr permissions_policy_state,
    std::optional<uint16_t> experiment_group_id,
    mojom::TrustedSignalsPublicKeyPtr public_key)
    : thread_selector_(v8_helpers.size()),
      url_loader_factory_(std::move(pending_url_loader_factory)),
      trusted_signals_kvv2_manager_(trusted_signals_kvv2_manager),
      script_source_url_(script_source_load->url),
      script_source_load_(std::move(script_source_load)),
      wasm_helper_url_(wasm_helper_load
                           ? std::optional<GURL>(wasm_helper_load->url)
                           : std::nullopt),
      wasm_helper_load_(std::move(wasm_helper_load)),
      top_window_origin_(top_window_origin),
      auction_network_events_handler_(
          std::move(auction_network_events_handler)) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_);

  DCHECK(!v8_helpers.empty());
  DCHECK_EQ(v8_helpers.size(), shared_storage_hosts.size());

  for (size_t i = 0; i < v8_helpers.size(); ++i) {
    v8_runners_.push_back(v8_helpers[i]->v8_runner());
    v8_helpers_.push_back(std::move(v8_helpers[i]));
    debug_ids_.push_back(
        base::MakeRefCounted<AuctionV8Helper::DebugId>(v8_helpers_[i].get()));
    v8_state_.push_back(std::unique_ptr<V8State, base::OnTaskRunnerDeleter>(
        new V8State(v8_helpers_[i], debug_ids_[i],
                    std::move(shared_storage_hosts[i]), script_source_url_,
                    top_window_origin_, permissions_policy_state->Clone(),
                    wasm_helper_url_, trusted_bidding_signals_url,
                    weak_ptr_factory_.GetWeakPtr()),
        base::OnTaskRunnerDeleter(v8_runners_[i])));
  }

  if (trusted_bidding_signals_url) {
    size_t signals_request_thread = thread_selector_.GetThread();
    trusted_signals_request_manager_ =
        std::make_unique<TrustedSignalsRequestManager>(
            TrustedSignalsRequestManager::Type::kBiddingSignals,
            url_loader_factory_.get(),
            /*auction_network_events_handler=*/
            CreateNewAuctionNetworkEventsHandlerRemote(
                auction_network_events_handler_),
            /*automatically_send_requests=*/false, top_window_origin,
            *trusted_bidding_signals_url, experiment_group_id,
            trusted_bidding_signals_slot_size_param, std::move(public_key),
            /*send_creative_scanning_metadata=*/false,
            v8_helpers_[signals_request_thread].get());
    // Consider this task complete for bookkeeping purposes because it's
    // relatively cheap compared to executing worklet scripts.
    thread_selector_.TaskCompletedOnThread(signals_request_thread);
  }

  paused_ = pause_for_debugger_on_start;
  if (!paused_) {
    Start();
  }
}

BidderWorklet::~BidderWorklet() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_);

  for (const auto& debug_id : debug_ids_) {
    debug_id->AbortDebuggerPauses();
  }
}

std::vector<int> BidderWorklet::context_group_ids_for_testing() const {
  std::vector<int> results;
  for (const auto& debug_id : debug_ids_) {
    results.push_back(debug_id->context_group_id());
  }
  return results;
}

// static
bool BidderWorklet::IsKAnon(
    const mojom::BidderWorkletNonSharedParams* bidder_worklet_non_shared_params,
    const std::string& key) {
  return std::find(bidder_worklet_non_shared_params->kanon_keys.begin(),
                   bidder_worklet_non_shared_params->kanon_keys.end(),
                   mojom::KAnonKey::New(key)) !=
         bidder_worklet_non_shared_params->kanon_keys.end();
}

// static
bool BidderWorklet::IsMainAdKAnon(
    const mojom::BidderWorkletNonSharedParams* bidder_worklet_non_shared_params,
    const GURL& script_source_url,
    const SetBidBindings::BidAndWorkletOnlyMetadata& bid_and_metadata) {
  const mojom::BidderWorkletBidPtr& bid = bid_and_metadata.bid;
  if (!bid) {
    return true;
  }

  url::Origin owner = url::Origin::Create(script_source_url);
  if (!BidderWorklet::IsKAnon(
          bidder_worklet_non_shared_params,
          blink::HashedKAnonKeyForAdBid(owner, script_source_url,
                                        bid->ad_descriptor.url.spec()))) {
    return false;
  }
  if (bid->selected_buyer_and_seller_reporting_id.has_value()) {
    if (!BidderWorklet::IsKAnon(
            bidder_worklet_non_shared_params,
            blink::HashedKAnonKeyForAdNameReportingWithoutInterestGroup(
                /*interest_group_owner=*/owner,
                /*interest_group_name=*/bidder_worklet_non_shared_params->name,
                /*interest_group_bidding_url=*/script_source_url,
                /*ad_render_url=*/bid->ad_descriptor.url.spec(),
                bid_and_metadata.buyer_reporting_id,
                bid_and_metadata.buyer_and_seller_reporting_id,
                bid_and_metadata.bid
                    ->selected_buyer_and_seller_reporting_id))) {
      return false;
    }
  }
  return true;
}

// static
bool BidderWorklet::IsComponentAdKAnon(
    const mojom::BidderWorkletNonSharedParams* bidder_worklet_non_shared_params,
    const blink::AdDescriptor& ad_component_descriptor) {
  return IsKAnon(
      bidder_worklet_non_shared_params,
      blink::HashedKAnonKeyForAdComponentBid(ad_component_descriptor));
}

// static
bool BidderWorklet::SupportMultiBid() {
  return base::FeatureList::IsEnabled(blink::features::kFledgeMultiBid);
}

void BidderWorklet::BeginGenerateBid(
    mojom::BidderWorkletNonSharedParamsPtr bidder_worklet_non_shared_params,
    mojom::TrustedSignalsCacheKeyPtr trusted_signals_cache_key,
    mojom::KAnonymityBidMode kanon_mode,
    const url::Origin& interest_group_join_origin,
    const std::optional<GURL>& direct_from_seller_per_buyer_signals,
    const std::optional<GURL>& direct_from_seller_auction_signals,
    const url::Origin& browser_signal_seller_origin,
    const std::optional<url::Origin>& browser_signal_top_level_seller_origin,
    const base::TimeDelta browser_signal_recency,
    bool browser_signal_for_debugging_only_sampling,
    blink::mojom::BiddingBrowserSignalsPtr bidding_browser_signals,
    base::Time auction_start_time,
    const std::optional<blink::AdSize>& requested_ad_size,
    uint16_t multi_bid_limit,
    uint64_t group_by_origin_id,
    uint64_t trace_id,
    mojo::PendingAssociatedRemote<mojom::GenerateBidClient> generate_bid_client,
    mojo::PendingAssociatedReceiver<mojom::GenerateBidFinalizer>
        bid_finalizer) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_);

  // Add to end of list to make best effort attempt to maintain task order.
  auto generate_bid_task =
      generate_bid_tasks_.emplace(generate_bid_tasks_.end());
  generate_bid_task->bidder_worklet_non_shared_params =
      std::move(bidder_worklet_non_shared_params);
  generate_bid_task->kanon_mode = kanon_mode;
  generate_bid_task->browser_signal_seller_origin =
      browser_signal_seller_origin;
  generate_bid_task->browser_signal_top_level_seller_origin =
      browser_signal_top_level_seller_origin;
  generate_bid_task->browser_signal_recency = browser_signal_recency;
  generate_bid_task->browser_signal_for_debugging_only_sampling =
      browser_signal_for_debugging_only_sampling;
  generate_bid_task->bidding_browser_signals =
      std::move(bidding_browser_signals);
  generate_bid_task->auction_start_time = auction_start_time;
  generate_bid_task->requested_ad_size = requested_ad_size;
  generate_bid_task->multi_bid_limit = multi_bid_limit;
  generate_bid_task->group_by_origin_id = group_by_origin_id;
  generate_bid_task->trace_id = trace_id;
  generate_bid_task->generate_bid_client.Bind(std::move(generate_bid_client));
  // Deleting `generate_bid_task` will destroy `generate_bid_client` and thus
  // abort this callback, so it's safe to use Unretained(this) and
  // `generate_bid_task` here.
  generate_bid_task->generate_bid_client.set_disconnect_handler(
      base::BindOnce(&BidderWorklet::OnGenerateBidClientDestroyed,
                     base::Unretained(this), generate_bid_task));

  // Listen to call to FinalizeGenerateBid completing arguments.
  generate_bid_task->finalize_generate_bid_receiver_id =
      finalize_receiver_set_.Add(this, std::move(bid_finalizer),
                                 generate_bid_task);

  HandleDirectFromSellerForGenerateBid(direct_from_seller_per_buyer_signals,
                                       direct_from_seller_auction_signals,
                                       generate_bid_task);

  const auto& trusted_bidding_signals_keys =
      generate_bid_task->bidder_worklet_non_shared_params
          ->trusted_bidding_signals_keys;
  generate_bid_task->trace_wait_deps_start = base::TimeTicks::Now();
  TRACE_EVENT_BEGIN("fledge", "wait_generate_bid_deps",
                    perfetto::Track(trace_id));
  if (trusted_signals_cache_key) {
    // A non-null `trusted_signals_cache_key` indicates that the
    // TrustedSignalsCache should be used through the
    // `trusted_signals_kvv2_manager_` interface.
    generate_bid_task->trusted_bidding_signals_kvv2_request =
        trusted_signals_kvv2_manager_->RequestSignals(
            TrustedSignalsKVv2Manager::SignalsType::kBidding,
            trusted_signals_cache_key->compression_group_token,
            trusted_signals_cache_key->partition_id,
            base::BindOnce(&BidderWorklet::OnTrustedBiddingSignalsDownloaded,
                           base::Unretained(this), generate_bid_task));
    return;
  } else if (trusted_signals_request_manager_) {
    if (trusted_signals_request_manager_->HasPublicKey()) {
      DCHECK(base::FeatureList::IsEnabled(
          blink::features::kFledgeTrustedSignalsKVv2Support));
      generate_bid_task->trusted_bidding_signals_request =
          trusted_signals_request_manager_->RequestKVv2BiddingSignals(
              generate_bid_task->bidder_worklet_non_shared_params->name,
              trusted_bidding_signals_keys, interest_group_join_origin,
              generate_bid_task->bidder_worklet_non_shared_params
                  ->execution_mode,
              base::BindOnce(&BidderWorklet::OnTrustedBiddingSignalsDownloaded,
                             base::Unretained(this), generate_bid_task));
    } else {
      generate_bid_task->trusted_bidding_signals_request =
          trusted_signals_request_manager_->RequestBiddingSignals(
              generate_bid_task->bidder_worklet_non_shared_params->name,
              trusted_bidding_signals_keys,
              generate_bid_task->bidder_worklet_non_shared_params
                  ->max_trusted_bidding_signals_url_length,
              base::BindOnce(&BidderWorklet::OnTrustedBiddingSignalsDownloaded,
                             base::Unretained(this), generate_bid_task));
    }
    return;
  }

  // Deleting `generate_bid_task` will destroy `generate_bid_client` and thus
  // abort this callback, so it's safe to use Unretained(this) and
  // `generate_bid_task` here.
  generate_bid_task->generate_bid_client->OnBiddingSignalsReceived(
      /*priority_vector=*/{},
      /*trusted_signals_fetch_latency=*/base::TimeDelta(),
      /*update_if_older_than=*/std::nullopt,
      base::BindOnce(&BidderWorklet::SignalsReceivedCallback,
                     base::Unretained(this), generate_bid_task));
}

void BidderWorklet::SendPendingSignalsRequests() {
  if (trusted_signals_request_manager_) {
    trusted_signals_request_manager_->StartBatchedTrustedSignalsRequest();
  }
}

void BidderWorklet::ReportWin(
    bool is_for_additional_bid,
    const std::optional<std::string>& interest_group_name_reporting_id,
    const std::optional<std::string>& buyer_reporting_id,
    const std::optional<std::string>& buyer_and_seller_reporting_id,
    const std::optional<std::string>& selected_buyer_and_seller_reporting_id,
    const std::optional<std::string>& auction_signals_json,
    const std::optional<std::string>& per_buyer_signals_json,
    const std::optional<GURL>& direct_from_seller_per_buyer_signals,
    const std::optional<std::string>&
        direct_from_seller_per_buyer_signals_header_ad_slot,
    const std::optional<GURL>& direct_from_seller_auction_signals,
    const std::optional<std::string>&
        direct_from_seller_auction_signals_header_ad_slot,
    const std::string& seller_signals_json,
    mojom::KAnonymityStatus kanon_status,
    const GURL& browser_signal_render_url,
    double browser_signal_bid,
    const std::optional<blink::AdCurrency>& browser_signal_bid_currency,
    double browser_signal_highest_scoring_other_bid,
    const std::optional<blink::AdCurrency>&
        browser_signal_highest_scoring_other_bid_currency,
    bool browser_signal_made_highest_scoring_other_bid,
    std::optional<double> browser_signal_ad_cost,
    std::optional<uint16_t> browser_signal_modeling_signals,
    uint8_t browser_signal_join_count,
    uint8_t browser_signal_recency,
    const url::Origin& browser_signal_seller_origin,
    const std::optional<url::Origin>& browser_signal_top_level_seller_origin,
    const std::optional<base::TimeDelta> browser_signal_reporting_timeout,
    std::optional<uint32_t> bidding_signals_data_version,
    const std::optional<std::string>& aggregate_win_signals,
    uint64_t trace_id,
    ReportWinCallback report_win_callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_);
  CHECK((!direct_from_seller_per_buyer_signals &&
         !direct_from_seller_auction_signals) ||
        (!direct_from_seller_per_buyer_signals_header_ad_slot &&
         !direct_from_seller_auction_signals_header_ad_slot));

  // Add to end of list to make best effort attempt to maintain task order.
  auto report_win_task = report_win_tasks_.emplace(report_win_tasks_.end());
  report_win_task->is_for_additional_bid = is_for_additional_bid;
  report_win_task->interest_group_name_reporting_id =
      interest_group_name_reporting_id;
  report_win_task->buyer_reporting_id = buyer_reporting_id;
  report_win_task->buyer_and_seller_reporting_id =
      buyer_and_seller_reporting_id;
  report_win_task->selected_buyer_and_seller_reporting_id =
      selected_buyer_and_seller_reporting_id;
  report_win_task->auction_signals_json = auction_signals_json;
  report_win_task->per_buyer_signals_json = per_buyer_signals_json;
  report_win_task->seller_signals_json = seller_signals_json;
  report_win_task->kanon_status = kanon_status;
  report_win_task->browser_signal_render_url = browser_signal_render_url;
  report_win_task->browser_signal_bid = browser_signal_bid;
  report_win_task->browser_signal_bid_currency = browser_signal_bid_currency;
  report_win_task->browser_signal_highest_scoring_other_bid =
      browser_signal_highest_scoring_other_bid;
  report_win_task->browser_signal_highest_scoring_other_bid_currency =
      browser_signal_highest_scoring_other_bid_currency;
  report_win_task->browser_signal_made_highest_scoring_other_bid =
      browser_signal_made_highest_scoring_other_bid;
  report_win_task->browser_signal_ad_cost = browser_signal_ad_cost;
  report_win_task->browser_signal_modeling_signals =
      browser_signal_modeling_signals;
  report_win_task->browser_signal_join_count = browser_signal_join_count;
  report_win_task->browser_signal_recency = browser_signal_recency;
  report_win_task->browser_signal_seller_origin = browser_signal_seller_origin;
  report_win_task->browser_signal_top_level_seller_origin =
      browser_signal_top_level_seller_origin;
  report_win_task->browser_signal_reporting_timeout =
      browser_signal_reporting_timeout;
  report_win_task->bidding_signals_data_version = bidding_signals_data_version;
  report_win_task->aggregate_win_signals = aggregate_win_signals;
  report_win_task->trace_id = trace_id;
  report_win_task->callback = std::move(report_win_callback);

  if (direct_from_seller_per_buyer_signals) {
    // Deleting `report_win_task` will destroy
    // `direct_from_seller_request_seller_signals` and thus abort this
    // callback, so it's safe to use Unretained(this) and `report_win_task`
    // here.
    report_win_task->direct_from_seller_request_per_buyer_signals =
        direct_from_seller_requester_per_buyer_signals_.LoadSignals(
            *url_loader_factory_, *direct_from_seller_per_buyer_signals,
            base::BindOnce(
                &BidderWorklet::
                    OnDirectFromSellerPerBuyerSignalsDownloadedReportWin,
                base::Unretained(this), report_win_task));
  } else {
    report_win_task->direct_from_seller_result_per_buyer_signals =
        DirectFromSellerSignalsRequester::Result();
  }

  if (direct_from_seller_auction_signals) {
    // Deleting `report_win_task` will destroy
    // `direct_from_seller_request_auction_signals` and thus abort this
    // callback, so it's safe to use Unretained(this) and `report_win_task`
    // here.
    report_win_task->direct_from_seller_request_auction_signals =
        direct_from_seller_requester_auction_signals_.LoadSignals(
            *url_loader_factory_, *direct_from_seller_auction_signals,
            base::BindOnce(
                &BidderWorklet::
                    OnDirectFromSellerAuctionSignalsDownloadedReportWin,
                base::Unretained(this), report_win_task));
  } else {
    report_win_task->direct_from_seller_result_auction_signals =
        DirectFromSellerSignalsRequester::Result();
  }
  report_win_task->trace_wait_deps_start = base::TimeTicks::Now();
  report_win_task->direct_from_seller_per_buyer_signals_header_ad_slot =
      direct_from_seller_per_buyer_signals_header_ad_slot;
  report_win_task->direct_from_seller_auction_signals_header_ad_slot =
      direct_from_seller_auction_signals_header_ad_slot;

  TRACE_EVENT_BEGIN("fledge", "wait_report_win_deps",
                    perfetto::Track(trace_id));
  RunReportWinIfReady(report_win_task);
}

void BidderWorklet::ConnectDevToolsAgent(
    mojo::PendingAssociatedReceiver<blink::mojom::DevToolsAgent> agent,
    uint32_t thread_index) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_);

  v8_runners_[thread_index]->PostTask(
      FROM_HERE, base::BindOnce(&V8State::ConnectDevToolsAgent,
                                base::Unretained(v8_state_[thread_index].get()),
                                std::move(agent)));
}

void BidderWorklet::FinishGenerateBid(
    const std::optional<std::string>& auction_signals_json,
    const std::optional<std::string>& per_buyer_signals_json,
    const std::optional<base::TimeDelta> per_buyer_timeout,
    const std::optional<blink::AdCurrency>& expected_buyer_currency,
    const std::optional<GURL>& direct_from_seller_per_buyer_signals,
    const std::optional<std::string>&
        direct_from_seller_per_buyer_signals_header_ad_slot,
    const std::optional<GURL>& direct_from_seller_auction_signals,
    const std::optional<std::string>&
        direct_from_seller_auction_signals_header_ad_slot) {
  CHECK((!direct_from_seller_per_buyer_signals &&
         !direct_from_seller_auction_signals) ||
        (!direct_from_seller_per_buyer_signals_header_ad_slot &&
         !direct_from_seller_auction_signals_header_ad_slot));

  GenerateBidTaskList::iterator task = finalize_receiver_set_.current_context();
  task->auction_signals_json = auction_signals_json;
  task->per_buyer_signals_json = per_buyer_signals_json;
  task->per_buyer_timeout = per_buyer_timeout;
  task->expected_buyer_currency = expected_buyer_currency;
  task->finalize_generate_bid_called = true;
  task->direct_from_seller_per_buyer_signals_header_ad_slot =
      direct_from_seller_per_buyer_signals_header_ad_slot;
  task->direct_from_seller_auction_signals_header_ad_slot =
      direct_from_seller_auction_signals_header_ad_slot;
  HandleDirectFromSellerForGenerateBid(direct_from_seller_per_buyer_signals,
                                       direct_from_seller_auction_signals,
                                       task);

  finalize_receiver_set_.Remove(*task->finalize_generate_bid_receiver_id);
  task->finalize_generate_bid_receiver_id = std::nullopt;
  task->wait_promises = base::TimeTicks::Now() - task->trace_wait_deps_start;
  GenerateBidIfReady(task);
  if (!finalized_any_bid_) {
    finalized_any_bid_ = true;
    SetEagerJsCompilation(true);
    if (features::kFledgeWaitForPromisesToPrepareContexts.Get()) {
      MaybePrepareContexts();
    }
  }
}

BidderWorklet::GenerateBidTask::GenerateBidTask() = default;
BidderWorklet::GenerateBidTask::~GenerateBidTask() = default;

BidderWorklet::ReportWinTask::ReportWinTask() = default;
BidderWorklet::ReportWinTask::~ReportWinTask() = default;

BidderWorklet::V8State::V8State(
    scoped_refptr<AuctionV8Helper> v8_helper,
    scoped_refptr<AuctionV8Helper::DebugId> debug_id,
    mojo::PendingRemote<mojom::AuctionSharedStorageHost>
        shared_storage_host_remote,
    const GURL& script_source_url,
    const url::Origin& top_window_origin,
    mojom::AuctionWorkletPermissionsPolicyStatePtr permissions_policy_state,
    const std::optional<GURL>& wasm_helper_url,
    const std::optional<GURL>& trusted_bidding_signals_url,
    base::WeakPtr<BidderWorklet> parent)
    : v8_helper_(std::move(v8_helper)),
      debug_id_(std::move(debug_id)),
      parent_(std::move(parent)),
      user_thread_(base::SequencedTaskRunner::GetCurrentDefault()),
      owner_(url::Origin::Create(script_source_url)),
      script_source_url_(script_source_url),
      top_window_origin_(top_window_origin),
      permissions_policy_state_(std::move(permissions_policy_state)),
      wasm_helper_url_(wasm_helper_url),
      trusted_bidding_signals_url_(trusted_bidding_signals_url),
      trusted_bidding_signals_origin_(
          trusted_bidding_signals_url_ ? std::make_optional(url::Origin::Create(
                                             *trusted_bidding_signals_url_))
                                       : std::nullopt),
      execution_mode_helper_(/*is_seller=*/false) {
  DETACH_FROM_SEQUENCE(v8_sequence_checker_);
  v8_helper_->v8_runner()->PostTask(
      FROM_HERE, base::BindOnce(&V8State::FinishInit, base::Unretained(this),
                                std::move(shared_storage_host_remote)));
}

void BidderWorklet::V8State::SetWorkletScript(
    WorkletLoader::Result worklet_script) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_);
  worklet_script_ = WorkletLoader::TakeScript(std::move(worklet_script));
}

void BidderWorklet::V8State::SetWasmHelper(
    WorkletWasmLoader::Result wasm_helper) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_);
  wasm_helper_ = std::move(wasm_helper);
}

BidderWorklet::V8State::SingleGenerateBidResult::SingleGenerateBidResult() =
    default;
BidderWorklet::V8State::SingleGenerateBidResult::SingleGenerateBidResult(
    std::unique_ptr<ContextRecycler> context_recycler_for_rerun,
    std::vector<SetBidBindings::BidAndWorkletOnlyMetadata> bids,
    std::optional<uint32_t> bidding_signals_data_version,
    std::optional<GURL> debug_loss_report_url,
    std::optional<GURL> debug_win_report_url,
    std::optional<double> set_priority,
    base::flat_map<std::string, mojom::PrioritySignalsDoublePtr>
        update_priority_signals_overrides,
    PrivateAggregationRequests pa_requests,
    RealTimeReportingContributions real_time_contributions,
    mojom::RejectReason reject_reason,
    bool script_timed_out,
    std::vector<std::string> error_msgs)
    : context_recycler_for_rerun(std::move(context_recycler_for_rerun)),
      bids(std::move(bids)),
      bidding_signals_data_version(std::move(bidding_signals_data_version)),
      debug_loss_report_url(std::move(debug_loss_report_url)),
      debug_win_report_url(std::move(debug_win_report_url)),
      set_priority(std::move(set_priority)),
      update_priority_signals_overrides(
          std::move(update_priority_signals_overrides)),
      pa_requests(std::move(pa_requests)),
      real_time_contributions(std::move(real_time_contributions)),
      reject_reason(reject_reason),
      script_timed_out(script_timed_out),
      error_msgs(std::move(error_msgs)) {}

BidderWorklet::V8State::SingleGenerateBidResult::SingleGenerateBidResult(
    SingleGenerateBidResult&&) = default;
BidderWorklet::V8State::SingleGenerateBidResult::~SingleGenerateBidResult() =
    default;
BidderWorklet::V8State::SingleGenerateBidResult&
BidderWorklet::V8State::SingleGenerateBidResult::operator=(
    SingleGenerateBidResult&&) = default;

bool BidderWorklet::V8State::SetBrowserSignals(
    ContextRecycler& context_recycler,
    v8::Local<v8::Context>& context,
    bool is_for_additional_bid,
    const std::optional<std::string>& interest_group_name_reporting_id,
    const std::optional<std::string>& buyer_reporting_id,
    const std::optional<std::string>& buyer_and_seller_reporting_id,
    const std::optional<std::string>& selected_buyer_and_seller_reporting_id,
    const GURL& browser_signal_render_url,
    DeprecatedUrlLazyFiller* deprecated_render_url,
    double browser_signal_bid,
    const std::optional<blink::AdCurrency>& browser_signal_bid_currency,
    double browser_signal_highest_scoring_other_bid,
    const std::optional<blink::AdCurrency>&
        browser_signal_highest_scoring_other_bid_currency,
    bool browser_signal_made_highest_scoring_other_bid,
    const std::optional<double>& browser_signal_ad_cost,
    const std::optional<uint16_t>& browser_signal_modeling_signals,
    uint8_t browser_signal_join_count,
    uint8_t browser_signal_recency,
    const url::Origin& browser_signal_seller_origin,
    const std::optional<url::Origin>& browser_signal_top_level_seller_origin,
    const std::optional<uint32_t>& bidding_signals_data_version,
    const std::string& kanon_status,
    const base::TimeDelta reporting_timeout,
    v8::Local<v8::Object> browser_signals,
    gin::Dictionary& browser_signals_dict) {
  if (!browser_signals_dict.Set("topWindowHostname",
                                top_window_origin_.host()) ||
      !browser_signals_dict.Set(
          "interestGroupOwner",
          url::Origin::Create(script_source_url_).Serialize()) ||
      (interest_group_name_reporting_id.has_value() &&
       !browser_signals_dict.Set("interestGroupName",
                                 *interest_group_name_reporting_id)) ||
      (buyer_reporting_id.has_value() &&
       !browser_signals_dict.Set("buyerReportingId", *buyer_reporting_id)) ||
      (buyer_and_seller_reporting_id.has_value() &&
       !browser_signals_dict.Set("buyerAndSellerReportingId",
                                 *buyer_and_seller_reporting_id)) ||
      (selected_buyer_and_seller_reporting_id.has_value() &&
       !browser_signals_dict.Set("selectedBuyerAndSellerReportingId",
                                 *selected_buyer_and_seller_reporting_id)) ||
      !browser_signals_dict.Set("renderURL",
                                browser_signal_render_url.spec()) ||
      // TODO(crbug.com/40266734): Remove deprecated `renderUrl` alias.
      (deprecated_render_url && !deprecated_render_url->AddDeprecatedUrlGetter(
                                    browser_signals, "renderUrl")) ||
      !browser_signals_dict.Set("bid", browser_signal_bid) ||
      !browser_signals_dict.Set(
          "bidCurrency",
          blink::PrintableAdCurrency(browser_signal_bid_currency)) ||
      (browser_signal_ad_cost.has_value() &&
       !browser_signals_dict.Set("adCost", *browser_signal_ad_cost)) ||
      !browser_signals_dict.Set("highestScoringOtherBid",
                                browser_signal_highest_scoring_other_bid) ||
      !browser_signals_dict.Set(
          "highestScoringOtherBidCurrency",
          blink::PrintableAdCurrency(
              browser_signal_highest_scoring_other_bid_currency)) ||
      !browser_signals_dict.Set(
          "madeHighestScoringOtherBid",
          browser_signal_made_highest_scoring_other_bid) ||
      !browser_signals_dict.Set("seller",
                                browser_signal_seller_origin.Serialize()) ||
      (browser_signal_top_level_seller_origin &&
       !browser_signals_dict.Set(
           "topLevelSeller",
           browser_signal_top_level_seller_origin->Serialize())) ||
      (bidding_signals_data_version.has_value() &&
       !browser_signals_dict.Set("dataVersion",
                                 bidding_signals_data_version.value())) ||
      (!kanon_status.empty() &&
       !browser_signals_dict.Set("kAnonStatus", kanon_status)) ||
      (!browser_signals_dict.Set("reportingTimeout",
                                 reporting_timeout.InMilliseconds()))) {
    return false;
  }

  if (!context_recycler.report_win_lazy_filler()->FillInObject(
          browser_signal_modeling_signals, browser_signal_join_count,
          !is_for_additional_bid
              ? std::optional<uint8_t>(browser_signal_recency)
              : std::nullopt,
          browser_signals)) {
    return false;
  }
  return true;
}

bool BidderWorklet::V8State::SetReportAggregateWinArgs(
    v8::Local<v8::Context>& context,
    ContextRecycler& context_recycler,
    const std::optional<std::string>& auction_signals_json,
    const std::optional<std::string>& per_buyer_signals_json,
    const std::string& seller_signals_json,
    bool is_for_additional_bid,
    const std::optional<std::string>& interest_group_name_reporting_id,
    const std::optional<std::string>& buyer_reporting_id,
    const std::optional<std::string>& buyer_and_seller_reporting_id,
    const std::optional<std::string>& selected_buyer_and_seller_reporting_id,
    const GURL& browser_signal_render_url,
    double browser_signal_bid,
    const std::optional<blink::AdCurrency>& browser_signal_bid_currency,
    double browser_signal_highest_scoring_other_bid,
    const std::optional<blink::AdCurrency>&
        browser_signal_highest_scoring_other_bid_currency,
    bool browser_signal_made_highest_scoring_other_bid,
    const std::optional<double>& browser_signal_ad_cost,
    const std::optional<uint16_t>& browser_signal_modeling_signals,
    uint8_t browser_signal_join_count,
    uint8_t browser_signal_recency,
    const url::Origin& browser_signal_seller_origin,
    const std::optional<url::Origin>& browser_signal_top_level_seller_origin,
    const std::optional<base::TimeDelta> browser_signal_reporting_timeout,
    const std::optional<uint32_t>& bidding_signals_data_version,
    const std::string& kanon_status,
    const std::optional<std::string>& aggregate_win_signals,
    const base::TimeDelta reporting_timeout,
    const v8::Local<v8::Value>& per_buyer_signals,
    const v8::Local<v8::Value>& auction_signals,
    const ReportBindings::ModelingSignalsConfig& modeling_signals_config,
    v8::LocalVector<v8::Value>& report_aggregate_win_args) {
  v8::Isolate* isolate = v8_helper_->isolate();
  v8::Local<v8::Object> report_aggregate_win_browser_signals =
      v8::Object::New(isolate);
  gin::Dictionary report_aggregate_win_browser_signals_dict(
      isolate, report_aggregate_win_browser_signals);

  v8::Local<v8::Object> report_aggregate_win_direct_from_seller_signals =
      v8::Object::New(isolate);
  gin::Dictionary report_aggregate_win_direct_from_seller_signals_dict(
      isolate, report_aggregate_win_direct_from_seller_signals);

  v8::Local<v8::Object> modeling_signals_config_v8 = v8::Object::New(isolate);
  gin::Dictionary modeling_signals_config_dict(isolate,
                                               modeling_signals_config_v8);

  v8::Local<v8::Value> aggregate_win_signals_v8 = v8::Null(isolate);
  if (aggregate_win_signals) {
    if (!v8_helper_->CreateValueFromJson(context, aggregate_win_signals.value())
             .ToLocal(&aggregate_win_signals_v8)) {
      return false;
    }
    report_aggregate_win_args.push_back(aggregate_win_signals_v8);
  }

  modeling_signals_config_dict.Set("destination",
                                   modeling_signals_config.destination.spec());
  modeling_signals_config_dict.Set(
      "aggregationCoordinatorOrigin",
      modeling_signals_config.aggregation_coordinator_origin.spec());
  modeling_signals_config_dict.Set("payloadLength",
                                   modeling_signals_config.payload_length);
  report_aggregate_win_args.push_back(modeling_signals_config_v8);

  if (!AppendJsonValueOrNull(v8_helper_.get(), context,
                             base::OptionalToPtr(auction_signals_json),
                             &report_aggregate_win_args) ||
      !AppendJsonValueOrNull(v8_helper_.get(), context,
                             base::OptionalToPtr(per_buyer_signals_json),
                             &report_aggregate_win_args) ||
      !v8_helper_->AppendJsonValue(context, seller_signals_json,
                                   &report_aggregate_win_args)) {
    return false;
  }

  if (!SetBrowserSignals(
          context_recycler, context, is_for_additional_bid,
          interest_group_name_reporting_id, buyer_reporting_id,
          buyer_and_seller_reporting_id, selected_buyer_and_seller_reporting_id,
          browser_signal_render_url,
          /*deprecated_render_url=*/nullptr, browser_signal_bid,
          browser_signal_bid_currency, browser_signal_highest_scoring_other_bid,
          browser_signal_highest_scoring_other_bid_currency,
          browser_signal_made_highest_scoring_other_bid, browser_signal_ad_cost,
          browser_signal_modeling_signals, browser_signal_join_count,
          browser_signal_recency, browser_signal_seller_origin,
          browser_signal_top_level_seller_origin, bidding_signals_data_version,
          kanon_status, reporting_timeout, report_aggregate_win_browser_signals,
          report_aggregate_win_browser_signals_dict)) {
    return false;
  }
  report_aggregate_win_args.push_back(report_aggregate_win_browser_signals);

  if (!report_aggregate_win_direct_from_seller_signals_dict.Set(
          "perBuyerSignals", per_buyer_signals) ||
      !report_aggregate_win_direct_from_seller_signals_dict.Set(
          "auctionSignals", auction_signals)) {
    return false;
  }
  report_aggregate_win_args.push_back(
      report_aggregate_win_direct_from_seller_signals);

  return true;
}

void BidderWorklet::V8State::ReportWin(
    bool is_for_additional_bid,
    const std::optional<std::string>& interest_group_name_reporting_id,
    const std::optional<std::string>& buyer_reporting_id,
    const std::optional<std::string>& buyer_and_seller_reporting_id,
    const std::optional<std::string>& selected_buyer_and_seller_reporting_id,
    const std::optional<std::string>& auction_signals_json,
    const std::optional<std::string>& per_buyer_signals_json,
    DirectFromSellerSignalsRequester::Result
        direct_from_seller_result_per_buyer_signals,
    const std::optional<std::string>&
        direct_from_seller_per_buyer_signals_header_ad_slot,
    DirectFromSellerSignalsRequester::Result
        direct_from_seller_result_auction_signals,
    const std::optional<std::string>&
        direct_from_seller_auction_signals_header_ad_slot,
    const std::string& seller_signals_json,
    mojom::KAnonymityStatus kanon_status,
    const GURL& browser_signal_render_url,
    double browser_signal_bid,
    const std::optional<blink::AdCurrency>& browser_signal_bid_currency,
    double browser_signal_highest_scoring_other_bid,
    const std::optional<blink::AdCurrency>&
        browser_signal_highest_scoring_other_bid_currency,
    bool browser_signal_made_highest_scoring_other_bid,
    const std::optional<double>& browser_signal_ad_cost,
    const std::optional<uint16_t>& browser_signal_modeling_signals,
    uint8_t browser_signal_join_count,
    uint8_t browser_signal_recency,
    const url::Origin& browser_signal_seller_origin,
    const std::optional<url::Origin>& browser_signal_top_level_seller_origin,
    const std::optional<base::TimeDelta> browser_signal_reporting_timeout,
    const std::optional<uint32_t>& bidding_signals_data_version,
    const std::optional<std::string>& aggregate_win_signals,
    uint64_t trace_id,
    ReportWinCallbackInternal callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_);
  // Matching TRACE_EVENT_BEGIN("fledge", "post_v8_task", ...);
  TRACE_EVENT_END("fledge", perfetto::Track(trace_id));
  base::ElapsedTimer elapsed_timer;

  // We may not be allowed any time to run.
  if (browser_signal_reporting_timeout.has_value() &&
      !browser_signal_reporting_timeout->is_positive()) {
    PostReportWinCallbackToUserThread(
        std::move(callback),
        /*report_url=*/std::nullopt,
        /*ad_beacon_map=*/{},
        /*ad_macro_map=*/{},
        /*pa_requests=*/{}, /*pmt_request_data=*/nullptr, base::TimeDelta(),
        /*script_timed_out=*/true,
        /*errors=*/{"reportWin() aborted due to zero timeout."});
    return;
  }

  AuctionV8Helper::FullIsolateScope isolate_scope(v8_helper_.get());
  v8::Isolate* isolate = v8_helper_->isolate();

  // Short lived context, to avoid leaking data at global scope between either
  // repeated calls to this worklet, or to calls to any other worklet.
  ContextRecycler context_recycler(v8_helper_.get());

  ContextRecyclerScope context_recycler_scope(context_recycler);
  v8::Local<v8::Context> context = context_recycler_scope.GetContext();
  AuctionV8Logger v8_logger(v8_helper_.get(), context);

  // We want this before RunScript, both because it's meant to be visible
  // to globals, and because we don't want to overwrite existing globals.
  context_recycler.AddTextConversionHelpers();

  v8::LocalVector<v8::Value> args(isolate);
  if (!AppendJsonValueOrNull(v8_helper_.get(), context,
                             base::OptionalToPtr(auction_signals_json),
                             &args) ||
      !AppendJsonValueOrNull(v8_helper_.get(), context,
                             base::OptionalToPtr(per_buyer_signals_json),
                             &args) ||
      !v8_helper_->AppendJsonValue(context, seller_signals_json, &args)) {
    PostReportWinCallbackToUserThread(
        std::move(callback),
        /*report_url=*/std::nullopt,
        /*ad_beacon_map=*/{},
        /*ad_macro_map=*/{},
        /*pa_requests=*/{}, /*pmt_request_data=*/nullptr, base::TimeDelta(),
        /*script_timed_out=*/false,
        /*errors=*/std::vector<std::string>());
    return;
  }

  v8::Local<v8::Object> browser_signals = v8::Object::New(isolate);
  gin::Dictionary browser_signals_dict(isolate, browser_signals);

  std::string kanon_status_str;
  switch (kanon_status) {
    case mojom::KAnonymityStatus::kUnknown:
      kanon_status_str = "notCalculated";
      break;
    case mojom::KAnonymityStatus::kBelowThreshold:
      kanon_status_str = "belowThreshold";
      break;
    case mojom::KAnonymityStatus::kPassingNotEnforced:
      kanon_status_str = "passedNotEnforced";
      break;
    case mojom::KAnonymityStatus::kPassingAndEnforced:
      kanon_status_str = "passedAndEnforced";
      break;
  }

  context_recycler.AddReportWinBrowserSignalsLazyFiller();

  DeprecatedUrlLazyFiller deprecated_render_url(
      v8_helper_.get(), &v8_logger, &browser_signal_render_url,
      "browserSignals.renderUrl is deprecated."
      " Please use browserSignals.renderURL instead.");
  base::TimeDelta reporting_timeout =
      browser_signal_reporting_timeout.has_value()
          ? *browser_signal_reporting_timeout
          : AuctionV8Helper::kScriptTimeout;
  const bool browser_signals_set = SetBrowserSignals(
      context_recycler, context, is_for_additional_bid,
      interest_group_name_reporting_id, buyer_reporting_id,
      buyer_and_seller_reporting_id, selected_buyer_and_seller_reporting_id,
      browser_signal_render_url, &deprecated_render_url, browser_signal_bid,
      browser_signal_bid_currency, browser_signal_highest_scoring_other_bid,
      browser_signal_highest_scoring_other_bid_currency,
      browser_signal_made_highest_scoring_other_bid, browser_signal_ad_cost,
      browser_signal_modeling_signals, browser_signal_join_count,
      browser_signal_recency, browser_signal_seller_origin,
      browser_signal_top_level_seller_origin, bidding_signals_data_version,
      kanon_status_str, reporting_timeout, browser_signals,
      browser_signals_dict);
  if (!browser_signals_set) {
    PostReportWinCallbackToUserThread(
        std::move(callback),
        /*report_url=*/std::nullopt,
        /*ad_beacon_map=*/{},
        /*ad_macro_map=*/{},
        /*pa_requests=*/{}, /*pmt_request_data=*/nullptr, base::TimeDelta(),
        /*script_timed_out=*/false,
        /*errors=*/std::vector<std::string>());
    return;
  }

  args.push_back(browser_signals);

  std::vector<std::string> errors_out;
  v8::Local<v8::Object> direct_from_seller_signals = v8::Object::New(isolate);
  gin::Dictionary direct_from_seller_signals_dict(isolate,
                                                  direct_from_seller_signals);
  v8::Local<v8::Value> per_buyer_signals = GetDirectFromSellerSignals(
      direct_from_seller_result_per_buyer_signals,
      direct_from_seller_per_buyer_signals_header_ad_slot, *v8_helper_, context,
      errors_out);
  v8::Local<v8::Value> auction_signals = GetDirectFromSellerSignals(
      direct_from_seller_result_auction_signals,
      direct_from_seller_auction_signals_header_ad_slot, *v8_helper_, context,
      errors_out);
  if (!direct_from_seller_signals_dict.Set("perBuyerSignals",
                                           per_buyer_signals) ||
      !direct_from_seller_signals_dict.Set("auctionSignals", auction_signals)) {
    PostReportWinCallbackToUserThread(std::move(callback),
                                      /*report_url=*/std::nullopt,
                                      /*ad_beacon_map=*/{},
                                      /*ad_macro_map=*/{}, /*pa_requests=*/{},
                                      /*pmt_request_data=*/nullptr,
                                      base::TimeDelta(),
                                      /*script_timed_out=*/false,
                                      /*errors=*/std::move(errors_out));
    return;
  }
  args.push_back(direct_from_seller_signals);

  // An empty return value indicates an exception was thrown. Any other return
  // value indicates no exception.
  v8_helper_->MaybeTriggerInstrumentationBreakpoint(
      *debug_id_, "beforeBidderWorkletReportingStart");

  v8::Local<v8::UnboundScript> unbound_worklet_script =
      worklet_script_.Get(isolate);
  TRACE_EVENT_BEGIN("fledge", "report_win", perfetto::Track(trace_id));
  std::unique_ptr<AuctionV8Helper::TimeLimit> total_timeout =
      v8_helper_->CreateTimeLimit(
          /*script_timeout=*/browser_signal_reporting_timeout);
  AuctionV8Helper::Result result =
      v8_helper_->RunScript(context, unbound_worklet_script, debug_id_.get(),
                            total_timeout.get(), errors_out);
  if (result != AuctionV8Helper::Result::kSuccess) {
    TRACE_EVENT_END("fledge", perfetto::Track(trace_id));
    PostReportWinCallbackToUserThread(
        std::move(callback), /*report_url=*/std::nullopt,
        /*ad_beacon_map=*/{}, /*ad_macro_map=*/{},
        /*pa_requests=*/{}, /*pmt_request_data=*/nullptr,
        elapsed_timer.Elapsed(),
        /*script_timed_out=*/result == AuctionV8Helper::Result::kTimeout,
        std::move(errors_out));
    return;
  }

  context_recycler.AddReportBindings(
      /*queue_report_aggregate_win_allowed=*/true);
  context_recycler.AddRegisterAdBeaconBindings();
  context_recycler.AddRegisterAdMacroBindings();

  context_recycler.AddPrivateAggregationBindings(
      permissions_policy_state_->private_aggregation_allowed,
      /*reserved_once_allowed=*/false);

  if (base::FeatureList::IsEnabled(network::features::kSharedStorageAPI)) {
    context_recycler.AddSharedStorageBindings(
        shared_storage_host_remote_.is_bound()
            ? shared_storage_host_remote_.get()
            : nullptr,
        mojom::AuctionWorkletFunction::kBidderReportWin,
        permissions_policy_state_->shared_storage_allowed);
  }

  v8::MaybeLocal<v8::Value> maybe_report_result_ret;
  result = v8_helper_->CallFunction(
      context, debug_id_.get(),
      v8_helper_->FormatScriptName(unbound_worklet_script),
      is_for_additional_bid ? "reportAdditionalBidWin" : "reportWin", args,
      total_timeout.get(), maybe_report_result_ret, errors_out);
  TRACE_EVENT_END("fledge", perfetto::Track(trace_id));
  base::TimeDelta elapsed = elapsed_timer.Elapsed();
  base::UmaHistogramTimes("Ads.InterestGroup.Auction.ReportWinTime", elapsed);

  if (result != AuctionV8Helper::Result::kSuccess) {
    // Keep Private Aggregation API requests since `reportWin()` might use it to
    // detect script timeout or failures.
    PostReportWinCallbackToUserThread(
        std::move(callback), /*report_url=*/std::nullopt,
        /*ad_beacon_map=*/{}, /*ad_macro_map=*/{},
        context_recycler.private_aggregation_bindings()
            ->TakePrivateAggregationRequests(
                /*did_uncaught_error_occur=*/result ==
                AuctionV8Helper::Result::kFailure),
        /*pmt_request_data=*/nullptr, elapsed,
        /*script_timed_out=*/result == AuctionV8Helper::Result::kTimeout,
        std::move(errors_out));
    return;
  }

  mojom::PrivateModelTrainingRequestDataPtr maybe_pmt_request_data;
  // If there was a config that means they called `queueReportAggregateWin()`
  if (context_recycler.report_bindings()
          ->modeling_signals_config()
          .has_value()) {
    // Allow `sendEncryptedTo()` to be called, if `modelingSignals`,
    // `joinCount`, and `recency` were not accessed within `reportWin()`.
    if (!context_recycler.report_win_lazy_filler()
             ->ShouldBlockSendEncrypted()) {
      if (context_recycler.report_bindings()
              ->modeling_signals_config()
              ->payload_length <= kMaxPayloadLength) {
        context_recycler.AddPrivateModelTrainingBindings();

      } else {
        errors_out.push_back(
            "queueReportAggregateWin(): modelingSignalsConfig's "
            "`payloadLength` may not exceed " +
            base::NumberToString(kMaxPayloadLength) + ".");
      }
    }

    v8::LocalVector<v8::Value> report_aggregate_win_args(isolate);

    const bool report_aggregate_win_signals_set = SetReportAggregateWinArgs(
        context, context_recycler, auction_signals_json, per_buyer_signals_json,
        seller_signals_json, is_for_additional_bid,
        interest_group_name_reporting_id, buyer_reporting_id,
        buyer_and_seller_reporting_id, selected_buyer_and_seller_reporting_id,
        browser_signal_render_url, browser_signal_bid,
        browser_signal_bid_currency, browser_signal_highest_scoring_other_bid,
        browser_signal_highest_scoring_other_bid_currency,
        browser_signal_made_highest_scoring_other_bid, browser_signal_ad_cost,
        browser_signal_modeling_signals, browser_signal_join_count,
        browser_signal_recency, browser_signal_seller_origin,
        browser_signal_top_level_seller_origin,
        browser_signal_reporting_timeout, bidding_signals_data_version,
        kanon_status_str, aggregate_win_signals, reporting_timeout,
        per_buyer_signals, auction_signals,
        context_recycler.report_bindings()->modeling_signals_config().value(),
        report_aggregate_win_args);

    if (report_aggregate_win_signals_set) {
      // TODO(crbug.com/408002788): We should not reuse the same
      // PrivateAggregationBindings here.
      v8::MaybeLocal<v8::Value> maybe_report_aggregate_win_result_ret;
      result = v8_helper_->CallFunction(
          context, debug_id_.get(),
          v8_helper_->FormatScriptName(unbound_worklet_script),
          "reportAggregateWin", report_aggregate_win_args, total_timeout.get(),
          maybe_report_aggregate_win_result_ret, errors_out);
    }

    if (context_recycler.private_model_training_bindings()) {
      // To prevent leaking a bit, we always send a request if
      // `reportAggregateWin()` is called. Therefore, even if
      // `sendEncryptedTo()` is not called or `reportAggregateWin()` encounters
      // an error/timeout, we send the data with an empty payload. We also send
      // data with an empty payload if the payload's size exceeds the specified
      // `payload_length`.
      mojo_base::BigBuffer maybe_payload =
          context_recycler.private_model_training_bindings()
              ->TakePayload()
              .value_or(mojo_base::BigBuffer(0));
      if (maybe_payload.size() > context_recycler.report_bindings()
                                     ->modeling_signals_config()
                                     ->payload_length) {
        errors_out.push_back(
            "sendEncryptedTo(): payload size may not exceed the specified "
            "modelingSignalsConfig.payloadLength.");
        maybe_payload = mojo_base::BigBuffer(0);
      }

      maybe_pmt_request_data = mojom::PrivateModelTrainingRequestData::New(
          /*payload=*/std::move(maybe_payload),
          /*payload_length=*/
          context_recycler.report_bindings()
              ->modeling_signals_config()
              ->payload_length,
          /*aggregation_coordinator_origin=*/
          context_recycler.report_bindings()
              ->modeling_signals_config()
              ->aggregation_coordinator_origin,
          /*destination=*/
          context_recycler.report_bindings()
              ->modeling_signals_config()
              ->destination);
    }
  }
  // This covers both the case where a report URL was provided, and the case one
  // was not.
  PostReportWinCallbackToUserThread(
      std::move(callback), context_recycler.report_bindings()->report_url(),
      context_recycler.register_ad_beacon_bindings()->TakeAdBeaconMap(),
      context_recycler.register_ad_macro_bindings()->TakeAdMacroMap(),
      context_recycler.private_aggregation_bindings()
          ->TakePrivateAggregationRequests(
              /*did_uncaught_error_occur=*/false),
      std::move(maybe_pmt_request_data), elapsed,
      /*script_timed_out=*/false, std::move(errors_out));
}

void BidderWorklet::V8State::GenerateBid(
    mojom::BidderWorkletNonSharedParamsPtr bidder_worklet_non_shared_params,
    mojom::KAnonymityBidMode kanon_mode,
    const std::optional<std::string>& auction_signals_json,
    const std::optional<std::string>& per_buyer_signals_json,
    DirectFromSellerSignalsRequester::Result
        direct_from_seller_result_per_buyer_signals,
    const std::optional<std::string>&
        direct_from_seller_per_buyer_signals_header_ad_slot,
    DirectFromSellerSignalsRequester::Result
        direct_from_seller_result_auction_signals,
    const std::optional<std::string>&
        direct_from_seller_auction_signals_header_ad_slot,
    const std::optional<base::TimeDelta> per_buyer_timeout,
    const std::optional<blink::AdCurrency>& expected_buyer_currency,
    const url::Origin& browser_signal_seller_origin,
    const std::optional<url::Origin>& browser_signal_top_level_seller_origin,
    const base::TimeDelta browser_signal_recency,
    bool browser_signal_for_debugging_only_sampling,
    blink::mojom::BiddingBrowserSignalsPtr bidding_browser_signals,
    base::Time auction_start_time,
    const std::optional<blink::AdSize>& requested_ad_size,
    uint16_t multi_bid_limit,
    uint64_t group_by_origin_id,
    scoped_refptr<TrustedSignals::Result> trusted_bidding_signals_result,
    bool trusted_bidding_signals_fetch_failed,
    uint64_t trace_id,
    base::ScopedClosureRunner cleanup_generate_bid_task,
    GenerateBidCallbackInternal callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_);
  // Matching TRACE_EVENT_BEGIN("fledge", "post_v8_task", ...);
  TRACE_EVENT_END("fledge", perfetto::Track(trace_id));

  // Don't need to run `cleanup_generate_bid_task` if this method is invoked;
  // it's bound to the closure to clean things up if this method got cancelled.
  cleanup_generate_bid_task.ReplaceClosure(base::OnceClosure());

  base::TimeTicks bidding_start = base::TimeTicks::Now();
  std::optional<SingleGenerateBidResult> result = RunGenerateBidOnce(
      *bidder_worklet_non_shared_params,
      base::OptionalToPtr(auction_signals_json),
      base::OptionalToPtr(per_buyer_signals_json),
      direct_from_seller_result_per_buyer_signals,
      direct_from_seller_per_buyer_signals_header_ad_slot,
      direct_from_seller_result_auction_signals,
      direct_from_seller_auction_signals_header_ad_slot, per_buyer_timeout,
      expected_buyer_currency, browser_signal_seller_origin,
      base::OptionalToPtr(browser_signal_top_level_seller_origin),
      browser_signal_recency, browser_signal_for_debugging_only_sampling,
      bidding_browser_signals, auction_start_time, requested_ad_size,
      multi_bid_limit, group_by_origin_id, trusted_bidding_signals_result,
      trace_id,
      /*context_recycler_for_rerun=*/nullptr,
      /*restrict_to_kanon_ads=*/false);

  if (!result.has_value()) {
    PostErrorBidCallbackToUserThread(
        std::move(callback),
        /*bidding_latency=*/base::TimeTicks::Now() - bidding_start,
        PrivateAggregationRequests(),
        GetRealTimeReportingContributionsOnError(
            trusted_bidding_signals_fetch_failed, /*is_bidding_signal=*/true));
    return;
  }

  std::vector<mojom::BidderWorkletBidPtr> bids =
      ClassifyBidsAndApplyComponentAdLimits(
          kanon_mode, bidder_worklet_non_shared_params.get(),
          script_source_url_, std::move(result->bids));

  if (kanon_mode != mojom::KAnonymityBidMode::kNone) {
    // Go through and see which bids are actually k-anon appropriate.
    bool found_kanon_bid = false;
    for (const auto& bid : bids) {
      if (bid->bid_role != mojom::BidRole::kUnenforcedKAnon) {
        found_kanon_bid = true;
        break;
      }
    }

    // If bids were returned, and none were k-anon, we re-run the script with
    // only k-anon ads available to it.
    //
    // At this point, we don't need to worry about the impact of k-anonymity
    // for reporting on k-anon where a bid returns a value for
    // `selectedBuyerAndSellerReportingId`. Assuming we find some
    // k-anon ads in the loop below that would encourage us to make the
    // k-anon-restricted call to `generateBid()`, we're only going to pass
    // `selectableBuyerAndSellerReportingIds` that would, in combination with
    // the renderUrl and other reporting ids, cause that bid to be k-anonymous
    // for reporting. If no `selectableBuyerAndSellerReportingIds` meet that
    // criteria, that's still fine: The bid could be returned with no
    // `selectedBuyerAndSellerReportingId`, such that the k-anonymous bid
    // would remain k-anonymous, even if it isn't k-anonymous for reporting.
    if (!bids.empty() && !found_kanon_bid) {
      bool has_kanon_ads = false;
      for (const auto& ad : *bidder_worklet_non_shared_params->ads) {
        if (BidderWorklet::IsKAnon(
                bidder_worklet_non_shared_params.get(),
                blink::HashedKAnonKeyForAdBid(owner_, script_source_url_,
                                              ad.render_url()))) {
          has_kanon_ads = true;
          break;
        }
      }

      // Main run got a non-k-anon result, and we care about k-anonymity. Re-run
      // the bidder with non-k-anon ads hidden, limiting it to a single bid.
      std::optional<SingleGenerateBidResult> restricted_result;
      if (has_kanon_ads) {
        restricted_result = RunGenerateBidOnce(
            *bidder_worklet_non_shared_params.get(),
            base::OptionalToPtr(auction_signals_json),
            base::OptionalToPtr(per_buyer_signals_json),
            direct_from_seller_result_per_buyer_signals,
            direct_from_seller_per_buyer_signals_header_ad_slot,
            direct_from_seller_result_auction_signals,
            direct_from_seller_auction_signals_header_ad_slot,
            per_buyer_timeout, expected_buyer_currency,
            browser_signal_seller_origin,
            base::OptionalToPtr(browser_signal_top_level_seller_origin),
            browser_signal_recency, browser_signal_for_debugging_only_sampling,
            bidding_browser_signals, auction_start_time, requested_ad_size,
            /* multi_bid_limit=*/1, group_by_origin_id,
            trusted_bidding_signals_result, trace_id,
            std::move(result->context_recycler_for_rerun),
            /*restrict_to_kanon_ads=*/true);
      } else {
        restricted_result = SingleGenerateBidResult(
            std::unique_ptr<ContextRecycler>(),
            std::vector<SetBidBindings::BidAndWorkletOnlyMetadata>(),
            /*bidding_signals_data_version=*/std::nullopt,
            /*debug_loss_report_url=*/std::nullopt,
            /*debug_win_report_url=*/std::nullopt,
            /*set_priority=*/std::nullopt,
            /*update_priority_signals_overrides=*/{},
            /*pa_requests=*/{},
            /*real_time_contributions=*/{},
            /*reject_reason=*/mojom::RejectReason::kNotAvailable,
            /*script_timed_out=*/false,
            /*error_msgs=*/{});
      }
      if (restricted_result.has_value()) {
        // All the bids from the re-run will be k-anon enforced; we need to make
        // sure to apply the component ad reduction, too.
        for (auto& candidate : restricted_result->bids) {
          candidate.bid->bid_role =
              auction_worklet::mojom::BidRole::kEnforcedKAnon;
          TrimAndCollectBid(std::move(candidate.bid),
                            candidate.target_num_ad_components, bids);
        }
      }

      // Figure out which of `restricted_result` or `result` we actually want
      // to use.
      if (kanon_mode == mojom::KAnonymityBidMode::kEnforce) {
        PrivateAggregationRequests non_kanon_pa_requests =
            std::move(result->pa_requests);
        std::erase_if(
            non_kanon_pa_requests,
            [](const auction_worklet::mojom::PrivateAggregationRequestPtr&
                   request) { return !HasKAnonFailureComponent(*request); });

        // We are enforcing the k-anonymity, so the restricted result is the one
        // to use for reporting, etc., and needs to succeed.
        if (!restricted_result.has_value()) {
          PostErrorBidCallbackToUserThread(
              std::move(callback),
              /*bidding_latency=*/base::TimeTicks::Now() - bidding_start,
              std::move(non_kanon_pa_requests),
              GetRealTimeReportingContributionsOnError(
                  trusted_bidding_signals_fetch_failed,
                  /*is_bidding_signal=*/true));
          return;
        }
        result = std::move(restricted_result);
        result->non_kanon_pa_requests = std::move(non_kanon_pa_requests);
      } else {
        DCHECK_EQ(kanon_mode, mojom::KAnonymityBidMode::kSimulate);
        // Here, `result` is already what we want for reporting, etc., so
        // nothing actually to do in this case.
      }
    }
  }

  // Add platform contributions if there needs to be one.
  MaybeAddRealTimeReportingPlatformContributions(
      trusted_bidding_signals_fetch_failed, /*is_bidding_signal=*/true,
      result->real_time_contributions);

  user_thread_->PostTask(
      FROM_HERE,
      base::BindOnce(std::move(callback), std::move(bids),
                     std::move(result->bidding_signals_data_version),
                     std::move(result->debug_loss_report_url),
                     std::move(result->debug_win_report_url),
                     std::move(result->set_priority),
                     std::move(result->update_priority_signals_overrides),
                     std::move(result->pa_requests),
                     std::move(result->non_kanon_pa_requests),
                     std::move(result->real_time_contributions),
                     /*bidding_latency=*/base::TimeTicks::Now() - bidding_start,
                     result->reject_reason, result->script_timed_out,
                     std::move(result->error_msgs)));
}

std::optional<BidderWorklet::V8State::SingleGenerateBidResult>
BidderWorklet::V8State::RunGenerateBidOnce(
    const mojom::BidderWorkletNonSharedParams& bidder_worklet_non_shared_params,
    const std::string* auction_signals_json,
    const std::string* per_buyer_signals_json,
    const DirectFromSellerSignalsRequester::Result&
        direct_from_seller_result_per_buyer_signals,
    const std::optional<std::string>&
        direct_from_seller_per_buyer_signals_header_ad_slot,
    const DirectFromSellerSignalsRequester::Result&
        direct_from_seller_result_auction_signals,
    const std::optional<std::string>&
        direct_from_seller_auction_signals_header_ad_slot,
    const std::optional<base::TimeDelta> per_buyer_timeout,
    const std::optional<blink::AdCurrency>& expected_buyer_currency,
    const url::Origin& browser_signal_seller_origin,
    const url::Origin* browser_signal_top_level_seller_origin,
    const base::TimeDelta browser_signal_recency,
    bool browser_signal_for_debugging_only_sampling,
    const blink::mojom::BiddingBrowserSignalsPtr& bidding_browser_signals,
    base::Time auction_start_time,
    const std::optional<blink::AdSize>& requested_ad_size,
    uint16_t multi_bid_limit,
    uint64_t group_by_origin_id,
    const scoped_refptr<TrustedSignals::Result>& trusted_bidding_signals_result,
    uint64_t trace_id,
    std::unique_ptr<ContextRecycler> context_recycler_for_rerun,
    bool restrict_to_kanon_ads) {
  // Can't make a bid without any ads, or if we aren't permitted to spend any
  // time on it.
  if (!bidder_worklet_non_shared_params.ads) {
    return std::nullopt;
  }

  std::vector<std::string> errors_out;
  if (per_buyer_timeout.has_value() &&
      !per_buyer_timeout.value().is_positive()) {
    errors_out.push_back("generateBid() aborted due to zero timeout.");
    return std::make_optional(SingleGenerateBidResult(
        std::unique_ptr<ContextRecycler>(),
        std::vector<SetBidBindings::BidAndWorkletOnlyMetadata>(),
        /*bidding_signals_data_version=*/std::nullopt,
        /*debug_loss_report_url=*/std::nullopt,
        /*debug_win_report_url=*/std::nullopt,
        /*set_priority=*/std::nullopt,
        /*update_priority_signals_overrides=*/{},
        /*pa_requests=*/{},
        /*real_time_contributions=*/{},
        /*reject_reason=*/mojom::RejectReason::kNotAvailable,
        /*script_timed_out=*/true, std::move(errors_out)));
  }

  if (context_recycler_for_rerun) {
    DCHECK(restrict_to_kanon_ads);
  }

  bool script_timed_out = false;

  base::TimeTicks start = base::TimeTicks::Now();

  AuctionV8Helper::FullIsolateScope isolate_scope(v8_helper_.get());
  v8::Isolate* isolate = v8_helper_->isolate();
  ContextRecycler* context_recycler = nullptr;
  std::unique_ptr<ContextRecycler> fresh_context_recycler;

  bool reused_context = false;
  bool should_deep_freeze = false;
  // See if we can reuse an existing context, and determine string to use for
  // `executionMode`.
  context_recycler = execution_mode_helper_.TryReuseContext(
      bidder_worklet_non_shared_params.execution_mode, group_by_origin_id,
      /*allow_group_by_origin=*/true,
      /*context_recycler_for_kanon_rerun=*/context_recycler_for_rerun.get(),
      should_deep_freeze);

  if (context_recycler) {
    reused_context = true;
  }

  base::UmaHistogramBoolean(
      "Ads.InterestGroup.Auction.BidderWorkletContextReused", reused_context);

  v8_helper_->MaybeTriggerInstrumentationBreakpoint(
      *debug_id_, "beforeBidderWorkletBiddingStart");

  std::unique_ptr<AuctionV8Helper::TimeLimit> total_timeout =
      v8_helper_->CreateTimeLimit(per_buyer_timeout);

  // No recycled context, make a fresh one or use a premade one.
  if (!context_recycler) {
    bool used_premade_context = false;
    if (unused_context_recyclers_.empty()) {
      fresh_context_recycler =
          CreateContextRecyclerAndRunTopLevelForGenerateBid(
              trace_id, *total_timeout, should_deep_freeze, script_timed_out,
              errors_out);
      ++non_premade_contexts_created;
    } else {
      std::tie(fresh_context_recycler, script_timed_out, errors_out) =
          std::move(unused_context_recyclers_.back());
      unused_context_recyclers_.pop_back();
      if (fresh_context_recycler && should_deep_freeze) {
        ContextRecyclerScope scope(*fresh_context_recycler);
        v8::Local<v8::Context> context = scope.GetContext();
        if (!ExecutionModeHelper::DeepFreezeContext(context, v8_helper_,
                                                    errors_out)) {
          fresh_context_recycler.reset();
        }
      }
      used_premade_context = true;
    }
    base::UmaHistogramBoolean("Ads.InterestGroup.Auction.UsedPremadeContext",
                              used_premade_context);

    if (!fresh_context_recycler) {
      return std::make_optional(SingleGenerateBidResult(
          std::unique_ptr<ContextRecycler>(),
          std::vector<SetBidBindings::BidAndWorkletOnlyMetadata>(),
          /*bidding_signals_data_version=*/std::nullopt,
          /*debug_loss_report_url=*/std::nullopt,
          /*debug_win_report_url=*/std::nullopt,
          /*set_priority=*/std::nullopt,
          /*update_priority_signals_overrides=*/{},
          /*pa_requests=*/{},
          /*real_time_contributions=*/{},
          /*reject_reason=*/mojom::RejectReason::kNotAvailable,
          /*script_timed_out=*/script_timed_out, std::move(errors_out)));
    }

    context_recycler = fresh_context_recycler.get();

    // Save the generated context for potential reuse in subsequent calls
    // based on the execution mode and feature flags. Contexts are saved if:
    //  - The kFledgeAlwaysReuseBidderContext feature is enabled, OR
    //  - The execution mode is not `compatibility` mode.
    // In `compatibility` mode without the feature flag, a fresh context is used
    // for each invocation and not saved for reuse.
    if (base::FeatureList::IsEnabled(
            features::kFledgeAlwaysReuseBidderContext) ||
        bidder_worklet_non_shared_params.execution_mode !=
            blink::mojom::InterestGroup::ExecutionMode::kCompatibilityMode) {
      execution_mode_helper_.SaveContextForReuse(
          bidder_worklet_non_shared_params.execution_mode, group_by_origin_id,
          /*allow_group_by_origin=*/true, std::move(fresh_context_recycler));
    }
  }

  base::RepeatingCallback<bool(const std::string&)>
      should_exclude_ad_due_to_kanon = base::BindRepeating(
          [](bool restrict_to_kanon_ads,
             const mojom::BidderWorkletNonSharedParams* params,
             const url::Origin* owner, const GURL* bidding_url,
             const std::string& ad_url_from_gurl_spec) {
            return restrict_to_kanon_ads &&
                   !BidderWorklet::IsKAnon(
                       params,
                       blink::HashedKAnonKeyForAdBid(*owner, *bidding_url,
                                                     ad_url_from_gurl_spec));
          },
          restrict_to_kanon_ads, &bidder_worklet_non_shared_params, &owner_,
          &script_source_url_);

  base::RepeatingCallback<bool(const std::string&)>
      should_exclude_component_ad_due_to_kanon = base::BindRepeating(
          [](bool restrict_to_kanon_ads,
             const mojom::BidderWorkletNonSharedParams* params,
             const std::string& ad_url_from_gurl_spec) {
            return restrict_to_kanon_ads &&
                   !BidderWorklet::IsKAnon(
                       params, blink::HashedKAnonKeyForAdComponentBid(
                                   ad_url_from_gurl_spec));
          },
          restrict_to_kanon_ads, &bidder_worklet_non_shared_params);

  base::RepeatingCallback<bool(const std::string&,
                               base::optional_ref<const std::string>,
                               base::optional_ref<const std::string>,
                               base::optional_ref<const std::string>)>
      should_exclude_reporting_id_set_due_to_kanon = base::BindRepeating(
          [](bool restrict_to_kanon_ads,
             const mojom::BidderWorkletNonSharedParams* params,
             const url::Origin* owner, const GURL* bidding_url,
             const std::string& ad_url_from_gurl_spec,
             base::optional_ref<const std::string> buyer_reporting_id,
             base::optional_ref<const std::string>
                 buyer_and_seller_reporting_id,
             base::optional_ref<const std::string>
                 selected_buyer_and_seller_reporting_id) {
            return restrict_to_kanon_ads &&
                   !BidderWorklet::IsKAnon(
                       params,
                       blink::
                           HashedKAnonKeyForAdNameReportingWithoutInterestGroup(
                               /*interest_group_owner=*/*owner,
                               /*interest_group_name=*/params->name,
                               /*interest_group_bidding_url=*/*bidding_url,
                               /*ad_render_url=*/ad_url_from_gurl_spec,
                               buyer_reporting_id,
                               buyer_and_seller_reporting_id,
                               selected_buyer_and_seller_reporting_id));
          },
          restrict_to_kanon_ads, &bidder_worklet_non_shared_params, &owner_,
          &script_source_url_);

  ContextRecyclerScope context_recycler_scope(*context_recycler);
  v8::Local<v8::Context> context = context_recycler_scope.GetContext();

  context_recycler->set_bid_bindings()->ReInitialize(
      start, browser_signal_top_level_seller_origin != nullptr,
      &bidder_worklet_non_shared_params, expected_buyer_currency,
      multi_bid_limit, should_exclude_ad_due_to_kanon,
      should_exclude_component_ad_due_to_kanon,
      should_exclude_reporting_id_set_due_to_kanon);

  TRACE_EVENT_BEGIN("fledge", "fillInInterestGroupV8Argument",
                    perfetto::Track(trace_id));
  v8::LocalVector<v8::Value> args(isolate);
  v8::Local<v8::Object> interest_group_object = v8::Object::New(isolate);
  gin::Dictionary interest_group_dict(isolate, interest_group_object);
  if (!interest_group_dict.Set("owner", owner_.Serialize()) ||
      !interest_group_dict.Set("name", bidder_worklet_non_shared_params.name) ||
      !interest_group_dict.Set("enableBiddingSignalsPrioritization",
                               bidder_worklet_non_shared_params
                                   .enable_bidding_signals_prioritization) ||
      !interest_group_dict.Set(
          "executionMode",
          auction_worklet::GetExecutionModeString(
              bidder_worklet_non_shared_params.execution_mode)) ||
      !interest_group_dict.Set(
          "trustedBiddingSignalsSlotSizeMode",
          blink::InterestGroup::TrustedBiddingSignalsSlotSizeModeToString(
              bidder_worklet_non_shared_params
                  .trusted_bidding_signals_slot_size_mode))) {
    return std::nullopt;
  }

  context_recycler->interest_group_lazy_filler()->ReInitialize(
      &script_source_url_,
      wasm_helper_url_.has_value() ? &wasm_helper_url_.value() : nullptr,
      trusted_bidding_signals_url_.has_value()
          ? &trusted_bidding_signals_url_.value()
          : nullptr,
      &bidder_worklet_non_shared_params);
  if (!context_recycler->interest_group_lazy_filler()->FillInObject(
          interest_group_object, should_exclude_ad_due_to_kanon,
          should_exclude_component_ad_due_to_kanon,
          should_exclude_reporting_id_set_due_to_kanon)) {
    return std::nullopt;
  }

  args.push_back(std::move(interest_group_object));
  TRACE_EVENT_END("fledge", perfetto::Track(trace_id));

  if (!AppendJsonValueOrNull(v8_helper_.get(), context, auction_signals_json,
                             &args) ||
      !AppendJsonValueOrNull(v8_helper_.get(), context, per_buyer_signals_json,
                             &args)) {
    return std::nullopt;
  }

  TRACE_EVENT_BEGIN("fledge", "fillInTrustedSignalsV8Argument",
                    perfetto::Track(trace_id));
  v8::Local<v8::Value> trusted_signals;
  SignalsOriginRelation trusted_signals_relation = ClassifyTrustedSignals(
      script_source_url_, trusted_bidding_signals_origin_);
  UMA_HISTOGRAM_ENUMERATION(
      "Ads.InterestGroup.Auction.TrustedBidderSignalsOriginRelation",
      trusted_signals_relation);
  if (!trusted_bidding_signals_result ||
      !bidder_worklet_non_shared_params.trusted_bidding_signals_keys ||
      bidder_worklet_non_shared_params.trusted_bidding_signals_keys->empty()) {
    trusted_signals = v8::Null(isolate);
  } else {
    trusted_signals = trusted_bidding_signals_result->GetBiddingSignals(
        v8_helper_.get(), context,
        *bidder_worklet_non_shared_params.trusted_bidding_signals_keys);
  }

  if (trusted_signals_relation == SignalsOriginRelation::kSameOriginSignals) {
    args.push_back(trusted_signals);
  } else {
    args.push_back(v8::Null(isolate));
  }
  TRACE_EVENT_END("fledge", perfetto::Track(trace_id));

  std::optional<uint32_t> bidding_signals_data_version;
  if (trusted_bidding_signals_result) {
    bidding_signals_data_version =
        trusted_bidding_signals_result->GetDataVersion();
  }

  TRACE_EVENT_BEGIN("fledge", "fillInBrowserSignalsV8Argument",
                    perfetto::Track(trace_id));
  v8::Local<v8::Object> browser_signals = v8::Object::New(isolate);
  gin::Dictionary browser_signals_dict(isolate, browser_signals);
  // TODO(crbug.com/336164429): Construct the fields of browser signals lazily.
  if (!browser_signals_dict.Set("topWindowHostname",
                                top_window_origin_.host()) ||
      !browser_signals_dict.Set("seller",
                                browser_signal_seller_origin.Serialize()) ||
      (browser_signal_top_level_seller_origin &&
       !browser_signals_dict.Set(
           "topLevelSeller",
           browser_signal_top_level_seller_origin->Serialize())) ||
      (base::FeatureList::IsEnabled(blink::features::kFledgeClickiness) &&
       (!MaybeSetViewOrClickCounts(
            isolate, browser_signals_dict, "viewCounts",
            bidding_browser_signals->view_and_click_counts->view_counts) ||
        !MaybeSetViewOrClickCounts(
            isolate, browser_signals_dict, "clickCounts",
            bidding_browser_signals->view_and_click_counts->click_counts))) ||
      !browser_signals_dict.Set("joinCount",
                                bidding_browser_signals->join_count) ||
      !browser_signals_dict.Set("bidCount",
                                bidding_browser_signals->bid_count) ||
      (base::FeatureList::IsEnabled(
           blink::features::kFledgeSampleDebugReports) &&
       !browser_signals_dict.Set(
           "forDebuggingOnlyInCooldownOrLockout",
           bidding_browser_signals
               ->for_debugging_only_in_cooldown_or_lockout)) ||
      (base::FeatureList::IsEnabled(
           blink::features::kFledgeEnableSampleDebugReportOnCookieSetting) &&
       !browser_signals_dict.Set("forDebuggingOnlySampling",
                                 browser_signal_for_debugging_only_sampling)) ||
      // `adComponentsLimit` is reported only when the corresponding change
      // is rolled out, to avoid affecting behavior if it's not.
      (base::FeatureList::IsEnabled(
           blink::features::kFledgeCustomMaxAuctionAdComponents) &&
       !browser_signals_dict.Set(
           "adComponentsLimit",
           // Cast to help gin on mac.
           static_cast<uint64_t>(blink::MaxAdAuctionAdComponents()))) ||
      // Report the multi-bid limit if the corresponding feature on.
      (SupportMultiBid() &&
       !browser_signals_dict.Set("multiBidLimit",
                                 static_cast<uint32_t>(multi_bid_limit))) ||
      (!browser_signals_dict.Set("recency",
                                 browser_signal_recency.InMilliseconds())) ||
      !SetDataVersion(trusted_signals_relation, bidding_signals_data_version,
                      browser_signals_dict) ||
      (requested_ad_size.has_value() &&
       !MaybeSetSizeMember(isolate, browser_signals_dict, "requestedSize",
                           requested_ad_size.value()))) {
    return std::nullopt;
  }

  if (wasm_helper_.success()) {
    v8::Local<v8::WasmModuleObject> module;
    v8::Maybe<bool> result = v8::Nothing<bool>();
    if (WorkletWasmLoader::MakeModule(wasm_helper_).ToLocal(&module)) {
      result = browser_signals->Set(
          context, gin::StringToV8(isolate, "wasmHelper"), module);
    }
    if (result.IsNothing() || !result.FromJust()) {
      return std::nullopt;
    }
  }

  context_recycler->bidding_browser_signals_lazy_filler()->ReInitialize(
      bidding_browser_signals.get(), auction_start_time);
  if (!context_recycler->bidding_browser_signals_lazy_filler()->FillInObject(
          browser_signals)) {
    return std::nullopt;
  }

  args.push_back(browser_signals);
  // End "fillInBrowserSignalsV8Argument" trace event.
  TRACE_EVENT_END("fledge", perfetto::Track(trace_id));

  v8::Local<v8::Object> direct_from_seller_signals = v8::Object::New(isolate);
  gin::Dictionary direct_from_seller_signals_dict(isolate,
                                                  direct_from_seller_signals);
  v8::Local<v8::Value> per_buyer_signals = GetDirectFromSellerSignals(
      direct_from_seller_result_per_buyer_signals,
      direct_from_seller_per_buyer_signals_header_ad_slot, *v8_helper_, context,
      errors_out);
  v8::Local<v8::Value> auction_signals = GetDirectFromSellerSignals(
      direct_from_seller_result_auction_signals,
      direct_from_seller_auction_signals_header_ad_slot, *v8_helper_, context,
      errors_out);
  if (!direct_from_seller_signals_dict.Set("perBuyerSignals",
                                           per_buyer_signals) ||
      !direct_from_seller_signals_dict.Set("auctionSignals", auction_signals)) {
    return std::nullopt;
  }
  args.push_back(direct_from_seller_signals);

  v8::Local<v8::Value> cross_origin_trusted_bidding_signals_value;
  if (trusted_signals_relation == SignalsOriginRelation::kCrossOriginSignals) {
    cross_origin_trusted_bidding_signals_value =
        TrustedSignals::Result::WrapCrossOriginSignals(
            v8_helper_.get(), context, *trusted_bidding_signals_origin_,
            trusted_signals);
  } else {
    cross_origin_trusted_bidding_signals_value = v8::Null(isolate);
  }
  args.push_back(cross_origin_trusted_bidding_signals_value);

  v8::Local<v8::Value> generate_bid_result;

  TRACE_EVENT_BEGIN("fledge", "generate_bid", perfetto::Track(trace_id));
  v8::MaybeLocal<v8::Value> maybe_generate_bid_result;

  AuctionV8Helper::Result script_result = v8_helper_->CallFunction(
      context, debug_id_.get(),
      v8_helper_->FormatScriptName(worklet_script_.Get(isolate)), "generateBid",
      args, total_timeout.get(), maybe_generate_bid_result, errors_out);
  bool got_return_value =
      script_result == AuctionV8Helper::Result::kSuccess &&
      maybe_generate_bid_result.ToLocal(&generate_bid_result);
  script_timed_out = script_result == AuctionV8Helper::Result::kTimeout;
  TRACE_EVENT_END("fledge", perfetto::Track(trace_id));

  base::TimeDelta time_duration = base::TimeTicks::Now() - start;
  base::UmaHistogramTimes("Ads.InterestGroup.Auction.GenerateBidTime",
                          time_duration);

  RealTimeReportingContributions real_time_contributions =
      context_recycler->real_time_reporting_bindings()
          ->TakeRealTimeReportingContributions();

  // Remove worklet latency contributions if the worklet execution time is
  // within the threshold.
  std::erase_if(
      real_time_contributions,
      [time_duration](
          const auction_worklet::mojom::RealTimeReportingContributionPtr&
              contribution) {
        return contribution->latency_threshold.has_value() &&
               time_duration.InMilliseconds() <=
                   contribution->latency_threshold.value();
      });

  if (got_return_value) {
    IdlConvert::Status status =
        context_recycler->set_bid_bindings()->SetBidImpl(
            generate_bid_result,
            base::StrCat({script_source_url_.spec(), " generateBid() "}));
    if (!status.is_success()) {
      if (status.is_timeout()) {
        script_timed_out = true;
      }
      errors_out.push_back(status.ConvertToErrorString(isolate));
    }
  }

  if (!context_recycler->set_bid_bindings()->has_bids()) {
    // If no bid was returned (due to an error or just not choosing to bid), or
    // the method timed out and no intermediate result was given through
    // `setBid()`, return an error. Keep debug loss reports, Private
    // Aggregation requests and real time reporting contributions  since
    // `generateBid()` might use them to detect script timeout or failures. Keep
    // any set priority and set priority overrides because an interest group may
    // want to update them even when not bidding. No need to return a
    // ContextRecycler since this will not be re-run.
    return std::make_optional(SingleGenerateBidResult(
        std::unique_ptr<ContextRecycler>(),
        std::vector<SetBidBindings::BidAndWorkletOnlyMetadata>(),
        /*bidding_signals_data_version=*/std::nullopt,
        context_recycler->for_debugging_only_bindings()->TakeLossReportUrl(),
        /*debug_win_report_url=*/std::nullopt,
        context_recycler->set_priority_bindings()->set_priority(),
        context_recycler->set_priority_signals_override_bindings()
            ->TakeSetPrioritySignalsOverrides(),
        context_recycler->private_aggregation_bindings()
            ->TakePrivateAggregationRequests(
                /*did_uncaught_error_occur=*/script_result ==
                AuctionV8Helper::Result::kFailure),
        std::move(real_time_contributions),
        context_recycler->set_bid_bindings()->reject_reason(), script_timed_out,
        std::move(errors_out)));
  }

  // If the context recycler wasn't saved based on `execution_mode`,
  // `fresh_context_recycler` is non-null here, and it will be provided to the
  // caller for potential re-use for k-anon re-run.
  return std::make_optional(SingleGenerateBidResult(
      std::move(fresh_context_recycler),
      context_recycler->set_bid_bindings()->TakeBids(),
      bidding_signals_data_version,
      context_recycler->for_debugging_only_bindings()->TakeLossReportUrl(),
      context_recycler->for_debugging_only_bindings()->TakeWinReportUrl(),
      context_recycler->set_priority_bindings()->set_priority(),
      context_recycler->set_priority_signals_override_bindings()
          ->TakeSetPrioritySignalsOverrides(),
      context_recycler->private_aggregation_bindings()
          ->TakePrivateAggregationRequests(
              /*did_uncaught_error_occur=*/script_result ==
              AuctionV8Helper::Result::kFailure),
      std::move(real_time_contributions), mojom::RejectReason::kNotAvailable,
      script_timed_out, std::move(errors_out)));
}

void BidderWorklet::V8State::PrepareContextRecycler(uint64_t trace_id) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_);
  bool script_timed_out;
  std::vector<std::string> errors_out;
  std::unique_ptr<AuctionV8Helper::TimeLimit> total_timeout =
      v8_helper_->CreateTimeLimit(
          /*script_timeout=*/std::nullopt);
  AuctionV8Helper::FullIsolateScope isolate_scope(v8_helper_.get());
  std::unique_ptr<ContextRecycler> context_recycler =
      CreateContextRecyclerAndRunTopLevelForGenerateBid(
          trace_id, *total_timeout, /*should_deep_freeze=*/false,
          script_timed_out, errors_out);
  unused_context_recyclers_.push_back(std::make_tuple(
      std::move(context_recycler), script_timed_out, errors_out));
}

std::unique_ptr<ContextRecycler>
BidderWorklet::V8State::CreateContextRecyclerAndRunTopLevelForGenerateBid(
    uint64_t trace_id,
    AuctionV8Helper::TimeLimit& total_timeout,
    bool should_deep_freeze,
    bool& script_timed_out,
    std::vector<std::string>& errors_out) {
  base::TimeTicks start = base::TimeTicks::Now();
  std::unique_ptr<ContextRecycler> context_recycler =
      std::make_unique<ContextRecycler>(v8_helper_.get());

  TRACE_EVENT_BEGIN("fledge", "get_bidder_context", perfetto::Track(trace_id));
  ContextRecyclerScope context_recycler_scope(*context_recycler);
  v8::Local<v8::Context> context = context_recycler_scope.GetContext();
  TRACE_EVENT_END("fledge", perfetto::Track(trace_id));

  // We want this before RunScript, both because it's meant to be visible
  // to globals, and because we don't want to overwrite existing globals.
  context_recycler->AddTextConversionHelpers();

  v8::Local<v8::UnboundScript> unbound_worklet_script =
      worklet_script_.Get(v8_helper_->isolate());

  TRACE_EVENT_BEGIN("fledge", "biddingScript", perfetto::Track(trace_id));
  AuctionV8Helper::Result result =
      v8_helper_->RunScript(context, unbound_worklet_script, debug_id_.get(),
                            &total_timeout, errors_out);
  TRACE_EVENT_END("fledge", perfetto::Track(trace_id));  // "biddingScript"
  base::UmaHistogramTimes("Ads.InterestGroup.Auction.BidScriptTime",
                          base::TimeTicks::Now() - start);

  if (result == AuctionV8Helper::Result::kTimeout) {
    script_timed_out = true;
  }

  if (result != AuctionV8Helper::Result::kSuccess) {
    return nullptr;
  }

  TRACE_EVENT_BEGIN("fledge", "addBindingsToContextRecycler",
                    perfetto::Track(trace_id));
  context_recycler->AddForDebuggingOnlyBindings();
  context_recycler->AddPrivateAggregationBindings(
      permissions_policy_state_->private_aggregation_allowed,
      /*reserved_once_allowed=*/true);
  context_recycler->AddRealTimeReportingBindings();

  if (base::FeatureList::IsEnabled(network::features::kSharedStorageAPI)) {
    context_recycler->AddSharedStorageBindings(
        shared_storage_host_remote_.is_bound()
            ? shared_storage_host_remote_.get()
            : nullptr,
        mojom::AuctionWorkletFunction::kBidderGenerateBid,
        permissions_policy_state_->shared_storage_allowed);
  }

  context_recycler->AddSetBidBindings();
  context_recycler->AddSetPriorityBindings();
  context_recycler->AddSetPrioritySignalsOverrideBindings();
  context_recycler->AddInterestGroupLazyFiller();
  context_recycler->AddBiddingBrowserSignalsLazyFiller();
  TRACE_EVENT_END("fledge", perfetto::Track(trace_id));

  if (should_deep_freeze && !ExecutionModeHelper::DeepFreezeContext(
                                context, v8_helper_, errors_out)) {
    return nullptr;
  }

  return context_recycler;
}

void BidderWorklet::V8State::ConnectDevToolsAgent(
    mojo::PendingAssociatedReceiver<blink::mojom::DevToolsAgent> agent) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_);
  v8_helper_->ConnectDevToolsAgent(std::move(agent), user_thread_, *debug_id_);
}

BidderWorklet::V8State::~V8State() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_);

  AuctionV8Helper::FullIsolateScope isolate_scope(v8_helper_.get());
  v8::Isolate* isolate = v8_helper_->isolate();
  v8::HeapStatistics heap_statistics;
  isolate->GetHeapStatistics(&heap_statistics);
  base::UmaHistogramCounts100000(
      "Ads.InterestGroup.Auction.BidderWorkletIsolateUsedHeapSizeKilobytes",
      heap_statistics.used_heap_size() / 1024);
  base::UmaHistogramCounts100000(
      "Ads.InterestGroup.Auction.BidderWorkletIsolateTotalHeapSizeKilobytes",
      heap_statistics.total_heap_size() / 1024);

  base::UmaHistogramCounts100("Ads.InterestGroup.Auction.UnusedPremadeContexts",
                              unused_context_recyclers_.size());
  base::UmaHistogramCounts100(
      "Ads.InterestGroup.Auction.NonPremadeContextsCreated",
      non_premade_contexts_created);
}

void BidderWorklet::V8State::FinishInit(
    mojo::PendingRemote<mojom::AuctionSharedStorageHost>
        shared_storage_host_remote) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_);

  if (shared_storage_host_remote) {
    shared_storage_host_remote_.Bind(std::move(shared_storage_host_remote));
  }

  debug_id_->SetResumeCallback(base::BindOnce(
      &BidderWorklet::V8State::PostResumeToUserThread, parent_, user_thread_));
}

// static
void BidderWorklet::V8State::PostResumeToUserThread(
    base::WeakPtr<BidderWorklet> parent,
    scoped_refptr<base::SequencedTaskRunner> user_thread) {
  // This is static since it's called from debugging, not BidderWorklet,
  // so the usual guarantee that BidderWorklet posts things before posting
  // V8State destruction is irrelevant.
  user_thread->PostTask(FROM_HERE,
                        base::BindOnce(&BidderWorklet::ResumeIfPaused, parent));
}

void BidderWorklet::V8State::PostReportWinCallbackToUserThread(
    ReportWinCallbackInternal callback,
    const std::optional<GURL>& report_url,
    base::flat_map<std::string, GURL> ad_beacon_map,
    base::flat_map<std::string, std::string> ad_macro_map,
    PrivateAggregationRequests pa_requests,
    mojom::PrivateModelTrainingRequestDataPtr maybe_pmt_request_data,
    base::TimeDelta reporting_latency,
    bool script_timed_out,
    std::vector<std::string> errors) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_);
  user_thread_->PostTask(
      FROM_HERE,
      base::BindOnce(std::move(callback), std::move(report_url),
                     std::move(ad_beacon_map), std::move(ad_macro_map),
                     std::move(pa_requests), std::move(maybe_pmt_request_data),
                     reporting_latency, script_timed_out, std::move(errors)));
}

void BidderWorklet::V8State::PostErrorBidCallbackToUserThread(
    GenerateBidCallbackInternal callback,
    base::TimeDelta bidding_latency,
    PrivateAggregationRequests non_kanon_pa_requests,
    RealTimeReportingContributions real_time_contributions,
    std::vector<std::string> error_msgs) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_);
  user_thread_->PostTask(
      FROM_HERE,
      base::BindOnce(
          std::move(callback), std::vector<mojom::BidderWorkletBidPtr>(),
          /*bidding_signals_data_version=*/std::nullopt,
          /*debug_loss_report_url=*/std::nullopt,
          /*debug_win_report_url=*/std::nullopt,
          /*set_priority=*/std::nullopt,
          /*update_priority_signals_overrides=*/
          base::flat_map<std::string, mojom::PrioritySignalsDoublePtr>(),
          /*pa_requests=*/
          PrivateAggregationRequests(), std::move(non_kanon_pa_requests),
          std::move(real_time_contributions), bidding_latency,
          /*reject_reason=*/mojom::RejectReason::kNotAvailable,
          /*script_timed_out=*/false, std::move(error_msgs)));
}

void BidderWorklet::ResumeIfPaused() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_);
  if (!paused_) {
    return;
  }

  resumed_count_++;
  if (resumed_count_ == v8_helpers_.size()) {
    paused_ = false;
    Start();
  }
}

void BidderWorklet::Start() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_);
  DCHECK(!paused_);
  base::UmaHistogramCounts100000(
      "Ads.InterestGroup.Net.RequestUrlSizeBytes.BiddingScriptJS",
      script_source_url_.spec().size());
  code_download_start_ = base::TimeTicks::Now();
  worklet_loader_ = std::make_unique<WorkletLoader>(
      url_loader_factory_.get(),
      /*auction_network_events_handler=*/
      CreateNewAuctionNetworkEventsHandlerRemote(
          auction_network_events_handler_),
      std::move(script_source_load_), v8_helpers_, debug_ids_,
      WorkletLoader::AllowTrustedScoringSignalsCallback(),
      base::BindOnce(&BidderWorklet::OnScriptDownloaded,
                     base::Unretained(this)));

  if (wasm_helper_url_.has_value()) {
    base::UmaHistogramCounts100000(
        "Ads.InterestGroup.Net.RequestUrlSizeBytes.BiddingScriptWasm",
        wasm_helper_url_->spec().size());
    wasm_loader_ = std::make_unique<WorkletWasmLoader>(
        url_loader_factory_.get(),
        /*auction_network_events_handler=*/
        CreateNewAuctionNetworkEventsHandlerRemote(
            auction_network_events_handler_),
        std::move(wasm_helper_load_), v8_helpers_, debug_ids_,
        base::BindOnce(&BidderWorklet::OnWasmDownloaded,
                       base::Unretained(this)));
  }
}

void BidderWorklet::OnScriptDownloaded(
    std::vector<WorkletLoader::Result> worklet_scripts,
    std::optional<std::string> error_msg) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_);

  DCHECK_EQ(worklet_scripts.size(), v8_helpers_.size());
  js_fetch_latency_ = base::TimeTicks::Now() - code_download_start_;

  // Use `worklet_scripts[0]` for metrics and for the failure check. All the
  // results should be the same.
  base::UmaHistogramCounts10M(
      "Ads.InterestGroup.Net.ResponseSizeBytes.BiddingScriptJS",
      worklet_scripts[0].original_size_bytes());
  base::UmaHistogramTimes("Ads.InterestGroup.Net.DownloadTime.BiddingScriptJS",
                          worklet_scripts[0].download_time());
  worklet_loader_.reset();

  // On failure, close pipe and delete `this`, as it can't do anything without a
  // loaded script.
  if (!worklet_scripts[0].success()) {
    std::move(close_pipe_callback_)
        .Run(error_msg ? error_msg.value() : std::string());
    // `this` should be deleted at this point.
    return;
  }

  if (error_msg.has_value()) {
    load_code_error_msgs_.push_back(std::move(error_msg.value()));
  }

  for (size_t i = 0; i < v8_runners_.size(); ++i) {
    v8_runners_[i]->PostTask(
        FROM_HERE, base::BindOnce(&BidderWorklet::V8State::SetWorkletScript,
                                  base::Unretained(v8_state_[i].get()),
                                  std::move(worklet_scripts[i])));
  }

  MaybeRecordCodeWait();
  MaybePrepareContexts();
  RunReadyTasks();
}

void BidderWorklet::OnWasmDownloaded(
    std::vector<WorkletWasmLoader::Result> worklet_scripts,
    std::optional<std::string> error_msg) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_);

  DCHECK_EQ(worklet_scripts.size(), v8_helpers_.size());
  wasm_fetch_latency_ = base::TimeTicks::Now() - code_download_start_;

  // Use `worklet_scripts[0]` for metrics and for the failure check. All the
  // results should be the same.
  base::UmaHistogramCounts10M(
      "Ads.InterestGroup.Net.ResponseSizeBytes.BiddingScriptWasm",
      worklet_scripts[0].original_size_bytes());
  base::UmaHistogramTimes(
      "Ads.InterestGroup.Net.DownloadTime.BiddingScriptWasm",
      worklet_scripts[0].download_time());
  wasm_loader_.reset();

  // If the WASM helper is actually requested, delete `this` and inform the
  // browser process of the failure. ReportWin() calls would theoretically still
  // be allowed, but that adds a lot more complexity around BidderWorklet reuse.
  if (!worklet_scripts[0].success()) {
    std::move(close_pipe_callback_)
        .Run(error_msg ? error_msg.value() : std::string());
    // `this` should be deleted at this point.
    return;
  }

  if (error_msg.has_value()) {
    load_code_error_msgs_.push_back(std::move(error_msg.value()));
  }

  for (size_t i = 0; i < v8_runners_.size(); ++i) {
    v8_runners_[i]->PostTask(
        FROM_HERE, base::BindOnce(&BidderWorklet::V8State::SetWasmHelper,
                                  base::Unretained(v8_state_[i].get()),
                                  std::move(worklet_scripts[i])));
  }

  MaybeRecordCodeWait();
  MaybePrepareContexts();
  RunReadyTasks();
}

void BidderWorklet::MaybeRecordCodeWait() {
  if (!IsCodeReady()) {
    return;
  }

  base::TimeTicks now = base::TimeTicks::Now();
  for (auto& task : generate_bid_tasks_) {
    task.wait_code = now - task.trace_wait_deps_start;
  }

  for (auto& task : report_win_tasks_) {
    task.wait_code = now - task.trace_wait_deps_start;
  }
}

void BidderWorklet::RunReadyTasks() {
  // Run all GenerateBid() tasks that are ready. GenerateBidIfReady() does *not*
  // modify `generate_bid_tasks_` when invoked, so this is safe.
  for (auto generate_bid_task = generate_bid_tasks_.begin();
       generate_bid_task != generate_bid_tasks_.end(); ++generate_bid_task) {
    GenerateBidIfReady(generate_bid_task);
  }

  // While reportWin() doesn't use WASM, since we do load it, we wait for it in
  // order to ensure determinism if the load fails.
  if (!IsCodeReady()) {
    return;
  }

  // Run all ReportWin() tasks that are ready. RunReportWinIfReady() does *not*
  // modify `report_win_tasks_` when invoked, so this is safe.
  for (auto report_win_task = report_win_tasks_.begin();
       report_win_task != report_win_tasks_.end(); ++report_win_task) {
    RunReportWinIfReady(report_win_task);
  }
}

void BidderWorklet::MaybePrepareContexts() {
  if (!base::FeatureList::IsEnabled(
          features::kFledgePrepareBidderContextsInAdvance) ||
      generate_bid_tasks_.empty() || !IsCodeReady() ||
      (!finalized_any_bid_ &&
       features::kFledgeWaitForPromisesToPrepareContexts.Get()) ||
      base::FeatureList::IsEnabled(features::kFledgeAlwaysReuseBidderContext)) {
    return;
  }

  // Estimate the maximum number of contexts we'll need.
  std::set<uint64_t> group_by_origin_partitions;
  size_t compatibility_mode_tasks = 0;
  bool frozen_mode_tasks = false;
  for (auto generate_bid_task = generate_bid_tasks_.begin();
       generate_bid_task != generate_bid_tasks_.end(); ++generate_bid_task) {
    if (GenerateBidTaskHasInputs(*generate_bid_task)) {
      return;
    }
    switch (
        generate_bid_task->bidder_worklet_non_shared_params->execution_mode) {
      case blink::mojom::InterestGroup::ExecutionMode::kGroupedByOriginMode:
        group_by_origin_partitions.insert(
            generate_bid_task->group_by_origin_id);
        break;
      case blink::mojom::InterestGroup::ExecutionMode::kFrozenContext:
        frozen_mode_tasks = true;
        break;
      case blink::mojom::InterestGroup::ExecutionMode::kCompatibilityMode:
        ++compatibility_mode_tasks;
        break;
    }
  }

  size_t contexts_to_prepare_per_thread =
      CalculateNumberOfContextsToPreparePerThread(
          /*max_expected_required_contexts=*/group_by_origin_partitions.size() +
              compatibility_mode_tasks + frozen_mode_tasks,
          /*threads=*/v8_helpers_.size());

  base::UmaHistogramCounts100(
      "Ads.InterestGroup.Auction.PremadeContextsScheduledPerThread",
      contexts_to_prepare_per_thread);

  // It doesn't make sense to attach the trace to an arbitrary
  // generate bid task, so use a new trace id.
  uint64_t trace_id = base::trace_event::GetNextGlobalTraceId();
  for (size_t context_index = 0; context_index < contexts_to_prepare_per_thread;
       ++context_index) {
    for (size_t thread_index = 0; thread_index < v8_runners_.size();
         ++thread_index) {
      context_preparation_task_tracker_.PostTask(
          v8_runners_[thread_index].get(), FROM_HERE,
          base::BindOnce(&BidderWorklet::V8State::PrepareContextRecycler,
                         base::Unretained(v8_state_[thread_index].get()),
                         trace_id));
    }
  }
}

void BidderWorklet::OnTrustedBiddingSignalsDownloaded(
    GenerateBidTaskList::iterator task,
    scoped_refptr<TrustedSignals::Result> result,
    std::optional<std::string> error_msg) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_);

  task->trusted_bidding_signals_download_complete_time = base::TimeTicks::Now();

  const TrustedSignals::Result::PerGroupData* per_group_data = nullptr;
  if (result) {
    per_group_data =
        result->GetPerGroupData(task->bidder_worklet_non_shared_params->name);
  }

  task->trusted_bidding_signals_fetch_failed = !result ? true : false;
  task->trusted_bidding_signals_result = std::move(result);
  task->trusted_bidding_signals_error_msg = std::move(error_msg);

  // If using TrustedSignalsRequestManager, the request is no longer needed, so
  // delete it to save resources. If using TrustedSignalsKVv2Manager, however,
  // that request keeps the reusable cached compression group alive, so keep the
  // KVv2 request around until finished generating the bid.
  task->trusted_bidding_signals_request.reset();

  // Deleting `generate_bid_task` will destroy `generate_bid_client` and thus
  // abort this callback, so it's safe to use Unretained(this) and
  // `generate_bid_task` here.
  task->generate_bid_client->OnBiddingSignalsReceived(
      per_group_data && per_group_data->priority_vector
          ? *per_group_data->priority_vector
          : TrustedSignals::Result::PriorityVector(),
      /*trusted_signals_fetch_latency=*/base::TimeTicks::Now() -
          task->trace_wait_deps_start,
      /*update_if_older_than=*/
      per_group_data ? per_group_data->update_if_older_than : std::nullopt,
      base::BindOnce(&BidderWorklet::SignalsReceivedCallback,
                     base::Unretained(this), task));
}

void BidderWorklet::OnGenerateBidClientDestroyed(
    GenerateBidTaskList::iterator task) {
  // If the task hasn't received the signals called callback or the code hasn't
  // loaded, it hasn't posted a task to run off-thread, so can be safely
  // deleted, as everything else, including fetching trusted bidding signals,
  // can be safely cancelled.
  if (!IsReadyToGenerateBid(*task)) {
    // GenerateBidIfReady is never called so make sure to close out this trace
    // event.
    TRACE_EVENT_END("fledge", perfetto::Track(task->trace_id));

    CleanUpBidTaskOnUserThread(task);
  } else {
    // Otherwise, there should be a pending V8 call. Try to cancel that, but if
    // it already started, it will just run and invoke the GenerateBidClient's
    // OnGenerateBidComplete() method, which will safely do nothing since the
    // pipe is now closed.
    DCHECK_NE(task->task_id, base::CancelableTaskTracker::kBadTaskId);
    cancelable_task_tracker_.TryCancel(task->task_id);
  }
}

void BidderWorklet::SignalsReceivedCallback(
    GenerateBidTaskList::iterator task) {
  DCHECK(!task->signals_received_callback_invoked);
  task->signals_received_callback_invoked = true;
  task->wait_trusted_signals =
      base::TimeTicks::Now() - task->trace_wait_deps_start;
  base::UmaHistogramTimes(
      "Ads.InterestGroup.Auction.PostSignalsReceivedResumeGenerateBidTime",
      base::TimeTicks::Now() -
          task->trusted_bidding_signals_download_complete_time);
  GenerateBidIfReady(task);
}

void BidderWorklet::HandleDirectFromSellerForGenerateBid(
    const std::optional<GURL>& direct_from_seller_per_buyer_signals,
    const std::optional<GURL>& direct_from_seller_auction_signals,
    GenerateBidTaskList::iterator task) {
  if (direct_from_seller_per_buyer_signals) {
    // We expect each parameter to be provided at most once between
    // BeginGenerateBid/FinishGenerateBid.  If we are already fetching this
    // kind of signals this is clearly the second time it was specified.
    DCHECK(!task->direct_from_seller_request_per_buyer_signals);

    // Deleting `task` will destroy
    // `direct_from_seller_request_per_buyer_signals` and thus abort this
    // callback, so it's safe to use Unretained(this) and `task`
    // here.
    task->direct_from_seller_request_per_buyer_signals =
        direct_from_seller_requester_per_buyer_signals_.LoadSignals(
            *url_loader_factory_, *direct_from_seller_per_buyer_signals,
            base::BindOnce(
                &BidderWorklet::
                    OnDirectFromSellerPerBuyerSignalsDownloadedGenerateBid,
                base::Unretained(this), task));
  }

  if (direct_from_seller_auction_signals) {
    // We expect each parameter to be provided at most once between
    // BeginGenerateBid/FinishGenerateBid.  If we are already fetching this
    // kind of signals this is clearly the second time it was specified.
    DCHECK(!task->direct_from_seller_request_auction_signals);
    // Deleting `task` will destroy
    // `direct_from_seller_request_auction_signals` and thus abort this
    // callback, so it's safe to use Unretained(this) and `task`
    // here.
    task->direct_from_seller_request_auction_signals =
        direct_from_seller_requester_auction_signals_.LoadSignals(
            *url_loader_factory_, *direct_from_seller_auction_signals,
            base::BindOnce(
                &BidderWorklet::
                    OnDirectFromSellerAuctionSignalsDownloadedGenerateBid,
                base::Unretained(this), task));
  }
}

void BidderWorklet::OnDirectFromSellerPerBuyerSignalsDownloadedGenerateBid(
    GenerateBidTaskList::iterator task,
    DirectFromSellerSignalsRequester::Result result) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_);

  task->direct_from_seller_result_per_buyer_signals = std::move(result);
  task->direct_from_seller_request_per_buyer_signals.reset();

  // The two direct from seller signals metrics for tracing are combined since
  // they should be roughly the same.
  task->wait_direct_from_seller_signals =
      std::max(task->wait_direct_from_seller_signals,
               base::TimeTicks::Now() - task->trace_wait_deps_start);

  GenerateBidIfReady(task);
}

void BidderWorklet::OnDirectFromSellerAuctionSignalsDownloadedGenerateBid(
    GenerateBidTaskList::iterator task,
    DirectFromSellerSignalsRequester::Result result) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_);

  task->direct_from_seller_result_auction_signals = std::move(result);
  task->direct_from_seller_request_auction_signals.reset();

  // The two direct from seller signals metrics for tracing are combined since
  // they should be roughly the same.
  task->wait_direct_from_seller_signals =
      std::max(task->wait_direct_from_seller_signals,
               base::TimeTicks::Now() - task->trace_wait_deps_start);

  GenerateBidIfReady(task);
}

bool BidderWorklet::GenerateBidTaskHasInputs(
    const GenerateBidTask& task) const {
  return task.signals_received_callback_invoked &&
         task.finalize_generate_bid_called &&
         !task.direct_from_seller_request_per_buyer_signals &&
         !task.direct_from_seller_request_auction_signals;
}

bool BidderWorklet::IsReadyToGenerateBid(const GenerateBidTask& task) const {
  return GenerateBidTaskHasInputs(task) && IsCodeReady();
}

void BidderWorklet::SetEagerJsCompilation(bool eagerly_compile_js) {
  for (size_t thread_index = 0; thread_index < v8_runners_.size();
       ++thread_index) {
    v8_runners_[thread_index]->PostTask(
        FROM_HERE,
        base::BindOnce(&AuctionV8Helper::SetEagerJsCompilation,
                       base::Unretained(v8_helpers_[thread_index].get()),
                       eagerly_compile_js));
  }
}

void BidderWorklet::GenerateBidIfReady(GenerateBidTaskList::iterator task) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_);
  if (!IsReadyToGenerateBid(*task)) {
    // Disable eager JS compilation if we're only waiting on Javascript, so that
    // we can begin generating this bid as soon as we have the script.
    if (GenerateBidTaskHasInputs(*task) && worklet_loader_ && !wasm_loader_) {
      SetEagerJsCompilation(false);
    }
    return;
  }

  // Cancel any preparation tasks we have running. We want our v8 runners to
  // generate bids from now on.
  context_preparation_task_tracker_.TryCancelAll();

  // If there was a trusted signals request, it should have already completed
  // and been cleaned up before `signals_received_callback_invoked` was set to
  // true.
  DCHECK(!task->trusted_bidding_signals_request);

  // End "wait_generate_bid_deps" trace event.
  TRACE_EVENT_END(
      "fledge", perfetto::Track(task->trace_id), "data",
      [&](perfetto::TracedValue trace_context) {
        auto dict = std::move(trace_context).WriteDictionary();
        if (!task->wait_code.is_zero()) {
          dict.Add("wait_code_ms", task->wait_code.InMillisecondsF());
        }
        if (!task->wait_trusted_signals.is_zero()) {
          dict.Add("wait_trusted_signals_ms",
                   task->wait_trusted_signals.InMillisecondsF());
        }
        if (!task->wait_direct_from_seller_signals.is_zero()) {
          dict.Add("wait_direct_from_seller_signals_ms",
                   task->wait_direct_from_seller_signals.InMillisecondsF());
        }
        if (!task->wait_promises.is_zero()) {
          dict.Add("wait_promises_ms", task->wait_promises.InMillisecondsF());
        }
      });
  TRACE_EVENT_BEGIN("fledge", "post_v8_task", perfetto::Track(task->trace_id));

  // Normally the PostTask below will eventually get `task` cleaned up once it
  // posts back to DeliverBidCallbackOnUserThread with its results, but that
  // won't happen if it gets cancelled. To deal with that, a ScopedClosureRunner
  // is passed to ask for `task` to get cleaned up in case the
  // V8State::GenerateBid closure gets destroyed without running.
  base::OnceClosure cleanup_generate_bid_task =
      base::BindPostTaskToCurrentDefault(
          base::BindOnce(&BidderWorklet::CleanUpBidTaskOnUserThread,
                         weak_ptr_factory_.GetWeakPtr(), task));

  // The thread selector only needs to worry about grouping tasks
  // when they're in group-by-origin mode.
  std::optional<uint64_t> maybe_group_by_origin_key;
  if (task->bidder_worklet_non_shared_params->execution_mode ==
      blink::mojom::InterestGroup::ExecutionMode::kGroupedByOriginMode) {
    maybe_group_by_origin_key = task->group_by_origin_id;
  }
  size_t thread_index = thread_selector_.GetThread(maybe_group_by_origin_key);

  // Other than the `generate_bid_client` and `task_id` fields, no fields of
  // `task` are needed after this point, so can consume them instead of copying
  // them.
  //
  // Since IsReadyToGenerateBid() is true, the GenerateBidTask won't be deleted
  // on the main thread during this call, even if the GenerateBidClient pipe is
  // deleted by the caller (unless the BidderWorklet  itself is deleted).
  // Therefore, it's safe to post a callback with the `task`  iterator the v8
  // thread.

  task->generate_bid_start_time = base::TimeTicks::Now();
  task->task_id = cancelable_task_tracker_.PostTask(
      v8_runners_[thread_index].get(), FROM_HERE,
      base::BindOnce(
          &BidderWorklet::V8State::GenerateBid,
          base::Unretained(v8_state_[thread_index].get()),
          std::move(task->bidder_worklet_non_shared_params), task->kanon_mode,
          std::move(task->auction_signals_json),
          std::move(task->per_buyer_signals_json),
          std::move(task->direct_from_seller_result_per_buyer_signals),
          std::move(task->direct_from_seller_per_buyer_signals_header_ad_slot),
          std::move(task->direct_from_seller_result_auction_signals),
          std::move(task->direct_from_seller_auction_signals_header_ad_slot),
          std::move(task->per_buyer_timeout),
          std::move(task->expected_buyer_currency),
          std::move(task->browser_signal_seller_origin),
          std::move(task->browser_signal_top_level_seller_origin),
          std::move(task->browser_signal_recency),
          task->browser_signal_for_debugging_only_sampling,
          std::move(task->bidding_browser_signals), task->auction_start_time,
          std::move(task->requested_ad_size), task->multi_bid_limit,
          task->group_by_origin_id,
          std::move(task->trusted_bidding_signals_result),
          task->trusted_bidding_signals_fetch_failed, task->trace_id,
          base::ScopedClosureRunner(std::move(cleanup_generate_bid_task)),
          base::BindOnce(&BidderWorklet::DeliverBidCallbackOnUserThread,
                         weak_ptr_factory_.GetWeakPtr(), task, thread_index)));
}

void BidderWorklet::OnDirectFromSellerPerBuyerSignalsDownloadedReportWin(
    ReportWinTaskList::iterator task,
    DirectFromSellerSignalsRequester::Result result) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_);

  task->direct_from_seller_result_per_buyer_signals = std::move(result);
  task->direct_from_seller_request_per_buyer_signals.reset();

  // The two direct from seller signals metrics for tracing are combined since
  // they should be roughly the same.
  task->wait_direct_from_seller_signals =
      std::max(task->wait_direct_from_seller_signals,
               base::TimeTicks::Now() - task->trace_wait_deps_start);

  RunReportWinIfReady(task);
}

void BidderWorklet::OnDirectFromSellerAuctionSignalsDownloadedReportWin(
    ReportWinTaskList::iterator task,
    DirectFromSellerSignalsRequester::Result result) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_);

  task->direct_from_seller_result_auction_signals = std::move(result);
  task->direct_from_seller_request_auction_signals.reset();

  // The two direct from seller signals metrics for tracing are combined since
  // they should be roughly the same.
  task->wait_direct_from_seller_signals =
      std::max(task->wait_direct_from_seller_signals,
               base::TimeTicks::Now() - task->trace_wait_deps_start);

  RunReportWinIfReady(task);
}

bool BidderWorklet::IsReadyToReportWin(const ReportWinTask& task) const {
  return IsCodeReady() && !task.direct_from_seller_request_per_buyer_signals &&
         !task.direct_from_seller_request_auction_signals;
}

void BidderWorklet::RunReportWinIfReady(ReportWinTaskList::iterator task) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_);
  if (!IsReadyToReportWin(*task)) {
    return;
  }

  // End "wait_report_win_deps" trace event.
  TRACE_EVENT_END(
      "fledge", perfetto::Track(task->trace_id), "data",
      [&](perfetto::TracedValue trace_context) {
        auto dict = std::move(trace_context).WriteDictionary();
        if (!task->wait_code.is_zero()) {
          dict.Add("wait_code_ms", task->wait_code.InMillisecondsF());
        }
        if (!task->wait_direct_from_seller_signals.is_zero()) {
          dict.Add("wait_direct_from_seller_signals_ms",
                   task->wait_direct_from_seller_signals.InMillisecondsF());
        }
      });
  TRACE_EVENT_BEGIN("fledge", "post_v8_task", perfetto::Track(task->trace_id));

  // Other than the callback field, no fields of `task` are needed after this
  // point, so can consume them instead of copying them.
  size_t thread_index = thread_selector_.GetThread();
  cancelable_task_tracker_.PostTask(
      v8_runners_[thread_index].get(), FROM_HERE,
      base::BindOnce(
          &BidderWorklet::V8State::ReportWin,
          base::Unretained(v8_state_[thread_index].get()),
          std::move(task->is_for_additional_bid),
          std::move(task->interest_group_name_reporting_id),
          std::move(task->buyer_reporting_id),
          std::move(task->buyer_and_seller_reporting_id),
          std::move(task->selected_buyer_and_seller_reporting_id),
          std::move(task->auction_signals_json),
          std::move(task->per_buyer_signals_json),
          std::move(task->direct_from_seller_result_per_buyer_signals),
          std::move(task->direct_from_seller_per_buyer_signals_header_ad_slot),
          std::move(task->direct_from_seller_result_auction_signals),
          std::move(task->direct_from_seller_auction_signals_header_ad_slot),
          std::move(task->seller_signals_json), std::move(task->kanon_status),
          std::move(task->browser_signal_render_url),
          std::move(task->browser_signal_bid),
          std::move(task->browser_signal_bid_currency),
          std::move(task->browser_signal_highest_scoring_other_bid),
          std::move(task->browser_signal_highest_scoring_other_bid_currency),
          std::move(task->browser_signal_made_highest_scoring_other_bid),
          std::move(task->browser_signal_ad_cost),
          std::move(task->browser_signal_modeling_signals),
          std::move(task->browser_signal_join_count),
          std::move(task->browser_signal_recency),
          std::move(task->browser_signal_seller_origin),
          std::move(task->browser_signal_top_level_seller_origin),
          std::move(task->browser_signal_reporting_timeout),
          std::move(task->bidding_signals_data_version),
          std::move(task->aggregate_win_signals), task->trace_id,
          base::BindOnce(&BidderWorklet::DeliverReportWinOnUserThread,
                         weak_ptr_factory_.GetWeakPtr(), task, thread_index)));
}

void BidderWorklet::DeliverBidCallbackOnUserThread(
    GenerateBidTaskList::iterator task,
    size_t thread_index_used_for_task,
    std::vector<mojom::BidderWorkletBidPtr> bids,
    std::optional<uint32_t> bidding_signals_data_version,
    std::optional<GURL> debug_loss_report_url,
    std::optional<GURL> debug_win_report_url,
    std::optional<double> set_priority,
    base::flat_map<std::string, mojom::PrioritySignalsDoublePtr>
        update_priority_signals_overrides,
    PrivateAggregationRequests pa_requests,
    PrivateAggregationRequests non_kanon_pa_requests,
    RealTimeReportingContributions real_time_contributions,
    base::TimeDelta bidding_latency,
    mojom::RejectReason reject_reason,
    bool script_timed_out,
    std::vector<std::string> error_msgs) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_);

  // TODO(https://crbug.com): Remove once bug is identified and fixed.
  CHECK(!debug_loss_report_url || debug_loss_report_url->is_valid());
  CHECK(!debug_win_report_url || debug_win_report_url->is_valid());

  thread_selector_.TaskCompletedOnThread(thread_index_used_for_task);

  error_msgs.insert(error_msgs.end(), load_code_error_msgs_.begin(),
                    load_code_error_msgs_.end());
  if (task->trusted_bidding_signals_error_msg) {
    error_msgs.emplace_back(
        std::move(task->trusted_bidding_signals_error_msg).value());
  }
  task->generate_bid_client->OnGenerateBidComplete(
      std::move(bids), bidding_signals_data_version, debug_loss_report_url,
      debug_win_report_url, set_priority,
      std::move(update_priority_signals_overrides), std::move(pa_requests),
      std::move(non_kanon_pa_requests), std::move(real_time_contributions),
      mojom::BidderTimingMetrics::New(
          /*js_fetch_latency=*/js_fetch_latency_,
          /*wasm_fetch_latency=*/wasm_fetch_latency_,
          /*script_latency=*/bidding_latency,
          /*script_timed_out=*/script_timed_out),
      mojom::GenerateBidDependencyLatencies::New(
          /*code_ready_latency=*/NullOptIfZero(task->wait_code),
          /*config_promises_latency=*/NullOptIfZero(task->wait_promises),
          /*direct_from_seller_signals_latency=*/
          NullOptIfZero(task->wait_direct_from_seller_signals),
          /*trusted_bidding_signals_latency=*/
          NullOptIfZero(task->wait_trusted_signals),
          /*deps_wait_start_time=*/task->trace_wait_deps_start,
          /*generate_bid_start_time=*/task->generate_bid_start_time,
          /*generate_bid_finish_time=*/base::TimeTicks::Now()),
      reject_reason, error_msgs);
  CleanUpBidTaskOnUserThread(task);
}

void BidderWorklet::CleanUpBidTaskOnUserThread(
    GenerateBidTaskList::iterator task) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_);
  // Disconnect the FinalizeGenerateBid pipe, if any, since that refers
  // to `task` (it generally will be closed already, but may not be if
  // GenerateBidClient disconnected before FinalizeGenerateBid was called).
  if (task->finalize_generate_bid_receiver_id.has_value()) {
    finalize_receiver_set_.Remove(*task->finalize_generate_bid_receiver_id);
  }
  generate_bid_tasks_.erase(task);
}

void BidderWorklet::DeliverReportWinOnUserThread(
    ReportWinTaskList::iterator task,
    size_t thread_index_used_for_task,
    std::optional<GURL> report_url,
    base::flat_map<std::string, GURL> ad_beacon_map,
    base::flat_map<std::string, std::string> ad_macro_map,
    PrivateAggregationRequests pa_requests,
    mojom::PrivateModelTrainingRequestDataPtr pmt_request_data,
    base::TimeDelta reporting_latency,
    bool script_timed_out,
    std::vector<std::string> errors) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_);
  thread_selector_.TaskCompletedOnThread(thread_index_used_for_task);
  errors.insert(errors.end(), load_code_error_msgs_.begin(),
                load_code_error_msgs_.end());
  std::move(task->callback)
      .Run(std::move(report_url), std::move(ad_beacon_map),
           std::move(ad_macro_map), std::move(pa_requests),
           std::move(pmt_request_data),
           mojom::BidderTimingMetrics::New(
               /*js_fetch_latency=*/js_fetch_latency_,
               /*wasm_fetch_latency=*/wasm_fetch_latency_,
               /*script_latency=*/reporting_latency,
               /*script_timed_out=*/script_timed_out),
           std::move(errors));
  report_win_tasks_.erase(task);
}

bool BidderWorklet::IsCodeReady() const {
  // If `paused_`, loading hasn't started yet. Otherwise, null loaders indicate
  // the worklet script has loaded successfully, and there's no WASM helper, or
  // it has also loaded successfully.
  return !paused_ && !worklet_loader_ && !wasm_loader_;
}

}  // namespace auction_worklet