| // 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/shared_storage/shared_storage_worklet_host.h" |
| |
| #include <stdint.h> |
| |
| #include <optional> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/check.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/not_fatal_until.h" |
| #include "base/strings/strcat.h" |
| #include "base/time/time.h" |
| #include "components/services/storage/shared_storage/shared_storage_manager.h" |
| #include "content/browser/attribution_reporting/attribution_manager.h" |
| #include "content/browser/devtools/devtools_instrumentation.h" |
| #include "content/browser/devtools/shared_storage_worklet_devtools_manager.h" |
| #include "content/browser/fenced_frame/fenced_frame_reporter.h" |
| #include "content/browser/private_aggregation/private_aggregation_budget_key.h" |
| #include "content/browser/private_aggregation/private_aggregation_host.h" |
| #include "content/browser/private_aggregation/private_aggregation_manager.h" |
| #include "content/browser/renderer_host/page_impl.h" |
| #include "content/browser/renderer_host/render_frame_host_impl.h" |
| #include "content/browser/shared_storage/shared_storage_document_service_impl.h" |
| #include "content/browser/shared_storage/shared_storage_render_thread_worklet_driver.h" |
| #include "content/browser/shared_storage/shared_storage_url_loader_factory_proxy.h" |
| #include "content/browser/shared_storage/shared_storage_worklet_driver.h" |
| #include "content/browser/shared_storage/shared_storage_worklet_host_manager.h" |
| #include "content/common/renderer.mojom.h" |
| #include "content/public/browser/browser_context.h" |
| #include "mojo/public/cpp/bindings/pending_remote.h" |
| #include "storage/browser/blob/blob_url_loader_factory.h" |
| #include "third_party/blink/public/common/features.h" |
| #include "third_party/blink/public/common/shared_storage/shared_storage_utils.h" |
| #include "third_party/blink/public/mojom/private_aggregation/private_aggregation_host.mojom.h" |
| #include "third_party/blink/public/mojom/shared_storage/shared_storage_worklet_service.mojom.h" |
| #include "third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom.h" |
| #include "third_party/blink/public/mojom/worker/worklet_global_scope_creation_params.mojom.h" |
| #include "url/origin.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| using AccessType = |
| SharedStorageWorkletHostManager::SharedStorageObserverInterface::AccessType; |
| |
| constexpr base::TimeDelta kKeepAliveTimeout = base::Seconds(2); |
| |
| using SharedStorageURNMappingResult = |
| FencedFrameURLMapping::SharedStorageURNMappingResult; |
| |
| using OperationResult = storage::SharedStorageManager::OperationResult; |
| using GetResult = storage::SharedStorageManager::GetResult; |
| |
| void LogSharedStorageWorkletErrorFromErrorMessage( |
| bool from_select_url, |
| const std::string& error_message) { |
| if (error_message == blink::kSharedStorageModuleScriptNotLoadedErrorMessage) { |
| LogSharedStorageWorkletError( |
| from_select_url ? blink::SharedStorageWorkletErrorType:: |
| kSelectURLNonWebVisibleModuleScriptNotLoaded |
| : blink::SharedStorageWorkletErrorType:: |
| kRunNonWebVisibleModuleScriptNotLoaded); |
| } else if (error_message == |
| blink::kSharedStorageOperationNotFoundErrorMessage) { |
| LogSharedStorageWorkletError( |
| from_select_url ? blink::SharedStorageWorkletErrorType:: |
| kSelectURLNonWebVisibleOperationNotFound |
| : blink::SharedStorageWorkletErrorType:: |
| kRunNonWebVisibleOperationNotFound); |
| } else if (error_message == |
| blink:: |
| kSharedStorageEmptyOperationDefinitionInstanceErrorMessage) { |
| LogSharedStorageWorkletError( |
| from_select_url |
| ? blink::SharedStorageWorkletErrorType:: |
| kSelectURLNonWebVisibleEmptyOperationDefinitionInstance |
| : blink::SharedStorageWorkletErrorType:: |
| kRunNonWebVisibleEmptyOperationDefinitionInstance); |
| } else if (error_message == |
| blink::kSharedStorageCannotDeserializeDataErrorMessage) { |
| LogSharedStorageWorkletError( |
| from_select_url ? blink::SharedStorageWorkletErrorType:: |
| kSelectURLNonWebVisibleCannotDeserializeData |
| : blink::SharedStorageWorkletErrorType:: |
| kRunNonWebVisibleCannotDeserializeData); |
| } else if (error_message == |
| blink::kSharedStorageEmptyScriptResultErrorMessage) { |
| LogSharedStorageWorkletError( |
| from_select_url ? blink::SharedStorageWorkletErrorType:: |
| kSelectURLNonWebVisibleEmptyScriptResult |
| : blink::SharedStorageWorkletErrorType:: |
| kRunNonWebVisibleEmptyScriptResult); |
| } else if (error_message == |
| blink::kSharedStorageReturnValueToIntErrorMessage && |
| from_select_url) { |
| LogSharedStorageWorkletError(blink::SharedStorageWorkletErrorType:: |
| kSelectURLNonWebVisibleReturnValueToInt); |
| } else if (error_message == |
| blink::kSharedStorageReturnValueOutOfRangeErrorMessage && |
| from_select_url) { |
| LogSharedStorageWorkletError( |
| blink::SharedStorageWorkletErrorType:: |
| kSelectURLNonWebVisibleReturnValueOutOfRange); |
| } else { |
| LogSharedStorageWorkletError( |
| from_select_url |
| ? blink::SharedStorageWorkletErrorType::kSelectURLNonWebVisibleOther |
| : blink::SharedStorageWorkletErrorType::kRunNonWebVisibleOther); |
| } |
| } |
| |
| SharedStorageURNMappingResult CreateSharedStorageURNMappingResult( |
| StoragePartition* storage_partition, |
| BrowserContext* browser_context, |
| PageImpl* page, |
| const url::Origin& main_frame_origin, |
| const url::Origin& shared_storage_origin, |
| const net::SchemefulSite& shared_storage_site, |
| std::vector<blink::mojom::SharedStorageUrlWithMetadataPtr> |
| urls_with_metadata, |
| uint32_t index, |
| double budget_remaining, |
| blink::SharedStorageSelectUrlBudgetStatus& budget_status) { |
| DCHECK_EQ(budget_status, blink::SharedStorageSelectUrlBudgetStatus::kOther); |
| DCHECK_GT(urls_with_metadata.size(), 0u); |
| DCHECK_LT(index, urls_with_metadata.size()); |
| DCHECK(page); |
| |
| double budget_to_charge = std::log2(urls_with_metadata.size()); |
| |
| // If we are running out of budget, consider this mapping to be failed. Use |
| // the default URL, and there's no need to further charge the budget. |
| if (budget_to_charge > 0.0) { |
| budget_status = (budget_to_charge > budget_remaining) |
| ? blink::SharedStorageSelectUrlBudgetStatus:: |
| kInsufficientSiteNavigationBudget |
| : page->CheckAndMaybeDebitSelectURLBudgets( |
| shared_storage_site, budget_to_charge); |
| if (budget_status != |
| blink::SharedStorageSelectUrlBudgetStatus::kSufficientBudget) { |
| index = 0; |
| budget_to_charge = 0.0; |
| } |
| } else { |
| budget_status = |
| blink::SharedStorageSelectUrlBudgetStatus::kSufficientBudget; |
| } |
| |
| GURL mapped_url = urls_with_metadata[index]->url; |
| |
| scoped_refptr<FencedFrameReporter> fenced_frame_reporter; |
| if (!urls_with_metadata[index]->reporting_metadata.empty()) { |
| fenced_frame_reporter = FencedFrameReporter::CreateForSharedStorage( |
| storage_partition->GetURLLoaderFactoryForBrowserProcess(), |
| browser_context, shared_storage_origin, |
| urls_with_metadata[index]->reporting_metadata, main_frame_origin); |
| } |
| return SharedStorageURNMappingResult( |
| mapped_url, |
| SharedStorageBudgetMetadata{.site = shared_storage_site, |
| .budget_to_charge = budget_to_charge}, |
| std::move(fenced_frame_reporter)); |
| } |
| |
| // TODO(crbug.com/40847123): Consider moving this function to |
| // third_party/blink/common/fenced_frame/fenced_frame_utils.cc. |
| bool IsValidFencedFrameReportingURL(const GURL& url) { |
| if (!url.is_valid()) { |
| return false; |
| } |
| return url.SchemeIs(url::kHttpsScheme); |
| } |
| |
| } // namespace |
| |
| class SharedStorageWorkletHost::ScopedDevToolsHandle |
| : blink::mojom::WorkletDevToolsHost { |
| public: |
| explicit ScopedDevToolsHandle(SharedStorageWorkletHost& owner) |
| : owner_(owner), devtools_token_(base::UnguessableToken::Create()) { |
| SharedStorageWorkletDevToolsManager::GetInstance()->WorkletCreated( |
| owner, devtools_token_, wait_for_debugger_); |
| } |
| |
| ScopedDevToolsHandle(const ScopedDevToolsHandle&) = delete; |
| ScopedDevToolsHandle& operator=(const ScopedDevToolsHandle&) = delete; |
| |
| ~ScopedDevToolsHandle() override { |
| SharedStorageWorkletDevToolsManager::GetInstance()->WorkletDestroyed( |
| *owner_); |
| } |
| |
| // blink::mojom::WorkletDevToolsHost: |
| void OnReadyForInspection( |
| mojo::PendingRemote<blink::mojom::DevToolsAgent> agent_remote, |
| mojo::PendingReceiver<blink::mojom::DevToolsAgentHost> |
| agent_host_receiver) override { |
| SharedStorageWorkletDevToolsManager::GetInstance() |
| ->WorkletReadyForInspection(*owner_, std::move(agent_remote), |
| std::move(agent_host_receiver)); |
| } |
| |
| const base::UnguessableToken& devtools_token() const { |
| return devtools_token_; |
| } |
| |
| bool wait_for_debugger() const { return wait_for_debugger_; } |
| |
| mojo::PendingRemote<blink::mojom::WorkletDevToolsHost> |
| BindNewPipeAndPassRemote() { |
| return host_.BindNewPipeAndPassRemote(); |
| } |
| |
| private: |
| raw_ref<SharedStorageWorkletHost> owner_; |
| |
| mojo::Receiver<blink::mojom::WorkletDevToolsHost> host_{this}; |
| |
| bool wait_for_debugger_ = false; |
| |
| const base::UnguessableToken devtools_token_; |
| }; |
| |
| SharedStorageWorkletHost::SharedStorageWorkletHost( |
| SharedStorageDocumentServiceImpl& document_service, |
| const url::Origin& frame_origin, |
| const GURL& script_source_url, |
| network::mojom::CredentialsMode credentials_mode, |
| const std::vector<blink::mojom::OriginTrialFeature>& origin_trial_features, |
| mojo::PendingAssociatedReceiver<blink::mojom::SharedStorageWorkletHost> |
| worklet_host, |
| blink::mojom::SharedStorageDocumentService::CreateWorkletCallback callback) |
| : driver_(std::make_unique<SharedStorageRenderThreadWorkletDriver>( |
| document_service.render_frame_host(), |
| script_source_url)), |
| document_service_(document_service.GetWeakPtr()), |
| page_( |
| static_cast<PageImpl&>(document_service.render_frame_host().GetPage()) |
| .GetWeakPtrImpl()), |
| storage_partition_(static_cast<StoragePartitionImpl*>( |
| document_service.render_frame_host().GetStoragePartition())), |
| shared_storage_manager_(storage_partition_->GetSharedStorageManager()), |
| shared_storage_worklet_host_manager_( |
| storage_partition_->GetSharedStorageWorkletHostManager()), |
| browser_context_( |
| document_service.render_frame_host().GetBrowserContext()), |
| shared_storage_origin_(url::Origin::Create(script_source_url)), |
| shared_storage_site_(net::SchemefulSite(shared_storage_origin_)), |
| main_frame_origin_(document_service.main_frame_origin()), |
| is_same_origin_worklet_(document_service.render_frame_host() |
| .GetLastCommittedOrigin() |
| .IsSameOriginWith(shared_storage_origin_)), |
| creation_time_(base::TimeTicks::Now()) { |
| GetContentClient()->browser()->OnSharedStorageWorkletHostCreated( |
| &(document_service.render_frame_host())); |
| |
| receiver_.Bind(std::move(worklet_host)); |
| |
| // This is invoked from `document_service_`. Thus both `page_` and |
| // `document_service_` should be valid. |
| DCHECK(page_); |
| DCHECK(document_service_); |
| IncrementPendingOperationsCount(); |
| |
| script_source_url_ = script_source_url; |
| origin_trial_features_ = origin_trial_features; |
| |
| devtools_handle_ = std::make_unique<ScopedDevToolsHandle>(*this); |
| |
| // Initialize the `URLLoaderFactory` now, as later on the worklet may enter |
| // keep-alive phase and won't have access to the `RenderFrameHost`. |
| mojo::PendingRemote<network::mojom::URLLoaderFactory> |
| frame_url_loader_factory; |
| if (script_source_url.SchemeIsBlob()) { |
| storage::BlobURLLoaderFactory::Create( |
| static_cast<StoragePartitionImpl*>( |
| document_service_->render_frame_host() |
| .GetProcess() |
| ->GetStoragePartition()) |
| ->GetBlobUrlRegistry() |
| ->GetBlobFromUrl(script_source_url), |
| script_source_url, |
| frame_url_loader_factory.InitWithNewPipeAndPassReceiver()); |
| } else { |
| document_service_->render_frame_host().CreateNetworkServiceDefaultFactory( |
| frame_url_loader_factory.InitWithNewPipeAndPassReceiver()); |
| } |
| |
| mojo::PendingRemote<network::mojom::URLLoaderFactory> url_loader_factory; |
| |
| url_loader_factory_proxy_ = |
| std::make_unique<SharedStorageURLLoaderFactoryProxy>( |
| std::move(frame_url_loader_factory), |
| url_loader_factory.InitWithNewPipeAndPassReceiver(), frame_origin, |
| script_source_url, credentials_mode, |
| static_cast<RenderFrameHostImpl&>( |
| document_service_->render_frame_host()) |
| .ComputeSiteForCookies()); |
| |
| shared_storage_worklet_host_manager_->NotifySharedStorageAccessed( |
| AccessType::kDocumentAddModule, document_service_->main_frame_id(), |
| shared_storage_origin_.Serialize(), |
| SharedStorageEventParams::CreateForAddModule(script_source_url)); |
| |
| GetAndConnectToSharedStorageWorkletService()->AddModule( |
| std::move(url_loader_factory), script_source_url, |
| base::BindOnce(&SharedStorageWorkletHost::OnAddModuleOnWorkletFinished, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| SharedStorageWorkletHost::~SharedStorageWorkletHost() { |
| base::UmaHistogramEnumeration("Storage.SharedStorage.Worklet.DestroyedStatus", |
| destroyed_status_); |
| |
| base::TimeDelta elapsed_time_since_creation = |
| base::TimeTicks::Now() - creation_time_; |
| if (pending_operations_count_ > 0 || |
| last_operation_finished_time_.is_null() || |
| elapsed_time_since_creation.is_zero()) { |
| base::UmaHistogramCounts100( |
| "Storage.SharedStorage.Worklet.Timing.UsefulResourceDuration", 100); |
| } else { |
| base::UmaHistogramCounts100( |
| "Storage.SharedStorage.Worklet.Timing.UsefulResourceDuration", |
| 100 * (last_operation_finished_time_ - creation_time_) / |
| elapsed_time_since_creation); |
| } |
| |
| if (!page_) |
| return; |
| |
| // If the worklet is destructed and there are still unresolved URNs (i.e. the |
| // keep-alive timeout is reached), consider the mapping to be failed. |
| auto it = unresolved_urns_.begin(); |
| while (it != unresolved_urns_.end()) { |
| const GURL& urn_uuid = it->first; |
| |
| blink::SharedStorageSelectUrlBudgetStatus budget_status = |
| blink::SharedStorageSelectUrlBudgetStatus::kOther; |
| |
| std::optional<FencedFrameConfig> config = |
| page_->fenced_frame_urls_map() |
| .OnSharedStorageURNMappingResultDetermined( |
| urn_uuid, |
| CreateSharedStorageURNMappingResult( |
| storage_partition_, browser_context_, page_.get(), |
| main_frame_origin_, shared_storage_origin_, |
| shared_storage_site_, std::move(it->second), |
| /*index=*/0, /*budget_remaining=*/0.0, budget_status)); |
| |
| shared_storage_worklet_host_manager_->NotifyConfigPopulated(config); |
| |
| it = unresolved_urns_.erase(it); |
| } |
| } |
| |
| void SharedStorageWorkletHost::SelectURL( |
| const std::string& name, |
| std::vector<blink::mojom::SharedStorageUrlWithMetadataPtr> |
| urls_with_metadata, |
| blink::CloneableMessage serialized_data, |
| bool keep_alive_after_operation, |
| blink::mojom::PrivateAggregationConfigPtr private_aggregation_config, |
| SelectURLCallback callback) { |
| CHECK(private_aggregation_config); |
| // `page_` can be null. See test |
| // MainFrameDocumentAssociatedDataChangesOnSameSiteNavigation in |
| // SitePerProcessBrowserTest. |
| if (!page_) { |
| std::move(callback).Run( |
| /*success=*/false, /*error_message=*/ |
| "Internal error: page does not exist.", |
| /*result_config=*/std::nullopt); |
| return; |
| } |
| |
| // Increment the page load metrics counter for selectURL calls. |
| GetContentClient()->browser()->OnSharedStorageSelectURLCalled( |
| &(page_->GetMainDocument())); |
| |
| // TODO(crbug.com/40946074): `document_service_` can somehow be null. |
| if (!document_service_) { |
| std::move(callback).Run( |
| /*success=*/false, /*error_message=*/ |
| "Internal error: document does not exist.", |
| /*result_config=*/std::nullopt); |
| return; |
| } |
| |
| if (!blink::IsValidSharedStorageURLsArrayLength(urls_with_metadata.size())) { |
| // This could indicate a compromised renderer, so let's terminate it. |
| receiver_.ReportBadMessage( |
| "Attempted to execute RunURLSelectionOperationOnWorklet with invalid " |
| "URLs array length."); |
| LogSharedStorageWorkletError( |
| blink::SharedStorageWorkletErrorType:: |
| kSelectURLNonWebVisibleInvalidURLArrayLength); |
| return; |
| } |
| |
| std::vector<SharedStorageEventParams::SharedStorageUrlSpecWithMetadata> |
| converted_urls; |
| for (const auto& url_with_metadata : urls_with_metadata) { |
| // TODO(crbug.com/40223071): Use `blink::IsValidFencedFrameURL()` here. |
| if (!url_with_metadata->url.is_valid()) { |
| // This could indicate a compromised renderer, since the URLs were already |
| // validated in the renderer. |
| receiver_.ReportBadMessage( |
| base::StrCat({"Invalid fenced frame URL '", |
| url_with_metadata->url.possibly_invalid_spec(), "'"})); |
| LogSharedStorageWorkletError( |
| blink::SharedStorageWorkletErrorType:: |
| kSelectURLNonWebVisibleInvalidFencedFrameURL); |
| return; |
| } |
| |
| std::map<std::string, std::string> reporting_metadata; |
| for (const auto& metadata_pair : url_with_metadata->reporting_metadata) { |
| if (!IsValidFencedFrameReportingURL(metadata_pair.second)) { |
| // This could indicate a compromised renderer, since the reporting URLs |
| // were already validated in the renderer. |
| receiver_.ReportBadMessage( |
| base::StrCat({"Invalid reporting URL '", |
| metadata_pair.second.possibly_invalid_spec(), |
| "' for '", metadata_pair.first, "'"})); |
| LogSharedStorageWorkletError( |
| blink::SharedStorageWorkletErrorType:: |
| kSelectURLNonWebVisibleInvalidReportingURL); |
| return; |
| } |
| reporting_metadata.insert( |
| std::make_pair(metadata_pair.first, metadata_pair.second.spec())); |
| } |
| |
| converted_urls.emplace_back(url_with_metadata->url, |
| std::move(reporting_metadata)); |
| } |
| |
| if (private_aggregation_config->context_id.has_value() && |
| !blink::IsValidPrivateAggregationContextId( |
| private_aggregation_config->context_id.value())) { |
| receiver_.ReportBadMessage("Invalid context_id."); |
| LogSharedStorageWorkletError(blink::SharedStorageWorkletErrorType:: |
| kSelectURLNonWebVisibleInvalidContextId); |
| return; |
| } |
| |
| if (!keep_alive_after_operation_) { |
| receiver_.ReportBadMessage( |
| "Received further operations when previous operation did not include " |
| "the option \'keepAlive: true\'."); |
| LogSharedStorageWorkletError(blink::SharedStorageWorkletErrorType:: |
| kSelectURLNonWebVisibleKeepAliveFalse); |
| return; |
| } |
| |
| // TODO(crbug.com/335818079): If `keep_alive_after_operation_` switches to |
| // false, but the operation doesn't get executed (e.g. fails other checks), we |
| // should destroy this worklet host as well. |
| keep_alive_after_operation_ = keep_alive_after_operation; |
| |
| size_t shared_storage_fenced_frame_root_count = 0u; |
| size_t fenced_frame_depth = |
| static_cast<RenderFrameHostImpl&>(document_service_->render_frame_host()) |
| .frame_tree_node() |
| ->GetFencedFrameDepth(shared_storage_fenced_frame_root_count); |
| |
| DCHECK_LE(shared_storage_fenced_frame_root_count, fenced_frame_depth); |
| |
| size_t max_allowed_fenced_frame_depth = base::checked_cast<size_t>( |
| blink::features::kSharedStorageMaxAllowedFencedFrameDepthForSelectURL |
| .Get()); |
| |
| if (fenced_frame_depth > max_allowed_fenced_frame_depth) { |
| std::move(callback).Run( |
| /*success=*/false, |
| /*error_message=*/ |
| base::StrCat( |
| {"selectURL() is called in a context with a fenced frame depth (", |
| base::NumberToString(fenced_frame_depth), |
| ") exceeding the maximum allowed number (", |
| base::NumberToString(max_allowed_fenced_frame_depth), ")."}), |
| /*result_config=*/std::nullopt); |
| return; |
| } |
| |
| auto pending_urn_uuid = |
| page_->fenced_frame_urls_map().GeneratePendingMappedURN(); |
| |
| if (!pending_urn_uuid.has_value()) { |
| // Pending urn::uuid cannot be inserted to pending urn map because number of |
| // urn mappings has reached limit. |
| std::move(callback).Run( |
| /*success=*/false, /*error_message=*/ |
| "sharedStorage.selectURL() failed because number of urn::uuid to url " |
| "mappings has reached the limit.", |
| /*result_config=*/std::nullopt); |
| return; |
| } |
| |
| GURL urn_uuid = pending_urn_uuid.value(); |
| |
| std::string debug_message; |
| if (!IsSharedStorageSelectURLAllowed(&debug_message)) { |
| if (is_same_origin_worklet_) { |
| std::move(callback).Run( |
| /*success=*/false, |
| /*error_message=*/ |
| GetSharedStorageErrorMessage(debug_message, |
| kSharedStorageSelectURLDisabledMessage), |
| /*result_config=*/std::nullopt); |
| } else { |
| // When the worklet and the worklet creator are not same-origin, the user |
| // preferences for the worklet origin should not be revealed. |
| LogSharedStorageWorkletError( |
| blink::SharedStorageWorkletErrorType:: |
| kSelectURLNonWebVisibleCrossOriginSharedStorageDisabled); |
| FencedFrameConfig config(urn_uuid, GURL()); |
| std::move(callback).Run( |
| /*success=*/true, /*error_message=*/{}, |
| /*result_config=*/ |
| config.RedactFor(FencedFrameEntity::kEmbedder)); |
| } |
| return; |
| } |
| |
| // Further error checks/messages should be avoided, as they might indirectly |
| // reveal the user preferences for the worklet origin. |
| |
| IncrementPendingOperationsCount(); |
| |
| std::vector<GURL> urls; |
| for (const auto& url_with_metadata : urls_with_metadata) |
| urls.emplace_back(url_with_metadata->url); |
| |
| bool emplace_succeeded = |
| unresolved_urns_.emplace(urn_uuid, std::move(urls_with_metadata)).second; |
| |
| // Assert that `urn_uuid` was not in the set before. |
| DCHECK(emplace_succeeded); |
| |
| FencedFrameConfig config(urn_uuid, GURL()); |
| std::move(callback).Run( |
| /*success=*/true, /*error_message=*/{}, |
| /*result_config=*/ |
| config.RedactFor(FencedFrameEntity::kEmbedder)); |
| |
| shared_storage_worklet_host_manager_->NotifyUrnUuidGenerated(urn_uuid); |
| |
| shared_storage_worklet_host_manager_->NotifySharedStorageAccessed( |
| AccessType::kDocumentSelectURL, document_service_->main_frame_id(), |
| shared_storage_origin_.Serialize(), |
| SharedStorageEventParams::CreateForSelectURL(name, serialized_data, |
| std::move(converted_urls))); |
| |
| GetAndConnectToSharedStorageWorkletService()->RunURLSelectionOperation( |
| name, urls, std::move(serialized_data), |
| MaybeBindPrivateAggregationHost(private_aggregation_config), |
| base::BindOnce( |
| &SharedStorageWorkletHost:: |
| OnRunURLSelectionOperationOnWorkletScriptExecutionFinished, |
| weak_ptr_factory_.GetWeakPtr(), urn_uuid, base::TimeTicks::Now())); |
| } |
| |
| void SharedStorageWorkletHost::Run( |
| const std::string& name, |
| blink::CloneableMessage serialized_data, |
| bool keep_alive_after_operation, |
| blink::mojom::PrivateAggregationConfigPtr private_aggregation_config, |
| RunCallback callback) { |
| CHECK(private_aggregation_config); |
| // `page_` can be null. See test |
| // MainFrameDocumentAssociatedDataChangesOnSameSiteNavigation in |
| // SitePerProcessBrowserTest. |
| if (!page_) { |
| std::move(callback).Run( |
| /*success=*/false, /*error_message=*/ |
| "Internal error: page does not exist."); |
| return; |
| } |
| |
| // TODO(crbug.com/40946074): `document_service_` can somehow be null. |
| if (!document_service_) { |
| std::move(callback).Run( |
| /*success=*/false, /*error_message=*/ |
| "Internal error: document does not exist."); |
| return; |
| } |
| |
| if (private_aggregation_config->context_id.has_value() && |
| !blink::IsValidPrivateAggregationContextId( |
| private_aggregation_config->context_id.value())) { |
| receiver_.ReportBadMessage("Invalid context_id."); |
| LogSharedStorageWorkletError(blink::SharedStorageWorkletErrorType:: |
| kRunNonWebVisibleInvalidContextId); |
| return; |
| } |
| |
| if (!keep_alive_after_operation_) { |
| receiver_.ReportBadMessage( |
| "Received further operations when previous operation did not include " |
| "the option \'keepAlive: true\'."); |
| LogSharedStorageWorkletError( |
| blink::SharedStorageWorkletErrorType::kRunNonWebVisibleKeepAliveFalse); |
| return; |
| } |
| |
| // TODO(crbug.com/335818079): If `keep_alive_after_operation_` switches to |
| // false, but the operation doesn't get executed (e.g. fails other checks), we |
| // should destroy this worklet host as well. |
| keep_alive_after_operation_ = keep_alive_after_operation; |
| |
| std::string debug_message; |
| if (!IsSharedStorageAllowed(&debug_message)) { |
| if (is_same_origin_worklet_) { |
| std::move(callback).Run( |
| /*success=*/false, |
| /*error_message=*/GetSharedStorageErrorMessage( |
| debug_message, kSharedStorageDisabledMessage)); |
| } else { |
| // When the worklet and the worklet creator are not same-origin, the user |
| // preferences for the worklet origin should not be revealed. |
| LogSharedStorageWorkletError( |
| blink::SharedStorageWorkletErrorType:: |
| kRunNonWebVisibleCrossOriginSharedStorageDisabled); |
| std::move(callback).Run( |
| /*success=*/true, |
| /*error_message=*/{}); |
| } |
| return; |
| } |
| |
| // Further error checks/messages should be avoided, as they might indirectly |
| // reveal the user preferences for the worklet origin. |
| |
| IncrementPendingOperationsCount(); |
| |
| std::move(callback).Run(/*success=*/true, /*error_message=*/{}); |
| |
| shared_storage_worklet_host_manager_->NotifySharedStorageAccessed( |
| AccessType::kDocumentRun, document_service_->main_frame_id(), |
| shared_storage_origin_.Serialize(), |
| SharedStorageEventParams::CreateForRun(name, serialized_data)); |
| |
| GetAndConnectToSharedStorageWorkletService()->RunOperation( |
| name, std::move(serialized_data), |
| MaybeBindPrivateAggregationHost(private_aggregation_config), |
| base::BindOnce(&SharedStorageWorkletHost::OnRunOperationOnWorkletFinished, |
| weak_ptr_factory_.GetWeakPtr(), base::TimeTicks::Now())); |
| } |
| |
| bool SharedStorageWorkletHost::HasPendingOperations() { |
| return pending_operations_count_ > 0; |
| } |
| |
| void SharedStorageWorkletHost::EnterKeepAliveOnDocumentDestroyed( |
| KeepAliveFinishedCallback callback) { |
| // At this point the `SharedStorageDocumentServiceImpl` is being destroyed, so |
| // `document_service_` is still valid. But it will be auto reset soon after. |
| DCHECK(document_service_); |
| DCHECK(HasPendingOperations()); |
| DCHECK(keep_alive_finished_callback_.is_null()); |
| |
| keep_alive_finished_callback_ = std::move(callback); |
| |
| keep_alive_timer_.Start( |
| FROM_HERE, GetKeepAliveTimeout(), |
| base::BindOnce(&SharedStorageWorkletHost::FinishKeepAlive, |
| weak_ptr_factory_.GetWeakPtr(), /*timeout_reached=*/true)); |
| |
| enter_keep_alive_time_ = base::TimeTicks::Now(); |
| destroyed_status_ = blink::SharedStorageWorkletDestroyedStatus::kOther; |
| } |
| |
| void SharedStorageWorkletHost::SharedStorageSet( |
| const std::u16string& key, |
| const std::u16string& value, |
| bool ignore_if_present, |
| SharedStorageSetCallback callback) { |
| std::string debug_message; |
| if (!IsSharedStorageAllowed(&debug_message)) { |
| std::move(callback).Run( |
| /*success=*/false, |
| /*error_message=*/GetSharedStorageErrorMessage( |
| debug_message, kSharedStorageDisabledMessage)); |
| return; |
| } |
| |
| if (document_service_) { |
| shared_storage_worklet_host_manager_->NotifySharedStorageAccessed( |
| AccessType::kWorkletSet, document_service_->main_frame_id(), |
| shared_storage_origin_.Serialize(), |
| SharedStorageEventParams::CreateForSet(base::UTF16ToUTF8(key), |
| base::UTF16ToUTF8(value), |
| ignore_if_present)); |
| } |
| |
| auto operation_completed_callback = base::BindOnce( |
| [](SharedStorageSetCallback callback, OperationResult result) { |
| if (result != OperationResult::kSet && |
| result != OperationResult::kIgnored) { |
| std::move(callback).Run( |
| /*success=*/false, |
| /*error_message=*/"sharedStorage.set() failed"); |
| return; |
| } |
| |
| std::move(callback).Run( |
| /*success=*/true, |
| /*error_message=*/{}); |
| }, |
| std::move(callback)); |
| |
| storage::SharedStorageDatabase::SetBehavior set_behavior = |
| ignore_if_present |
| ? storage::SharedStorageDatabase::SetBehavior::kIgnoreIfPresent |
| : storage::SharedStorageDatabase::SetBehavior::kDefault; |
| |
| shared_storage_manager_->Set(shared_storage_origin_, key, value, |
| std::move(operation_completed_callback), |
| set_behavior); |
| } |
| |
| void SharedStorageWorkletHost::SharedStorageAppend( |
| const std::u16string& key, |
| const std::u16string& value, |
| SharedStorageAppendCallback callback) { |
| std::string debug_message; |
| if (!IsSharedStorageAllowed(&debug_message)) { |
| std::move(callback).Run( |
| /*success=*/false, |
| /*error_message=*/GetSharedStorageErrorMessage( |
| debug_message, kSharedStorageDisabledMessage)); |
| return; |
| } |
| |
| if (document_service_) { |
| shared_storage_worklet_host_manager_->NotifySharedStorageAccessed( |
| AccessType::kWorkletAppend, document_service_->main_frame_id(), |
| shared_storage_origin_.Serialize(), |
| SharedStorageEventParams::CreateForAppend(base::UTF16ToUTF8(key), |
| base::UTF16ToUTF8(value))); |
| } |
| |
| auto operation_completed_callback = base::BindOnce( |
| [](SharedStorageAppendCallback callback, OperationResult result) { |
| if (result != OperationResult::kSet) { |
| std::move(callback).Run( |
| /*success=*/false, |
| /*error_message=*/"sharedStorage.append() failed"); |
| return; |
| } |
| |
| std::move(callback).Run( |
| /*success=*/true, |
| /*error_message=*/{}); |
| }, |
| std::move(callback)); |
| |
| shared_storage_manager_->Append(shared_storage_origin_, key, value, |
| std::move(operation_completed_callback)); |
| } |
| |
| void SharedStorageWorkletHost::SharedStorageDelete( |
| const std::u16string& key, |
| SharedStorageDeleteCallback callback) { |
| std::string debug_message; |
| if (!IsSharedStorageAllowed(&debug_message)) { |
| std::move(callback).Run( |
| /*success=*/false, |
| /*error_message=*/GetSharedStorageErrorMessage( |
| debug_message, kSharedStorageDisabledMessage)); |
| return; |
| } |
| |
| if (document_service_) { |
| shared_storage_worklet_host_manager_->NotifySharedStorageAccessed( |
| AccessType::kWorkletDelete, document_service_->main_frame_id(), |
| shared_storage_origin_.Serialize(), |
| SharedStorageEventParams::CreateForGetOrDelete(base::UTF16ToUTF8(key))); |
| } |
| |
| auto operation_completed_callback = base::BindOnce( |
| [](SharedStorageDeleteCallback callback, OperationResult result) { |
| if (result != OperationResult::kSuccess) { |
| std::move(callback).Run( |
| /*success=*/false, |
| /*error_message=*/"sharedStorage.delete() failed"); |
| return; |
| } |
| |
| std::move(callback).Run( |
| /*success=*/true, |
| /*error_message=*/{}); |
| }, |
| std::move(callback)); |
| |
| shared_storage_manager_->Delete(shared_storage_origin_, key, |
| std::move(operation_completed_callback)); |
| } |
| |
| void SharedStorageWorkletHost::SharedStorageClear( |
| SharedStorageClearCallback callback) { |
| std::string debug_message; |
| if (!IsSharedStorageAllowed(&debug_message)) { |
| std::move(callback).Run( |
| /*success=*/false, |
| /*error_message=*/GetSharedStorageErrorMessage( |
| debug_message, kSharedStorageDisabledMessage)); |
| return; |
| } |
| |
| if (document_service_) { |
| shared_storage_worklet_host_manager_->NotifySharedStorageAccessed( |
| AccessType::kWorkletClear, document_service_->main_frame_id(), |
| shared_storage_origin_.Serialize(), |
| SharedStorageEventParams::CreateDefault()); |
| } |
| |
| auto operation_completed_callback = base::BindOnce( |
| [](SharedStorageClearCallback callback, OperationResult result) { |
| if (result != OperationResult::kSuccess) { |
| std::move(callback).Run( |
| /*success=*/false, |
| /*error_message=*/"sharedStorage.clear() failed"); |
| return; |
| } |
| |
| std::move(callback).Run( |
| /*success=*/true, |
| /*error_message=*/{}); |
| }, |
| std::move(callback)); |
| |
| shared_storage_manager_->Clear(shared_storage_origin_, |
| std::move(operation_completed_callback)); |
| } |
| |
| void SharedStorageWorkletHost::SharedStorageGet( |
| const std::u16string& key, |
| SharedStorageGetCallback callback) { |
| std::string debug_message; |
| if (!IsSharedStorageAllowed(&debug_message)) { |
| std::move(callback).Run(blink::mojom::SharedStorageGetStatus::kError, |
| /*error_message=*/ |
| GetSharedStorageErrorMessage( |
| debug_message, kSharedStorageDisabledMessage), |
| /*value=*/{}); |
| return; |
| } |
| |
| if (document_service_) { |
| shared_storage_worklet_host_manager_->NotifySharedStorageAccessed( |
| AccessType::kWorkletGet, document_service_->main_frame_id(), |
| shared_storage_origin_.Serialize(), |
| SharedStorageEventParams::CreateForGetOrDelete(base::UTF16ToUTF8(key))); |
| } |
| |
| auto operation_completed_callback = base::BindOnce( |
| [](SharedStorageGetCallback callback, GetResult result) { |
| // If the key is not found but there is no other error, the worklet will |
| // resolve the promise to undefined. |
| if (result.result == OperationResult::kNotFound || |
| result.result == OperationResult::kExpired) { |
| std::move(callback).Run( |
| blink::mojom::SharedStorageGetStatus::kNotFound, |
| /*error_message=*/"sharedStorage.get() could not find key", |
| /*value=*/{}); |
| return; |
| } |
| |
| if (result.result != OperationResult::kSuccess) { |
| std::move(callback).Run( |
| blink::mojom::SharedStorageGetStatus::kError, |
| /*error_message=*/"sharedStorage.get() failed", /*value=*/{}); |
| return; |
| } |
| |
| std::move(callback).Run(blink::mojom::SharedStorageGetStatus::kSuccess, |
| /*error_message=*/{}, /*value=*/result.data); |
| }, |
| std::move(callback)); |
| |
| shared_storage_manager_->Get(shared_storage_origin_, key, |
| std::move(operation_completed_callback)); |
| } |
| |
| void SharedStorageWorkletHost::SharedStorageKeys( |
| mojo::PendingRemote<blink::mojom::SharedStorageEntriesListener> |
| pending_listener) { |
| std::string debug_message; |
| if (!IsSharedStorageAllowed(&debug_message)) { |
| mojo::Remote<blink::mojom::SharedStorageEntriesListener> listener( |
| std::move(pending_listener)); |
| listener->DidReadEntries( |
| /*success=*/false, |
| GetSharedStorageErrorMessage(debug_message, |
| kSharedStorageDisabledMessage), |
| /*entries=*/{}, /*has_more_entries=*/false, /*total_queued_to_send=*/0); |
| return; |
| } |
| |
| if (document_service_) { |
| shared_storage_worklet_host_manager_->NotifySharedStorageAccessed( |
| AccessType::kWorkletKeys, document_service_->main_frame_id(), |
| shared_storage_origin_.Serialize(), |
| SharedStorageEventParams::CreateDefault()); |
| } |
| |
| shared_storage_manager_->Keys(shared_storage_origin_, |
| std::move(pending_listener), base::DoNothing()); |
| } |
| |
| void SharedStorageWorkletHost::SharedStorageEntries( |
| mojo::PendingRemote<blink::mojom::SharedStorageEntriesListener> |
| pending_listener) { |
| std::string debug_message; |
| if (!IsSharedStorageAllowed(&debug_message)) { |
| mojo::Remote<blink::mojom::SharedStorageEntriesListener> listener( |
| std::move(pending_listener)); |
| listener->DidReadEntries( |
| /*success=*/false, |
| GetSharedStorageErrorMessage(debug_message, |
| kSharedStorageDisabledMessage), |
| /*entries=*/{}, /*has_more_entries=*/false, /*total_queued_to_send=*/0); |
| return; |
| } |
| |
| if (document_service_) { |
| shared_storage_worklet_host_manager_->NotifySharedStorageAccessed( |
| AccessType::kWorkletEntries, document_service_->main_frame_id(), |
| shared_storage_origin_.Serialize(), |
| SharedStorageEventParams::CreateDefault()); |
| } |
| |
| shared_storage_manager_->Entries( |
| shared_storage_origin_, std::move(pending_listener), base::DoNothing()); |
| } |
| |
| void SharedStorageWorkletHost::SharedStorageLength( |
| SharedStorageLengthCallback callback) { |
| std::string debug_message; |
| if (!IsSharedStorageAllowed(&debug_message)) { |
| std::move(callback).Run( |
| /*success=*/false, |
| /*error_message=*/ |
| GetSharedStorageErrorMessage(debug_message, |
| kSharedStorageDisabledMessage), |
| /*length=*/0); |
| return; |
| } |
| |
| if (document_service_) { |
| shared_storage_worklet_host_manager_->NotifySharedStorageAccessed( |
| AccessType::kWorkletLength, document_service_->main_frame_id(), |
| shared_storage_origin_.Serialize(), |
| SharedStorageEventParams::CreateDefault()); |
| } |
| |
| auto operation_completed_callback = base::BindOnce( |
| [](SharedStorageLengthCallback callback, int result) { |
| if (result == -1) { |
| std::move(callback).Run( |
| /*success=*/false, |
| /*error_message=*/"sharedStorage.length() failed", /*length=*/0); |
| return; |
| } |
| |
| DCHECK_GE(result, 0); |
| |
| std::move(callback).Run( |
| /*success=*/true, |
| /*error_message=*/{}, |
| /*length=*/result); |
| }, |
| std::move(callback)); |
| |
| shared_storage_manager_->Length(shared_storage_origin_, |
| std::move(operation_completed_callback)); |
| } |
| |
| void SharedStorageWorkletHost::SharedStorageRemainingBudget( |
| SharedStorageRemainingBudgetCallback callback) { |
| std::string debug_message; |
| if (!IsSharedStorageAllowed(&debug_message)) { |
| std::move(callback).Run( |
| /*success=*/false, |
| /*error_message=*/ |
| GetSharedStorageErrorMessage(debug_message, |
| kSharedStorageDisabledMessage), |
| /*bits=*/0.0); |
| return; |
| } |
| |
| if (document_service_) { |
| shared_storage_worklet_host_manager_->NotifySharedStorageAccessed( |
| AccessType::kWorkletRemainingBudget, document_service_->main_frame_id(), |
| shared_storage_origin_.Serialize(), |
| SharedStorageEventParams::CreateDefault()); |
| } |
| |
| auto operation_completed_callback = base::BindOnce( |
| [](SharedStorageRemainingBudgetCallback callback, BudgetResult result) { |
| if (result.result != OperationResult::kSuccess) { |
| std::move(callback).Run( |
| /*success=*/false, |
| /*error_message=*/"sharedStorage.remainingBudget() failed", |
| /*bits=*/0.0); |
| return; |
| } |
| |
| std::move(callback).Run( |
| /*success=*/true, |
| /*error_message=*/{}, |
| /*bits=*/result.bits); |
| }, |
| std::move(callback)); |
| |
| shared_storage_manager_->GetRemainingBudget( |
| shared_storage_site_, std::move(operation_completed_callback)); |
| } |
| |
| void SharedStorageWorkletHost::DidAddMessageToConsole( |
| blink::mojom::ConsoleMessageLevel level, |
| const std::string& message) { |
| if (!document_service_) { |
| DCHECK(IsInKeepAlivePhase()); |
| return; |
| } |
| |
| // Mimic what's being done for console outputs from Window context, which |
| // manually triggers the observer method. |
| static_cast<RenderFrameHostImpl&>(document_service_->render_frame_host()) |
| .DidAddMessageToConsole(level, base::UTF8ToUTF16(message), |
| /*line_no=*/0, /*source_id=*/{}, |
| /*untrusted_stack_trace=*/{}); |
| } |
| |
| void SharedStorageWorkletHost::RecordUseCounters( |
| const std::vector<blink::mojom::WebFeature>& features) { |
| // If the worklet host has outlived the page, we unfortunately can't count the |
| // feature. |
| if (!page_) |
| return; |
| |
| for (blink::mojom::WebFeature feature : features) { |
| GetContentClient()->browser()->LogWebFeatureForCurrentPage( |
| &page_->GetMainDocument(), feature); |
| } |
| } |
| |
| RenderProcessHost* SharedStorageWorkletHost::GetProcessHost() const { |
| return driver_->GetProcessHost(); |
| } |
| |
| RenderFrameHostImpl* SharedStorageWorkletHost::GetFrame() { |
| if (document_service_) { |
| return static_cast<RenderFrameHostImpl*>( |
| &document_service_->render_frame_host()); |
| } |
| |
| return nullptr; |
| } |
| |
| void SharedStorageWorkletHost::OnAddModuleOnWorkletFinished( |
| blink::mojom::SharedStorageDocumentService::CreateWorkletCallback callback, |
| bool success, |
| const std::string& error_message) { |
| std::move(callback).Run(success, error_message); |
| |
| DecrementPendingOperationsCount(); |
| } |
| |
| void SharedStorageWorkletHost::OnRunOperationOnWorkletFinished( |
| base::TimeTicks start_time, |
| bool success, |
| const std::string& error_message) { |
| if (!success) { |
| LogSharedStorageWorkletErrorFromErrorMessage(/*from_select_url=*/false, |
| error_message); |
| if (document_service_) { |
| DCHECK(!IsInKeepAlivePhase()); |
| devtools_instrumentation::LogWorkletMessage( |
| static_cast<RenderFrameHostImpl&>( |
| document_service_->render_frame_host()), |
| blink::mojom::ConsoleMessageLevel::kError, error_message); |
| } |
| } else { |
| LogSharedStorageWorkletError( |
| blink::SharedStorageWorkletErrorType::kSuccess); |
| } |
| |
| base::UmaHistogramLongTimes( |
| "Storage.SharedStorage.Document.Timing.Run.ExecutedInWorklet", |
| base::TimeTicks::Now() - start_time); |
| DecrementPendingOperationsCount(); |
| } |
| |
| void SharedStorageWorkletHost:: |
| OnRunURLSelectionOperationOnWorkletScriptExecutionFinished( |
| const GURL& urn_uuid, |
| base::TimeTicks start_time, |
| bool success, |
| const std::string& error_message, |
| uint32_t index) { |
| auto it = unresolved_urns_.find(urn_uuid); |
| DCHECK(it != unresolved_urns_.end()); |
| |
| if ((success && index >= it->second.size()) || (!success && index != 0)) { |
| // This could indicate a compromised worklet environment, so let's terminate |
| // it. |
| shared_storage_worklet_service_client_.ReportBadMessage( |
| "Unexpected index number returned from selectURL()."); |
| LogSharedStorageWorkletError( |
| blink::SharedStorageWorkletErrorType:: |
| kSelectURLNonWebVisibleUnexpectedIndexReturned); |
| |
| unresolved_urns_.erase(it); |
| DecrementPendingOperationsCount(); |
| return; |
| } |
| |
| shared_storage_manager_->GetRemainingBudget( |
| shared_storage_site_, |
| base::BindOnce(&SharedStorageWorkletHost:: |
| OnRunURLSelectionOperationOnWorkletFinished, |
| weak_ptr_factory_.GetWeakPtr(), urn_uuid, start_time, |
| success, error_message, index)); |
| } |
| |
| void SharedStorageWorkletHost::OnRunURLSelectionOperationOnWorkletFinished( |
| const GURL& urn_uuid, |
| base::TimeTicks start_time, |
| bool script_execution_succeeded, |
| const std::string& script_execution_error_message, |
| uint32_t index, |
| BudgetResult budget_result) { |
| auto it = unresolved_urns_.find(urn_uuid); |
| DCHECK(it != unresolved_urns_.end()); |
| |
| std::vector<blink::mojom::SharedStorageUrlWithMetadataPtr> |
| urls_with_metadata = std::move(it->second); |
| unresolved_urns_.erase(it); |
| |
| if (page_) { |
| blink::SharedStorageSelectUrlBudgetStatus budget_status = |
| blink::SharedStorageSelectUrlBudgetStatus::kOther; |
| |
| SharedStorageURNMappingResult mapping_result = |
| CreateSharedStorageURNMappingResult( |
| storage_partition_, browser_context_, page_.get(), |
| main_frame_origin_, shared_storage_origin_, shared_storage_site_, |
| std::move(urls_with_metadata), index, budget_result.bits, |
| budget_status); |
| |
| // Log histograms. These do not need the `document_service_`. |
| blink::LogSharedStorageSelectURLBudgetStatus(budget_status); |
| if (budget_status != |
| blink::SharedStorageSelectUrlBudgetStatus::kSufficientBudget) { |
| LogSharedStorageWorkletError( |
| blink::SharedStorageWorkletErrorType:: |
| kSelectURLNonWebVisibleInsufficientBudget); |
| } else if (!script_execution_succeeded) { |
| LogSharedStorageWorkletErrorFromErrorMessage( |
| /*from_select_url=*/true, script_execution_error_message); |
| } else { |
| LogSharedStorageWorkletError( |
| blink::SharedStorageWorkletErrorType::kSuccess); |
| } |
| |
| if (document_service_) { |
| DCHECK(!IsInKeepAlivePhase()); |
| |
| // Let the insufficient-budget failure supersede the script failure. |
| if (budget_status != |
| blink::SharedStorageSelectUrlBudgetStatus::kSufficientBudget) { |
| devtools_instrumentation::LogWorkletMessage( |
| static_cast<RenderFrameHostImpl&>( |
| document_service_->render_frame_host()), |
| blink::mojom::ConsoleMessageLevel::kError, |
| "Insufficient budget for selectURL()."); |
| } else if (!script_execution_succeeded) { |
| devtools_instrumentation::LogWorkletMessage( |
| static_cast<RenderFrameHostImpl&>( |
| document_service_->render_frame_host()), |
| blink::mojom::ConsoleMessageLevel::kError, |
| script_execution_error_message); |
| } |
| } |
| |
| std::optional<FencedFrameConfig> config = |
| page_->fenced_frame_urls_map() |
| .OnSharedStorageURNMappingResultDetermined( |
| urn_uuid, std::move(mapping_result)); |
| |
| shared_storage_worklet_host_manager_->NotifyConfigPopulated(config); |
| } |
| |
| base::UmaHistogramLongTimes( |
| "Storage.SharedStorage.Document.Timing.SelectURL.ExecutedInWorklet", |
| base::TimeTicks::Now() - start_time); |
| DecrementPendingOperationsCount(); |
| } |
| |
| void SharedStorageWorkletHost::ExpireWorklet() { |
| // `this` is not in keep-alive. |
| DCHECK(document_service_); |
| DCHECK(shared_storage_worklet_host_manager_); |
| |
| // This will remove this worklet host from the manager. |
| shared_storage_worklet_host_manager_->ExpireWorkletHostForDocumentService( |
| document_service_.get(), this); |
| |
| // Do not add code after this. SharedStorageWorkletHost has been destroyed. |
| } |
| |
| bool SharedStorageWorkletHost::IsInKeepAlivePhase() const { |
| return !!keep_alive_finished_callback_; |
| } |
| |
| void SharedStorageWorkletHost::FinishKeepAlive(bool timeout_reached) { |
| if (timeout_reached) { |
| destroyed_status_ = |
| blink::SharedStorageWorkletDestroyedStatus::kKeepAliveEndedDueToTimeout; |
| } else { |
| destroyed_status_ = blink::SharedStorageWorkletDestroyedStatus:: |
| kKeepAliveEndedDueToOperationsFinished; |
| DCHECK(!enter_keep_alive_time_.is_null()); |
| base::UmaHistogramTimes( |
| "Storage.SharedStorage.Worklet.Timing." |
| "KeepAliveEndedDueToOperationsFinished.KeepAliveDuration", |
| base::TimeTicks::Now() - enter_keep_alive_time_); |
| } |
| |
| // This will remove this worklet host from the manager. |
| std::move(keep_alive_finished_callback_).Run(this); |
| |
| // Do not add code after this. SharedStorageWorkletHost has been destroyed. |
| } |
| |
| void SharedStorageWorkletHost::IncrementPendingOperationsCount() { |
| base::CheckedNumeric<uint32_t> count = pending_operations_count_; |
| pending_operations_count_ = (++count).ValueOrDie(); |
| } |
| |
| void SharedStorageWorkletHost::DecrementPendingOperationsCount() { |
| base::CheckedNumeric<uint32_t> count = pending_operations_count_; |
| pending_operations_count_ = (--count).ValueOrDie(); |
| |
| if (pending_operations_count_) |
| return; |
| |
| // This time will be overridden if another operation is subsequently queued |
| // and completed. |
| last_operation_finished_time_ = base::TimeTicks::Now(); |
| |
| if (!IsInKeepAlivePhase() && keep_alive_after_operation_) { |
| return; |
| } |
| |
| if (IsInKeepAlivePhase()) { |
| FinishKeepAlive(/*timeout_reached=*/false); |
| return; |
| } |
| |
| ExpireWorklet(); |
| |
| // Do not add code after here. The worklet will be closed. |
| } |
| |
| base::TimeDelta SharedStorageWorkletHost::GetKeepAliveTimeout() const { |
| return kKeepAliveTimeout; |
| } |
| |
| blink::mojom::SharedStorageWorkletService* |
| SharedStorageWorkletHost::GetAndConnectToSharedStorageWorkletService() { |
| DCHECK(document_service_); |
| |
| if (!shared_storage_worklet_service_) { |
| bool private_aggregation_permissions_policy_allowed = |
| document_service_->render_frame_host().IsFeatureEnabled( |
| blink::mojom::PermissionsPolicyFeature::kPrivateAggregation); |
| |
| auto global_scope_creation_params = |
| blink::mojom::WorkletGlobalScopeCreationParams::New( |
| script_source_url_, shared_storage_origin_, origin_trial_features_, |
| devtools_handle_->devtools_token(), |
| devtools_handle_->BindNewPipeAndPassRemote(), |
| devtools_handle_->wait_for_debugger()); |
| |
| driver_->StartWorkletService( |
| shared_storage_worklet_service_.BindNewPipeAndPassReceiver(), |
| std::move(global_scope_creation_params)); |
| |
| auto embedder_context = static_cast<RenderFrameHostImpl&>( |
| document_service_->render_frame_host()) |
| .frame_tree_node() |
| ->GetEmbedderSharedStorageContextIfAllowed(); |
| shared_storage_worklet_service_->Initialize( |
| shared_storage_worklet_service_client_.BindNewEndpointAndPassRemote(), |
| private_aggregation_permissions_policy_allowed, embedder_context); |
| } |
| |
| return shared_storage_worklet_service_.get(); |
| } |
| |
| mojo::PendingRemote<blink::mojom::PrivateAggregationHost> |
| SharedStorageWorkletHost::MaybeBindPrivateAggregationHost( |
| const blink::mojom::PrivateAggregationConfigPtr& |
| private_aggregation_config) { |
| CHECK(browser_context_, base::NotFatalUntil::M128); |
| CHECK(private_aggregation_config); |
| |
| if (!blink::ShouldDefinePrivateAggregationInSharedStorage()) { |
| return mojo::PendingRemote<blink::mojom::PrivateAggregationHost>(); |
| } |
| |
| PrivateAggregationManager* private_aggregation_manager = |
| PrivateAggregationManager::GetManager(*browser_context_); |
| CHECK(private_aggregation_manager, base::NotFatalUntil::M128); |
| |
| mojo::PendingRemote<blink::mojom::PrivateAggregationHost> |
| pending_pa_host_remote; |
| |
| std::optional<base::TimeDelta> timeout = |
| (base::FeatureList::IsEnabled(blink::features::kSharedStorageAPIM118) && |
| private_aggregation_config->context_id) |
| ? std::optional<base::TimeDelta>(base::Seconds(5)) |
| : std::nullopt; |
| |
| bool success = private_aggregation_manager->BindNewReceiver( |
| shared_storage_origin_, main_frame_origin_, |
| PrivateAggregationBudgetKey::Api::kSharedStorage, |
| private_aggregation_config->context_id, std::move(timeout), |
| private_aggregation_config->aggregation_coordinator_origin, |
| pending_pa_host_remote.InitWithNewPipeAndPassReceiver()); |
| CHECK(success); |
| |
| return pending_pa_host_remote; |
| } |
| |
| bool SharedStorageWorkletHost::IsSharedStorageAllowed( |
| std::string* out_debug_message) { |
| RenderFrameHost* rfh = |
| document_service_ ? &(document_service_->render_frame_host()) : nullptr; |
| return GetContentClient()->browser()->IsSharedStorageAllowed( |
| browser_context_, rfh, main_frame_origin_, shared_storage_origin_, |
| out_debug_message); |
| } |
| |
| bool SharedStorageWorkletHost::IsSharedStorageSelectURLAllowed( |
| std::string* out_debug_message) { |
| CHECK(document_service_); |
| |
| // Will trigger a call to |
| // `content_settings::PageSpecificContentSettings::BrowsingDataAccessed()` for |
| // reporting purposes. |
| if (!IsSharedStorageAllowed(out_debug_message)) { |
| return false; |
| } |
| |
| return GetContentClient()->browser()->IsSharedStorageSelectURLAllowed( |
| browser_context_, main_frame_origin_, shared_storage_origin_, |
| out_debug_message); |
| } |
| |
| } // namespace content |