| // 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/browser/interest_group/auction_worklet_manager.h" |
| |
| #include <stdint.h> |
| |
| #include <map> |
| #include <memory> |
| #include <string> |
| #include <tuple> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "base/check.h" |
| #include "base/compiler_specific.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/observer_list.h" |
| #include "base/strings/strcat.h" |
| #include "content/browser/interest_group/auction_process_manager.h" |
| #include "content/browser/interest_group/auction_url_loader_factory_proxy.h" |
| #include "content/browser/interest_group/debuggable_auction_worklet.h" |
| #include "content/browser/renderer_host/render_frame_host_impl.h" |
| #include "content/common/content_export.h" |
| #include "content/services/auction_worklet/public/mojom/bidder_worklet.mojom.h" |
| #include "content/services/auction_worklet/public/mojom/seller_worklet.mojom.h" |
| #include "mojo/public/cpp/bindings/pending_receiver.h" |
| #include "mojo/public/cpp/bindings/pending_remote.h" |
| #include "mojo/public/cpp/bindings/remote.h" |
| #include "services/network/public/mojom/client_security_state.mojom.h" |
| #include "services/network/public/mojom/url_loader_factory.mojom.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| #include "url/gurl.h" |
| #include "url/origin.h" |
| |
| namespace content { |
| |
| class AuctionWorkletManager::WorkletOwner |
| : public base::RefCounted<AuctionWorkletManager::WorkletOwner> { |
| public: |
| // On construction, attempts to immediately create a worklet. If that |
| // succeeds, it's up to the AuctionWorkletManager itself to inform the caller |
| // a worklet was synchronously created. Otherwise, the WorkletOwner will |
| // immediately start waiting for a process to be available, and once one is, |
| // create a worklet, informing all associated WorkletHandles. |
| WorkletOwner(AuctionWorkletManager* worklet_manager, |
| WorkletInfo worklet_info); |
| |
| // Registers/unregisters a WorkletHandle for the worklet `this` owns. |
| void RegisterHandle(WorkletHandle* handle) { handles_.AddObserver(handle); } |
| void UnregisterHandle(WorkletHandle* handle) { |
| handles_.RemoveObserver(handle); |
| } |
| |
| auction_worklet::mojom::BidderWorklet* bidder_worklet() { |
| DCHECK(bidder_worklet_); |
| return bidder_worklet_.get(); |
| } |
| |
| auction_worklet::mojom::SellerWorklet* seller_worklet() { |
| DCHECK(seller_worklet_); |
| return seller_worklet_.get(); |
| } |
| |
| const WorkletInfo& worklet_info() const { return worklet_info_; } |
| |
| // Whether or not a worklet has been created. Once a worklet has been created, |
| // always returns true, even after disconnect or error. |
| bool worklet_created() const { |
| return bidder_worklet_.is_bound() || seller_worklet_.is_bound(); |
| } |
| |
| private: |
| friend class base::RefCounted<WorkletOwner>; |
| |
| ~WorkletOwner(); |
| |
| // Called if the worklet becomes unusable. This happens on destruction (once |
| // all refs have been released) or when the Mojo pipe is closed. Removes |
| // `this` from `worklet_manager_`, so that future requests using the same |
| // `worklet_info` will trigger creation of a new worklet, rather than trying |
| // to use the unusable one. |
| void WorkletNoLongerUsable(); |
| |
| // Called once the AuctionProcessManager provides a process to load a worklet |
| // in. Immediately loads the worklet and informs WorkletHandles. |
| void OnProcessAssigned(); |
| |
| // Mojo disconnect with reason handler. If there's a description, it's a load |
| // error. Otherwise, it's a crash. Passes error information on to all |
| // associated WorkletHandles. |
| void OnWorkletDisconnected(uint32_t /* custom_reason */, |
| const std::string& description); |
| |
| // Set to null once `this` is removed from AuctionWorkletManager's |
| // WorkletOwner list, which happens on destruction or on Mojo pipe closure. |
| // The latter allows a handle to still exist and refer to a WorkletOwner with |
| // a broken Worklet pipe, while new requests for the same worklet will result |
| // in creating a fresh Mojo worklet. |
| raw_ptr<AuctionWorkletManager> worklet_manager_; |
| |
| const WorkletInfo worklet_info_; |
| |
| AuctionProcessManager::ProcessHandle process_handle_; |
| |
| // List of live WorkletHandles to notify in case a Worklet is created or on |
| // Mojo pipe closure. |
| base::ObserverList<WorkletHandle, /*check_empty=*/true> handles_; |
| |
| std::unique_ptr<AuctionURLLoaderFactoryProxy> url_loader_factory_proxy_; |
| mojo::Remote<auction_worklet::mojom::BidderWorklet> bidder_worklet_; |
| mojo::Remote<auction_worklet::mojom::SellerWorklet> seller_worklet_; |
| // This must be destroyed before the worklet it's passed, since it hangs on to |
| // a raw pointer to it. |
| std::unique_ptr<DebuggableAuctionWorklet> worklet_debug_; |
| }; |
| |
| AuctionWorkletManager::WorkletOwner::WorkletOwner( |
| AuctionWorkletManager* worklet_manager, |
| WorkletInfo worklet_info) |
| : worklet_manager_(worklet_manager), |
| worklet_info_(std::move(worklet_info)) { |
| if (worklet_manager_->auction_process_manager()->RequestWorkletService( |
| worklet_info_.type, url::Origin::Create(worklet_info_.script_url), |
| worklet_manager_->delegate()->GetFrameSiteInstance(), |
| &process_handle_, |
| base::BindOnce( |
| &AuctionWorkletManager::WorkletOwner::OnProcessAssigned, |
| base::Unretained(this)))) { |
| OnProcessAssigned(); |
| } |
| } |
| |
| AuctionWorkletManager::WorkletOwner::~WorkletOwner() { |
| WorkletNoLongerUsable(); |
| } |
| |
| void AuctionWorkletManager::WorkletOwner::WorkletNoLongerUsable() { |
| if (worklet_manager_) { |
| worklet_manager_->OnWorkletNoLongerUsable(this); |
| worklet_manager_ = nullptr; |
| } |
| } |
| |
| void AuctionWorkletManager::WorkletOwner::OnProcessAssigned() { |
| DCHECK(!bidder_worklet_.is_bound()); |
| DCHECK(!seller_worklet_.is_bound()); |
| |
| Delegate* delegate = worklet_manager_->delegate(); |
| mojo::PendingRemote<network::mojom::URLLoaderFactory> url_loader_factory; |
| url_loader_factory_proxy_ = std::make_unique<AuctionURLLoaderFactoryProxy>( |
| url_loader_factory.InitWithNewPipeAndPassReceiver(), |
| base::BindRepeating(&Delegate::GetFrameURLLoaderFactory, |
| base::Unretained(delegate)), |
| base::BindRepeating(&Delegate::GetTrustedURLLoaderFactory, |
| base::Unretained(delegate)), |
| worklet_manager_->top_window_origin(), worklet_manager_->frame_origin(), |
| /*is_for_seller_=*/worklet_info_.type == WorkletType::kSeller, |
| delegate->GetClientSecurityState(), worklet_info_.script_url, |
| worklet_info_.wasm_url, worklet_info_.signals_url); |
| |
| switch (worklet_info_.type) { |
| case WorkletType::kBidder: { |
| mojo::PendingReceiver<auction_worklet::mojom::BidderWorklet> |
| worklet_receiver = bidder_worklet_.BindNewPipeAndPassReceiver(); |
| worklet_debug_ = base::WrapUnique(new DebuggableAuctionWorklet( |
| delegate->GetFrame(), &process_handle_, worklet_info_.script_url, |
| bidder_worklet_.get())); |
| process_handle_.GetService()->LoadBidderWorklet( |
| std::move(worklet_receiver), worklet_debug_->should_pause_on_start(), |
| std::move(url_loader_factory), worklet_info_.script_url, |
| worklet_info_.wasm_url, worklet_info_.signals_url, |
| worklet_manager_->top_window_origin(), |
| worklet_info_.experiment_group_id.has_value(), |
| worklet_info_.experiment_group_id.value_or(0u)); |
| bidder_worklet_.set_disconnect_with_reason_handler(base::BindOnce( |
| &WorkletOwner::OnWorkletDisconnected, base::Unretained(this))); |
| break; |
| } |
| |
| case WorkletType::kSeller: { |
| mojo::PendingReceiver<auction_worklet::mojom::SellerWorklet> |
| worklet_receiver = seller_worklet_.BindNewPipeAndPassReceiver(); |
| worklet_debug_ = base::WrapUnique(new DebuggableAuctionWorklet( |
| delegate->GetFrame(), &process_handle_, worklet_info_.script_url, |
| seller_worklet_.get())); |
| process_handle_.GetService()->LoadSellerWorklet( |
| std::move(worklet_receiver), worklet_debug_->should_pause_on_start(), |
| std::move(url_loader_factory), worklet_info_.script_url, |
| worklet_info_.signals_url, worklet_manager_->top_window_origin(), |
| worklet_info_.experiment_group_id.has_value(), |
| worklet_info_.experiment_group_id.value_or(0u)); |
| seller_worklet_.set_disconnect_with_reason_handler(base::BindOnce( |
| &WorkletOwner::OnWorkletDisconnected, base::Unretained(this))); |
| break; |
| } |
| } |
| |
| for (auto& handle : handles_) { |
| handle.OnWorkletAvailable(); |
| } |
| } |
| |
| void AuctionWorkletManager::WorkletOwner::OnWorkletDisconnected( |
| uint32_t /* custom_reason */, |
| const std::string& description) { |
| // This may only be invoked once per worklet, and `worklet_manager_` is only |
| // cleared by this method and on destruction, so it should not be null. |
| DCHECK(worklet_manager_); |
| |
| WorkletNoLongerUsable(); |
| |
| FatalErrorType error_type; |
| std::vector<std::string> errors; |
| // If there's a description, it's a load failure. Otherwise, it's a crash. |
| if (!description.empty()) { |
| error_type = FatalErrorType::kScriptLoadFailed; |
| errors.push_back(description); |
| } else { |
| error_type = FatalErrorType::kWorkletCrash; |
| errors.emplace_back( |
| base::StrCat({worklet_info_.script_url.spec(), " crashed."})); |
| } |
| |
| for (auto& handle : handles_) { |
| handle.OnFatalError(error_type, errors); |
| } |
| } |
| |
| AuctionWorkletManager::WorkletHandle::~WorkletHandle() { |
| worklet_owner_->UnregisterHandle(this); |
| } |
| |
| auction_worklet::mojom::BidderWorklet* |
| AuctionWorkletManager::WorkletHandle::GetBidderWorklet() { |
| DCHECK_EQ(WorkletType::kBidder, worklet_owner_->worklet_info().type); |
| DCHECK(worklet_owner_->bidder_worklet()); |
| return worklet_owner_->bidder_worklet(); |
| } |
| |
| auction_worklet::mojom::SellerWorklet* |
| AuctionWorkletManager::WorkletHandle::GetSellerWorklet() { |
| DCHECK_EQ(WorkletType::kSeller, worklet_owner_->worklet_info().type); |
| DCHECK(worklet_owner_->seller_worklet()); |
| return worklet_owner_->seller_worklet(); |
| } |
| |
| AuctionWorkletManager::WorkletHandle::WorkletHandle( |
| scoped_refptr<WorkletOwner> worklet_owner, |
| base::OnceClosure worklet_available_callback, |
| FatalErrorCallback fatal_error_callback) |
| : worklet_owner_(std::move(worklet_owner)), |
| worklet_available_callback_(std::move(worklet_available_callback)), |
| fatal_error_callback_(std::move(fatal_error_callback)) { |
| DCHECK(worklet_available_callback_); |
| DCHECK(fatal_error_callback_); |
| |
| // Delete `worklet_available_callback_` if worklet is already available, since |
| // it won't be needed. |
| if (worklet_owner_->worklet_created()) |
| worklet_available_callback_.Reset(); |
| |
| worklet_owner_->RegisterHandle(this); |
| } |
| |
| void AuctionWorkletManager::WorkletHandle::OnWorkletAvailable() { |
| DCHECK(worklet_available_callback_); |
| std::move(worklet_available_callback_).Run(); |
| } |
| |
| void AuctionWorkletManager::WorkletHandle::OnFatalError( |
| FatalErrorType type, |
| const std::vector<std::string>& errors) { |
| DCHECK(fatal_error_callback_); |
| std::move(fatal_error_callback_).Run(type, errors); |
| } |
| |
| bool AuctionWorkletManager::WorkletHandle::worklet_created() const { |
| return worklet_owner_->worklet_created(); |
| } |
| |
| AuctionWorkletManager::AuctionWorkletManager( |
| AuctionProcessManager* auction_process_manager, |
| url::Origin top_window_origin, |
| url::Origin frame_origin, |
| Delegate* delegate) |
| : auction_process_manager_(auction_process_manager), |
| top_window_origin_(std::move(top_window_origin)), |
| frame_origin_(std::move(frame_origin)), |
| delegate_(delegate) {} |
| |
| AuctionWorkletManager::~AuctionWorkletManager() = default; |
| |
| bool AuctionWorkletManager::WorkletInfo::WorkletInfo::operator<( |
| const WorkletInfo& other) const { |
| return std::tie(type, script_url, wasm_url, signals_url, |
| experiment_group_id) < |
| std::tie(other.type, other.script_url, other.wasm_url, |
| other.signals_url, other.experiment_group_id); |
| } |
| |
| bool AuctionWorkletManager::RequestBidderWorklet( |
| const GURL& bidding_logic_url, |
| const absl::optional<GURL>& wasm_url, |
| const absl::optional<GURL>& trusted_bidding_signals_url, |
| absl::optional<uint16_t> experiment_group_id, |
| base::OnceClosure worklet_available_callback, |
| FatalErrorCallback fatal_error_callback, |
| std::unique_ptr<WorkletHandle>& out_worklet_handle) { |
| DCHECK(!out_worklet_handle); |
| |
| WorkletInfo worklet_info(WorkletType::kBidder, |
| /*script_url=*/bidding_logic_url, wasm_url, |
| /*signals_url=*/trusted_bidding_signals_url, |
| trusted_bidding_signals_url.has_value() |
| ? experiment_group_id |
| : absl::nullopt); |
| return RequestWorkletInternal( |
| std::move(worklet_info), std::move(worklet_available_callback), |
| std::move(fatal_error_callback), out_worklet_handle); |
| } |
| |
| bool AuctionWorkletManager::RequestSellerWorklet( |
| const GURL& decision_logic_url, |
| const absl::optional<GURL>& trusted_scoring_signals_url, |
| absl::optional<uint16_t> experiment_group_id, |
| base::OnceClosure worklet_available_callback, |
| FatalErrorCallback fatal_error_callback, |
| std::unique_ptr<WorkletHandle>& out_worklet_handle) { |
| DCHECK(!out_worklet_handle); |
| |
| WorkletInfo worklet_info(WorkletType::kSeller, |
| /*script_url=*/decision_logic_url, |
| /*wasm_url=*/absl::nullopt, |
| /*signals_url=*/trusted_scoring_signals_url, |
| experiment_group_id); |
| return RequestWorkletInternal( |
| std::move(worklet_info), std::move(worklet_available_callback), |
| std::move(fatal_error_callback), out_worklet_handle); |
| } |
| |
| AuctionWorkletManager::WorkletInfo::WorkletInfo( |
| WorkletType type, |
| const GURL& script_url, |
| const absl::optional<GURL>& wasm_url, |
| const absl::optional<GURL>& signals_url, |
| absl::optional<uint16_t> experiment_group_id) |
| : type(type), |
| script_url(script_url), |
| wasm_url(wasm_url), |
| signals_url(signals_url), |
| experiment_group_id(experiment_group_id) {} |
| |
| AuctionWorkletManager::WorkletInfo::WorkletInfo(const WorkletInfo&) = default; |
| AuctionWorkletManager::WorkletInfo::WorkletInfo(WorkletInfo&&) = default; |
| AuctionWorkletManager::WorkletInfo::~WorkletInfo() = default; |
| |
| bool AuctionWorkletManager::RequestWorkletInternal( |
| WorkletInfo worklet_info, |
| base::OnceClosure worklet_available_callback, |
| FatalErrorCallback fatal_error_callback, |
| std::unique_ptr<WorkletHandle>& out_worklet_handle) { |
| auto worklet_it = worklets_.find(worklet_info); |
| scoped_refptr<WorkletOwner> worklet; |
| if (worklet_it != worklets_.end()) { |
| worklet = worklet_it->second; |
| } else { |
| // Can't just insert in the map and put a reference in `worklet_it`, since |
| // need to keep a live reference. |
| worklet = base::MakeRefCounted<WorkletOwner>(this, worklet_info); |
| worklets_.emplace(std::pair(std::move(worklet_info), worklet.get())); |
| } |
| out_worklet_handle.reset(new WorkletHandle( |
| std::move(worklet), std::move(worklet_available_callback), |
| std::move(fatal_error_callback))); |
| return out_worklet_handle->worklet_created(); |
| } |
| |
| void AuctionWorkletManager::OnWorkletNoLongerUsable(WorkletOwner* worklet) { |
| DCHECK(worklets_.count(worklet->worklet_info())); |
| DCHECK_EQ(worklet, worklets_[worklet->worklet_info()]); |
| |
| worklets_.erase(worklet->worklet_info()); |
| } |
| |
| } // namespace content |