blob: 5eb3a733382d694233a3374c9eae65ef6e84786e [file] [log] [blame]
// Copyright 2014 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/worker_host/shared_worker_host.h"
#include <algorithm>
#include <utility>
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/unguessable_token.h"
#include "content/browser/broadcast_channel/broadcast_channel_provider.h"
#include "content/browser/broadcast_channel/broadcast_channel_service.h"
#include "content/browser/code_cache/generated_code_cache_context.h"
#include "content/browser/devtools/devtools_instrumentation.h"
#include "content/browser/devtools/shared_worker_devtools_manager.h"
#include "content/browser/loader/url_loader_factory_utils.h"
#include "content/browser/network/cross_origin_embedder_policy_reporter.h"
#include "content/browser/renderer_host/code_cache_host_impl.h"
#include "content/browser/renderer_host/private_network_access_util.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/security/dip/document_isolation_policy_reporter.h"
#include "content/browser/service_worker/service_worker_client.h"
#include "content/browser/service_worker/service_worker_context_core.h"
#include "content/browser/service_worker/service_worker_main_resource_handle.h"
#include "content/browser/storage_partition_impl.h"
#include "content/browser/url_loader_factory_params_helper.h"
#include "content/browser/websockets/websocket_connector_impl.h"
#include "content/browser/webtransport/web_transport_connector_impl.h"
#include "content/browser/worker_host/shared_worker_content_settings_proxy_impl.h"
#include "content/browser/worker_host/shared_worker_service_impl.h"
#include "content/browser/worker_host/worker_script_fetcher.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/permission_controller.h"
#include "content/public/browser/permission_descriptor_util.h"
#include "content/public/browser/service_worker_context.h"
#include "content/public/browser/worker_type.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_features.h"
#include "net/base/isolation_info.h"
#include "net/cookies/site_for_cookies.h"
#include "services/metrics/public/cpp/delegating_ukm_recorder.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/metrics/public/cpp/ukm_source_id.h"
#include "services/network/public/cpp/cross_origin_embedder_policy.h"
#include "services/network/public/cpp/document_isolation_policy.h"
#include "services/network/public/cpp/is_potentially_trustworthy.h"
#include "services/network/public/mojom/client_security_state.mojom-shared.h"
#include "services/network/public/mojom/ip_address_space.mojom-shared.h"
#include "storage/browser/blob/blob_url_store_impl.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/loader/url_loader_factory_bundle.h"
#include "third_party/blink/public/common/messaging/message_port_channel.h"
#include "third_party/blink/public/common/privacy_budget/identifiability_study_settings.h"
#include "third_party/blink/public/common/privacy_budget/identifiability_study_worker_client_added.h"
#include "third_party/blink/public/common/renderer_preferences/renderer_preferences.h"
#include "third_party/blink/public/mojom/devtools/console_message.mojom.h"
#include "third_party/blink/public/mojom/renderer_preference_watcher.mojom.h"
#include "third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom.h"
#include "third_party/blink/public/mojom/worker/shared_worker_exception_details.mojom.h"
#include "third_party/blink/public/mojom/worker/shared_worker_info.mojom.h"
#include "third_party/blink/public/mojom/worker/worker_content_settings_proxy.mojom.h"
namespace {
// TODO(crbug.com/400473072): revisit the duration.
// Also, we may want to use the same constant we use for service workers.
// Current value come from `ServiceWorkerVersion::kRequestTimeout`.
constexpr base::TimeDelta kSharedWorkerDestructionDelay = base::Minutes(5);
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
// LINT.IfChange(SharedWorkerHostDestructionSource)
enum class SharedWorkerHostDestructionSource {
kUnknown = 0,
kOnContextClosed = 1,
kRenderProcessHostDestroyed = 2,
kNoClients = 3,
kWorkerConnectionLost = 4,
kMaxValue = kWorkerConnectionLost,
};
// LINT.ThenChange(//tools/metrics/histograms/metadata/content/enums.xml:SharedWorkerHostDestructionSource)
void RecordDestructionSource(SharedWorkerHostDestructionSource source) {
base::UmaHistogramEnumeration("Content.SharedWorker.Host.DestructionSource",
source);
}
} // namespace
namespace content {
// RAII helper class for talking to SharedWorkerDevToolsManager.
class SharedWorkerHost::ScopedDevToolsHandle {
public:
explicit ScopedDevToolsHandle(SharedWorkerHost* owner) : owner_(owner) {
SharedWorkerDevToolsManager::GetInstance()->WorkerCreated(
owner, &pause_on_start_, &dev_tools_token_);
}
ScopedDevToolsHandle(const ScopedDevToolsHandle&) = delete;
ScopedDevToolsHandle& operator=(const ScopedDevToolsHandle&) = delete;
~ScopedDevToolsHandle() {
SharedWorkerDevToolsManager::GetInstance()->WorkerDestroyed(owner_);
}
void WorkerReadyForInspection(
mojo::PendingRemote<blink::mojom::DevToolsAgent> agent_remote,
mojo::PendingReceiver<blink::mojom::DevToolsAgentHost>
agent_host_receiver) {
SharedWorkerDevToolsManager::GetInstance()->WorkerReadyForInspection(
owner_, std::move(agent_remote), std::move(agent_host_receiver));
}
bool pause_on_start() const { return pause_on_start_; }
const base::UnguessableToken& dev_tools_token() const {
return dev_tools_token_;
}
private:
raw_ptr<SharedWorkerHost> owner_;
// Indicates if the worker should be paused when it is started. This is set
// when a dev tools agent host already exists for that shared worker, which
// happens when a shared worker is restarted while it is being debugged.
bool pause_on_start_;
base::UnguessableToken dev_tools_token_;
};
class SharedWorkerHost::ScopedProcessHostRef {
public:
explicit ScopedProcessHostRef(RenderProcessHost* render_process_host)
: render_process_host_(render_process_host) {
if (!render_process_host_->AreRefCountsDisabled()) {
render_process_host_->IncrementWorkerRefCount();
}
}
~ScopedProcessHostRef() {
if (!render_process_host_->AreRefCountsDisabled()) {
render_process_host_->DecrementWorkerRefCount();
}
}
ScopedProcessHostRef(const ScopedProcessHostRef& other) = delete;
private:
const raw_ptr<RenderProcessHost> render_process_host_;
};
SharedWorkerHost::SharedWorkerHost(
SharedWorkerServiceImpl* service,
const SharedWorkerInstance& instance,
scoped_refptr<SiteInstanceImpl> site_instance,
std::vector<network::mojom::ContentSecurityPolicyPtr>
content_security_policies,
scoped_refptr<PolicyContainerHost> creator_policy_container_host)
: service_(service),
token_(blink::SharedWorkerToken()),
instance_(instance),
content_security_policies_(std::move(content_security_policies)),
site_instance_(std::move(site_instance)),
scoped_process_host_ref_(
std::make_unique<ScopedProcessHostRef>(site_instance_->GetProcess())),
next_connection_request_id_(1),
devtools_handle_(std::make_unique<ScopedDevToolsHandle>(this)),
code_cache_host_receivers_(GetProcessHost()
->GetStoragePartition()
->GetGeneratedCodeCacheContext()),
ukm_source_id_(ukm::ConvertToSourceId(ukm::AssignNewSourceId(),
ukm::SourceIdType::WORKER_ID)),
reporting_source_(base::UnguessableToken::Create()),
creator_policy_container_host_(std::move(creator_policy_container_host)) {
DCHECK(GetProcessHost());
DCHECK(GetProcessHost()->IsInitializedAndNotDead());
GetProcessHost()->AddObserver(this);
// Set up the worker pending receiver. This is needed first in either
// AddClient() or Start(). AddClient() can sometimes be called before Start()
// when two clients call new SharedWorker() at around the same time.
worker_receiver_ = worker_.BindNewPipeAndPassReceiver();
service_->NotifyWorkerCreated(token_, GetProcessHost()->GetDeprecatedID(),
instance_.storage_key().origin(),
devtools_handle_->dev_tools_token());
}
SharedWorkerHost::~SharedWorkerHost() {
if (started_) {
// Attempt to notify the worker before disconnecting.
if (worker_) {
worker_->Terminate();
}
} else {
// Tell clients that this worker failed to start.
for (const ClientInfo& info : clients_) {
info.client->OnScriptLoadFailed(/*error_message=*/"");
}
}
if (site_instance_->HasProcess()) {
// Send any final reports and allow the reporting configuration to be
// removed.
// Note that the RenderProcessHost and the associated StoragePartition
// outlives `this`.
GetProcessHost()
->GetStoragePartition()
->GetNetworkContext()
->SendReportsAndRemoveSource(reporting_source_);
GetProcessHost()->RemoveObserver(this);
}
// Notify the service that each client still connected will be removed and
// that the worker will terminate.
for (const auto& client : clients_) {
service_->NotifyClientRemoved(token_, client.render_frame_host_id);
}
service_->NotifyBeforeWorkerDestroyed(token_);
}
RenderProcessHost* SharedWorkerHost::GetProcessHost() const {
DCHECK(site_instance_->HasProcess());
return site_instance_->GetProcess();
}
void SharedWorkerHost::Start(
mojo::PendingRemote<blink::mojom::SharedWorkerFactory> factory,
blink::mojom::FetchClientSettingsObjectPtr
outside_fetch_client_settings_object,
ContentBrowserClient* client,
WorkerScriptFetcherResult result) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(!started_);
DCHECK(result.main_script_load_params);
DCHECK(result.subresource_loader_factories);
DCHECK(!result.subresource_loader_factories->pending_default_factory());
started_ = true;
final_response_url_ = result.final_response_url;
// `network::mojom::ClientSecurityState` contains a worker-relevant subset of
// the policy container. https://crbug.com/1177199 tracks using
// `PolicyContainerPolicies` for workers instead.
//
// https://html.spec.whatwg.org/C/#initialize-worker-policy-container
worker_client_security_state_ = network::mojom::ClientSecurityState::New();
scoped_refptr<PolicyContainerHost> policy_container_host;
if (result.final_response_url.SchemeIsLocal()) {
// TODO(crbug.com/40053797): Inherit from the file creator instead
// once creator policies are persisted through the filesystem store.
if (creator_policy_container_host_) {
worker_client_security_state_ =
DeriveClientSecurityState(creator_policy_container_host_->policies(),
PrivateNetworkRequestContext::kWorker);
} else {
auto policy = base::FeatureList::IsEnabled(
features::kPrivateNetworkAccessForWorkers)
? network::mojom::PrivateNetworkRequestPolicy::kBlock
: network::mojom::PrivateNetworkRequestPolicy::kAllow;
// Create a maximally restricted client security state if the policy
// container is missing.
worker_client_security_state_ = network::mojom::ClientSecurityState::New(
network::CrossOriginEmbedderPolicy(
network::mojom::CrossOriginEmbedderPolicyValue::kRequireCorp),
/*is_web_secure_context=*/false,
network::mojom::IPAddressSpace::kUnknown, policy,
network::DocumentIsolationPolicy(
network::mojom::DocumentIsolationPolicyValue::
kIsolateAndRequireCorp));
}
policy_container_host = std::move(creator_policy_container_host_);
} else {
// https://html.spec.whatwg.org/C/#creating-a-policy-container-from-a-fetch-response
// This does not parse the referrer policy, which will be
// updated in `SharedWorkerGlobalScope::Initialize()`.
PolicyContainerPolicies policies(
result.final_response_url,
result.main_script_load_params->response_head.get(), nullptr);
// A worker context can only be secure if its creator also is.
if (!creator_policy_container_host_->policies().is_web_secure_context) {
policies.is_web_secure_context = false;
}
worker_client_security_state_ = DeriveClientSecurityState(
policies, PrivateNetworkRequestContext::kWorker);
policy_container_host =
base::MakeRefCounted<PolicyContainerHost>(std::move(policies));
if (result.main_script_load_params->response_head->parsed_headers) {
worker_client_security_state_->cross_origin_embedder_policy =
result.main_script_load_params->response_head->parsed_headers
->cross_origin_embedder_policy;
}
switch (worker_client_security_state_->cross_origin_embedder_policy.value) {
case network::mojom::CrossOriginEmbedderPolicyValue::kNone:
OnFeatureUsed(blink::mojom::WebFeature::kCoepNoneSharedWorker);
break;
case network::mojom::CrossOriginEmbedderPolicyValue::kCredentialless:
OnFeatureUsed(
blink::mojom::WebFeature::kCoepCredentiallessSharedWorker);
break;
case network::mojom::CrossOriginEmbedderPolicyValue::kRequireCorp:
OnFeatureUsed(blink::mojom::WebFeature::kCoepRequireCorpSharedWorker);
break;
}
auto* storage_partition = static_cast<StoragePartitionImpl*>(
GetProcessHost()->GetStoragePartition());
// Create a COEP reporter with worker's policy.
coep_reporter_ = std::make_unique<CrossOriginEmbedderPolicyReporter>(
storage_partition->GetWeakPtr(), result.final_response_url,
worker_client_security_state_->cross_origin_embedder_policy
.reporting_endpoint,
worker_client_security_state_->cross_origin_embedder_policy
.report_only_reporting_endpoint,
GetReportingSource(), GetNetworkAnonymizationKey());
// Create a DIP reporter with worker's policy.
dip_reporter_ = std::make_unique<DocumentIsolationPolicyReporter>(
storage_partition->GetWeakPtr(), result.final_response_url,
worker_client_security_state_->document_isolation_policy
.reporting_endpoint,
worker_client_security_state_->document_isolation_policy
.report_only_reporting_endpoint,
GetReportingSource(), GetNetworkAnonymizationKey());
}
auto options = blink::mojom::WorkerOptions::New(
instance_.script_type(), instance_.credentials_mode(), instance_.name());
blink::mojom::SharedWorkerInfoPtr info(blink::mojom::SharedWorkerInfo::New(
instance_.url(), std::move(options),
mojo::Clone(content_security_policies_),
std::move(outside_fetch_client_settings_object),
instance_.same_site_cookies(), instance_.extended_lifetime()));
auto renderer_preferences = blink::RendererPreferences();
GetContentClient()->browser()->UpdateRendererPreferencesForWorker(
GetProcessHost()->GetBrowserContext(), &renderer_preferences);
// Create a RendererPreferenceWatcher to observe updates in the preferences.
mojo::PendingRemote<blink::mojom::RendererPreferenceWatcher> watcher_remote;
mojo::PendingReceiver<blink::mojom::RendererPreferenceWatcher>
preference_watcher_receiver =
watcher_remote.InitWithNewPipeAndPassReceiver();
GetContentClient()->browser()->RegisterRendererPreferenceWatcher(
GetProcessHost()->GetBrowserContext(), std::move(watcher_remote));
// Set up content settings interface.
mojo::PendingRemote<blink::mojom::WorkerContentSettingsProxy>
content_settings;
content_settings_ = std::make_unique<SharedWorkerContentSettingsProxyImpl>(
instance_.url(), this, content_settings.InitWithNewPipeAndPassReceiver());
// Set up BrowserInterfaceBroker interface
mojo::PendingRemote<blink::mojom::BrowserInterfaceBroker>
browser_interface_broker;
broker_receiver_.Bind(
browser_interface_broker.InitWithNewPipeAndPassReceiver());
// Set the default factory to the bundle for subresource loading to pass to
// the renderer.
bool bypass_redirect_checks = false;
result.subresource_loader_factories->pending_default_factory() =
CreateNetworkFactoryForSubresources(&bypass_redirect_checks);
result.subresource_loader_factories->set_bypass_redirect_checks(
bypass_redirect_checks);
blink::mojom::ServiceWorkerContainerInfoForClientPtr container_info;
blink::mojom::ControllerServiceWorkerInfoPtr controller;
if (service_worker_handle_->service_worker_client()) {
mojo::PendingRemote<network::mojom::CrossOriginEmbedderPolicyReporter>
coep_reporter;
if (coep_reporter_) {
coep_reporter_->Clone(coep_reporter.InitWithNewPipeAndPassReceiver());
}
mojo::PendingRemote<network::mojom::DocumentIsolationPolicyReporter>
dip_reporter;
if (dip_reporter_) {
dip_reporter_->Clone(dip_reporter.InitWithNewPipeAndPassReceiver());
}
std::tie(container_info, controller) =
service_worker_handle_->scoped_service_worker_client()
->CommitResponseAndRelease(
/*rfh_id=*/std::nullopt,
std::move(result.policy_container_policies),
std::move(coep_reporter), std::move(dip_reporter),
ukm_source_id());
}
mojo::PendingReceiver<blink::mojom::ReportingObserver>
coep_reporting_observer;
if (coep_reporter_) {
mojo::PendingRemote<blink::mojom::ReportingObserver> coep_reporting_remote;
coep_reporting_observer =
coep_reporting_remote.InitWithNewPipeAndPassReceiver();
coep_reporter_->BindObserver(std::move(coep_reporting_remote));
}
mojo::PendingReceiver<blink::mojom::ReportingObserver> dip_reporting_observer;
if (dip_reporter_) {
mojo::PendingRemote<blink::mojom::ReportingObserver> dip_reporting_remote;
dip_reporting_observer =
dip_reporting_remote.InitWithNewPipeAndPassReceiver();
dip_reporter_->BindObserver(std::move(dip_reporting_remote));
}
// Send the CreateSharedWorker message.
factory_.Bind(std::move(factory));
factory_->CreateSharedWorker(
std::move(info), token_, instance_.storage_key(),
instance_.renderer_origin(),
creator_policy_container_host_ &&
creator_policy_container_host_->policies().is_web_secure_context,
GetContentClient()->browser()->GetUserAgentBasedOnPolicy(
GetProcessHost()->GetBrowserContext()),
GetContentClient()->browser()->GetUserAgentMetadata(),
devtools_handle_->pause_on_start(), devtools_handle_->dev_tools_token(),
std::move(renderer_preferences), std::move(preference_watcher_receiver),
std::move(content_settings), std::move(container_info),
std::move(result.main_script_load_params),
std::move(result.subresource_loader_factories), std::move(controller),
policy_container_host->CreatePolicyContainerForBlink(),
receiver_.BindNewPipeAndPassRemote(), std::move(worker_receiver_),
std::move(browser_interface_broker), ukm_source_id_,
instance_.DoesRequireCrossSiteRequestForCookies(),
std::move(coep_reporting_observer), std::move(dip_reporting_observer));
if (service_worker_handle_->service_worker_client()) {
service_worker_handle_->service_worker_client()->SetContainerReady();
}
// Monitor the lifetime of the worker.
worker_.set_disconnect_handler(base::BindOnce(
&SharedWorkerHost::OnWorkerConnectionLost, weak_factory_.GetWeakPtr()));
}
// This is similar to
// RenderFrameHostImpl::CreateNetworkServiceDefaultFactoryAndObserve, but this
// host doesn't observe network service crashes. Instead, the renderer detects
// the connection error and terminates the worker.
mojo::PendingRemote<network::mojom::URLLoaderFactory>
SharedWorkerHost::CreateNetworkFactoryForSubresources(
bool* bypass_redirect_checks) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(bypass_redirect_checks);
network::mojom::URLLoaderFactoryParamsPtr factory_params =
CreateNetworkFactoryParamsForSubresources();
url::Origin origin = url::Origin::Create(instance_.url());
return url_loader_factory::CreatePendingRemote(
ContentBrowserClient::URLLoaderFactoryType::kWorkerSubResource,
url_loader_factory::TerminalParams::ForNetworkContext(
GetProcessHost()->GetStoragePartition()->GetNetworkContext(),
std::move(factory_params),
url_loader_factory::HeaderClientOption::kAllow,
url_loader_factory::FactoryOverrideOption::kAllow),
url_loader_factory::ContentClientParams(
GetProcessHost()->GetBrowserContext(),
/*frame=*/nullptr, GetProcessHost()->GetDeprecatedID(), origin,
GetStorageKey().ToPartialNetIsolationInfo(),
ukm::SourceIdObj::FromInt64(ukm_source_id_), bypass_redirect_checks),
devtools_instrumentation::WillCreateURLLoaderFactoryParams::
ForSharedWorker(this));
}
network::mojom::URLLoaderFactoryParamsPtr
SharedWorkerHost::CreateNetworkFactoryParamsForSubresources() {
url::Origin origin = GetStorageKey().origin();
mojo::PendingRemote<network::mojom::CrossOriginEmbedderPolicyReporter>
coep_reporter;
if (coep_reporter_) {
coep_reporter_->Clone(coep_reporter.InitWithNewPipeAndPassReceiver());
}
mojo::PendingRemote<network::mojom::DocumentIsolationPolicyReporter>
dip_reporter;
if (dip_reporter_) {
dip_reporter_->Clone(dip_reporter.InitWithNewPipeAndPassReceiver());
}
network::mojom::URLLoaderFactoryParamsPtr factory_params =
URLLoaderFactoryParamsHelper::CreateForWorker(
GetProcessHost(), origin, GetStorageKey().ToPartialNetIsolationInfo(),
std::move(coep_reporter), std::move(dip_reporter),
/*url_loader_network_observer=*/mojo::NullRemote(),
/*devtools_observer=*/mojo::NullRemote(),
mojo::Clone(worker_client_security_state_),
/*debug_tag=*/
"SharedWorkerHost::CreateNetworkFactoryForSubresource",
instance_.DoesRequireCrossSiteRequestForCookies(),
/*is_for_service_worker=*/false);
return factory_params;
}
blink::StorageKey SharedWorkerHost::GetBucketStorageKey() {
return GetStorageKey();
}
blink::mojom::PermissionStatus SharedWorkerHost::GetPermissionStatus(
blink::PermissionType permission_type) {
return GetProcessHost()
->GetBrowserContext()
->GetPermissionController()
->GetPermissionStatusForWorker(
content::PermissionDescriptorUtil::
CreatePermissionDescriptorForPermissionType(permission_type),
GetProcessHost(), GetStorageKey().origin());
}
void SharedWorkerHost::BindCacheStorageForBucket(
const storage::BucketInfo& bucket,
mojo::PendingReceiver<blink::mojom::CacheStorage> receiver) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
BindCacheStorageInternal(std::move(receiver), bucket.ToBucketLocator());
}
void SharedWorkerHost::BindCacheStorageInternal(
mojo::PendingReceiver<blink::mojom::CacheStorage> receiver,
const storage::BucketLocator& bucket_locator) {
mojo::PendingRemote<network::mojom::CrossOriginEmbedderPolicyReporter>
coep_reporter;
if (coep_reporter_) {
coep_reporter_->Clone(coep_reporter.InitWithNewPipeAndPassReceiver());
}
mojo::PendingRemote<network::mojom::DocumentIsolationPolicyReporter>
dip_reporter;
if (dip_reporter_) {
dip_reporter_->Clone(dip_reporter.InitWithNewPipeAndPassReceiver());
}
GetProcessHost()->BindCacheStorage(
cross_origin_embedder_policy(), std::move(coep_reporter),
worker_client_security_state_->document_isolation_policy,
std::move(dip_reporter), bucket_locator, std::move(receiver));
}
void SharedWorkerHost::GetSandboxedFileSystemForBucket(
const storage::BucketInfo& bucket,
const std::vector<std::string>& directory_path_components,
blink::mojom::BucketHost::GetDirectoryCallback callback) {
GetProcessHost()->GetSandboxedFileSystemForBucket(
bucket.ToBucketLocator(), directory_path_components, std::move(callback));
}
storage::BucketClientInfo SharedWorkerHost::GetBucketClientInfo() const {
return storage::BucketClientInfo{GetProcessHost()->GetDeprecatedID(),
token()};
}
void SharedWorkerHost::AllowFileSystem(
const GURL& url,
base::OnceCallback<void(bool)> callback) {
GetContentClient()->browser()->AllowWorkerFileSystem(
url, GetProcessHost()->GetBrowserContext(), GetRenderFrameIDsForWorker(),
GetStorageKey(), std::move(callback));
}
void SharedWorkerHost::AllowIndexedDB(const GURL& url,
base::OnceCallback<void(bool)> callback) {
std::move(callback).Run(GetContentClient()->browser()->AllowWorkerIndexedDB(
url, GetProcessHost()->GetBrowserContext(), GetRenderFrameIDsForWorker(),
GetStorageKey()));
}
void SharedWorkerHost::AllowCacheStorage(
const GURL& url,
base::OnceCallback<void(bool)> callback) {
std::move(callback).Run(
GetContentClient()->browser()->AllowWorkerCacheStorage(
url, GetProcessHost()->GetBrowserContext(),
GetRenderFrameIDsForWorker(), GetStorageKey()));
}
void SharedWorkerHost::AllowWebLocks(const GURL& url,
base::OnceCallback<void(bool)> callback) {
std::move(callback).Run(GetContentClient()->browser()->AllowWorkerWebLocks(
url, GetProcessHost()->GetBrowserContext(), GetRenderFrameIDsForWorker(),
GetStorageKey()));
}
void SharedWorkerHost::CreateWebTransportConnector(
mojo::PendingReceiver<blink::mojom::WebTransportConnector> receiver) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
const url::Origin origin = url::Origin::Create(instance().url());
mojo::MakeSelfOwnedReceiver(
std::make_unique<WebTransportConnectorImpl>(
GetProcessHost()->GetDeprecatedID(), /*frame=*/nullptr, origin,
GetNetworkAnonymizationKey()),
std::move(receiver));
}
void SharedWorkerHost::CreateWebSocketConnector(
mojo::PendingReceiver<blink::mojom::WebSocketConnector> receiver) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
const blink::StorageKey& storage_key = instance_.storage_key();
mojo::MakeSelfOwnedReceiver(
std::make_unique<WebSocketConnectorImpl>(
GetProcessHost()->GetDeprecatedID(), IPC::mojom::kRoutingIdNone,
storage_key.origin(), storage_key.ToPartialNetIsolationInfo()),
std::move(receiver));
}
void SharedWorkerHost::BindCacheStorage(
mojo::PendingReceiver<blink::mojom::CacheStorage> receiver) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
BindCacheStorageInternal(
std::move(receiver),
storage::BucketLocator::ForDefaultBucket(GetStorageKey()));
}
void SharedWorkerHost::CreateBroadcastChannelProvider(
mojo::PendingReceiver<blink::mojom::BroadcastChannelProvider> receiver) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto* storage_partition_impl = static_cast<StoragePartitionImpl*>(
GetProcessHost()->GetStoragePartition());
auto* broadcast_channel_service =
storage_partition_impl->GetBroadcastChannelService();
broadcast_channel_service->AddReceiver(
std::make_unique<BroadcastChannelProvider>(broadcast_channel_service,
GetStorageKey()),
std::move(receiver));
}
void SharedWorkerHost::CreateBlobUrlStoreProvider(
mojo::PendingReceiver<blink::mojom::BlobURLStore> receiver) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto* storage_partition_impl = static_cast<StoragePartitionImpl*>(
GetProcessHost()->GetStoragePartition());
storage_partition_impl->GetBlobUrlRegistry()->AddReceiver(
GetStorageKey(), instance().renderer_origin(),
GetProcessHost()->GetDeprecatedID(), std::move(receiver),
/*context_type_for_debugging=*/"Shared Worker",
base::BindRepeating(
[](base::WeakPtr<SharedWorkerHost> host) -> std::string {
if (!host) {
return "destroyed SharedWorkerHost";
}
return host->GetStorageKey().GetDebugString();
},
weak_factory_.GetWeakPtr()),
// Storage access can only be granted to dedicated workers.
base::BindRepeating([]() -> bool { return false; }),
!(GetContentClient()->browser()->IsBlobUrlPartitioningEnabled(
GetProcessHost()->GetBrowserContext())),
storage::BlobURLValidityCheckBehavior::
ALLOW_OPAQUE_ORIGIN_STORAGE_KEY_MISMATCH);
}
void SharedWorkerHost::CreateBucketManagerHost(
mojo::PendingReceiver<blink::mojom::BucketManagerHost> receiver) {
GetProcessHost()->BindBucketManagerHost(AsWeakPtr(), std::move(receiver));
}
#if BUILDFLAG(ENABLE_COMPUTE_PRESSURE)
void SharedWorkerHost::BindPressureService(
mojo::PendingReceiver<blink::mojom::WebPressureManager> receiver) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!network::IsOriginPotentiallyTrustworthy(GetStorageKey().origin())) {
return;
}
// https://www.w3.org/TR/compute-pressure/#policy-control
if (std::ranges::any_of(GetRenderFrameIDsForWorker(), [](const auto& id) {
auto* render_frame_host = RenderFrameHostImpl::FromID(id);
return render_frame_host &&
!render_frame_host->IsFeatureEnabled(
network::mojom::PermissionsPolicyFeature::kComputePressure);
})) {
for (const auto& id : GetRenderFrameIDsForWorker()) {
auto* rfh = RenderFrameHostImpl::FromID(id);
if (!rfh) {
continue;
}
if (rfh->IsFeatureEnabled(
network::mojom::PermissionsPolicyFeature::kComputePressure)) {
rfh->AddMessageToConsole(
blink::mojom::ConsoleMessageLevel::kWarning,
"This frame is connected to a Shared Worker that has requested "
"access to the Compute Pressure API. This worker can't access the "
"API because another frame connected that is not allowed to access "
"this feature due to Permissions Policy.");
} else {
rfh->AddMessageToConsole(
blink::mojom::ConsoleMessageLevel::kWarning,
"This frame is connected to a Shared Worker that has requested "
"access to the Compute Pressure API. This worker can't access the "
"API because this frame is not allowed to access this feature due "
"to Permissions Policy.");
}
}
return;
}
if (!pressure_service_) {
pressure_service_ = std::make_unique<PressureServiceForSharedWorker>(this);
}
pressure_service_->BindReceiver(std::move(receiver));
}
#endif // BUILDFLAG(ENABLE_COMPUTE_PRESSURE)
void SharedWorkerHost::CreateCodeCacheHost(
mojo::PendingReceiver<blink::mojom::CodeCacheHost> receiver) {
// Create a new CodeCacheHostImpl and bind it to the given receiver.
code_cache_host_receivers_.Add(GetProcessHost()->GetDeprecatedID(),
GetNetworkIsolationKey(), GetStorageKey(),
std::move(receiver));
}
void SharedWorkerHost::Destruct() {
// Ask the service to destroy |this| which will terminate the worker.
service_->DestroyHost(this);
}
SharedWorkerHost::ClientInfo::ClientInfo(
mojo::Remote<blink::mojom::SharedWorkerClient> client,
int connection_request_id,
GlobalRenderFrameHostId render_frame_host_id)
: client(std::move(client)),
connection_request_id(connection_request_id),
render_frame_host_id(render_frame_host_id) {}
SharedWorkerHost::ClientInfo::~ClientInfo() {}
void SharedWorkerHost::OnConnected(int connection_request_id) {
for (const ClientInfo& info : clients_) {
if (info.connection_request_id != connection_request_id) {
continue;
}
info.client->OnConnected(std::vector<blink::mojom::WebFeature>(
used_features_.begin(), used_features_.end()));
return;
}
}
void SharedWorkerHost::OnContextClosed() {
// Not possible: there is no Mojo connection on which OnContextClosed can
// be called.
DCHECK(started_);
RecordDestructionSource(SharedWorkerHostDestructionSource::kOnContextClosed);
Destruct();
}
void SharedWorkerHost::OnReadyForInspection(
mojo::PendingRemote<blink::mojom::DevToolsAgent> agent_remote,
mojo::PendingReceiver<blink::mojom::DevToolsAgentHost>
agent_host_receiver) {
devtools_handle_->WorkerReadyForInspection(std::move(agent_remote),
std::move(agent_host_receiver));
}
void SharedWorkerHost::OnScriptLoadFailed(const std::string& error_message) {
for (const ClientInfo& info : clients_) {
info.client->OnScriptLoadFailed(error_message);
}
}
void SharedWorkerHost::OnReportException(
blink::mojom::SharedWorkerExceptionDetailsPtr details) {
for (const ClientInfo& info : clients_) {
info.client->OnReportException(details.Clone());
}
}
// [spec]:
// https://html.spec.whatwg.org/C/#check-a-global-object's-embedder-policy
bool SharedWorkerHost::CheckCrossOriginEmbedderPolicy(
network::CrossOriginEmbedderPolicy creator_cross_origin_embedder_policy,
network::CrossOriginEmbedderPolicy worker_cross_origin_embedder_policy) {
// [spec]: 4. If ownerPolicy's report-only value is "require-corp" or
// "credentialless" and policy's value is "unsafe-none", then queue a
// cross-origin embedder policy inheritance violation with response, "worker
// initialization", owner's policy's report only reporting endpoint,
// "reporting", and owner.
// TODO(crbug.com/40122193): Add reporters.
// [spec]: 5. If ownerPolicy's value is "unsafe-none" or policy's value is
// "require-corp" or "credentialless", then return true.
if (!network::CompatibleWithCrossOriginIsolated(
creator_cross_origin_embedder_policy) ||
network::CompatibleWithCrossOriginIsolated(
worker_cross_origin_embedder_policy)) {
return true;
}
// [spec]: 7. Return false.
return false;
}
void SharedWorkerHost::OnFeatureUsed(blink::mojom::WebFeature feature) {
// Avoid reporting a feature more than once, and enable any new clients to
// observe features that were historically used.
if (!used_features_.insert(feature).second) {
return;
}
for (const ClientInfo& info : clients_) {
info.client->OnFeatureUsed(feature);
}
}
void SharedWorkerHost::RenderProcessHostDestroyed(RenderProcessHost* host) {
// Remove `this` as a RenderProcessHost observer. The destructor for `this`
// also calls RemoveObserver, but the process may be cleared by the time that
// call is reached, so call it here first.
host->RemoveObserver(this);
RecordDestructionSource(
SharedWorkerHostDestructionSource::kRenderProcessHostDestroyed);
Destruct();
}
std::vector<GlobalRenderFrameHostId>
SharedWorkerHost::GetRenderFrameIDsForWorker() {
std::vector<GlobalRenderFrameHostId> result;
result.reserve(clients_.size());
for (const ClientInfo& info : clients_) {
result.push_back(info.render_frame_host_id);
}
return result;
}
base::WeakPtr<SharedWorkerHost> SharedWorkerHost::AsWeakPtr() {
return weak_factory_.GetWeakPtr();
}
net::NetworkIsolationKey SharedWorkerHost::GetNetworkIsolationKey() const {
// Note: Since shared workers are partitioned by the storage key, we'll use
// the storage key to create a NIK that matches the current partitioning
// scheme. In other words, if storage partitioning is disabled, frames with
// different top-level sites will be able to share the same shared worker, so
// it doesn't make sense to incorporate the top-level site into the NIK in
// that case either.
return GetStorageKey().ToPartialNetIsolationInfo().network_isolation_key();
}
net::NetworkAnonymizationKey SharedWorkerHost::GetNetworkAnonymizationKey()
const {
return GetStorageKey()
.ToPartialNetIsolationInfo()
.network_anonymization_key();
}
const blink::StorageKey& SharedWorkerHost::GetStorageKey() const {
return instance().storage_key();
}
void SharedWorkerHost::ReportNoBinderForInterface(const std::string& error) {
broker_receiver_.ReportBadMessage(error + " for the shared worker scope");
}
void SharedWorkerHost::AddClient(
mojo::PendingRemote<blink::mojom::SharedWorkerClient> client,
GlobalRenderFrameHostId client_render_frame_host_id,
const blink::MessagePortChannel& port,
ukm::SourceId client_ukm_source_id) {
mojo::Remote<blink::mojom::SharedWorkerClient> remote_client(
std::move(client));
// Pass the actual creation context type, so the client can understand if
// there is a mismatch between security levels.
remote_client->OnCreated(instance_.creation_context_type());
#if BUILDFLAG(ENABLE_COMPUTE_PRESSURE)
// Stop delivering Compute Pressure data if the added client is not allowed
// to use the policy-controlled feature.
// see https://www.w3.org/TR/compute-pressure/#policy-control
auto* render_frame_host =
RenderFrameHostImpl::FromID(client_render_frame_host_id);
if (pressure_service_ && render_frame_host &&
!render_frame_host->IsFeatureEnabled(
network::mojom::PermissionsPolicyFeature::kComputePressure)) {
render_frame_host->AddMessageToConsole(
blink::mojom::ConsoleMessageLevel::kWarning,
"This frame is now connected to a Shared Worker using the Compute "
"Pressure API. This worker will no longer receive Compute Pressure "
"events because this frame is not allowed to access this feature due "
"to Permissions Policy.");
for (const auto& id : GetRenderFrameIDsForWorker()) {
auto* rfh = RenderFrameHostImpl::FromID(id);
if (!rfh) {
continue;
}
rfh->AddMessageToConsole(
blink::mojom::ConsoleMessageLevel::kWarning,
"This frame is connected to a Shared Worker using the Compute "
"Pressure API. This worker will no longer receive Compute Pressure "
"events because another frame connected that is not allowed to "
"access this feature due to Permissions Policy.");
}
pressure_service_.reset();
}
#endif // BUILDFLAG(ENABLE_COMPUTE_PRESSURE)
clients_.emplace_back(std::move(remote_client), next_connection_request_id_++,
client_render_frame_host_id);
ClientInfo& info = clients_.back();
// Observe when the client goes away.
info.client.set_disconnect_handler(base::BindOnce(
&SharedWorkerHost::OnClientConnectionLost, weak_factory_.GetWeakPtr()));
ukm::DelegatingUkmRecorder* ukm_recorder = ukm::DelegatingUkmRecorder::Get();
if (ukm_recorder) {
ukm::builders::Worker_ClientAdded(ukm_source_id_)
.SetClientSourceId(client_ukm_source_id)
.SetWorkerType(static_cast<int64_t>(WorkerType::kSharedWorker))
.Record(ukm_recorder);
if (blink::IdentifiabilityStudySettings::Get()->IsActive()) {
blink::IdentifiabilityStudyWorkerClientAdded(ukm_source_id_)
.SetClientSourceId(client_ukm_source_id)
.SetWorkerType(blink::IdentifiableSurface::WorkerType::kSharedWorker)
.Record(ukm_recorder);
}
}
worker_->Connect(info.connection_request_id, port.ReleaseHandle());
// Notify that a new client was added now.
service_->NotifyClientAdded(token_, client_render_frame_host_id);
}
void SharedWorkerHost::SetServiceWorkerHandle(
std::unique_ptr<ServiceWorkerMainResourceHandle> service_worker_handle) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
service_worker_handle_ = std::move(service_worker_handle);
}
void SharedWorkerHost::PruneNonExistentClients() {
DCHECK(!started_);
auto it = clients_.begin();
auto end = clients_.end();
while (it != end) {
if (!RenderFrameHostImpl::FromID(it->render_frame_host_id)) {
service_->NotifyClientRemoved(token_, it->render_frame_host_id);
it = clients_.erase(it);
} else {
++it;
}
}
}
bool SharedWorkerHost::HasClients() const {
return !clients_.empty();
}
const base::UnguessableToken& SharedWorkerHost::GetDevToolsToken() const {
return devtools_handle_->dev_tools_token();
}
mojo::Remote<blink::mojom::SharedWorker>
SharedWorkerHost::TerminateRemoteWorkerForTesting() {
mojo::Remote<blink::mojom::SharedWorker> worker = std::move(worker_);
// Tell the remote worker to terminate.
if (worker && worker.is_connected()) {
worker.reset_on_disconnect();
worker->Terminate();
}
return worker;
}
void SharedWorkerHost::OnClientConnectionLost() {
// We'll get a notification for each dropped connection.
for (auto it = clients_.begin(); it != clients_.end(); ++it) {
if (!it->client.is_connected()) {
// Notify the service that the client is gone.
service_->NotifyClientRemoved(token_, it->render_frame_host_id);
clients_.erase(it);
break;
}
}
if (instance_.extended_lifetime()) {
if (!clients_.empty()) { // Early return.
return;
}
base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&SharedWorkerHost::DestructIfNoClients,
weak_factory_.GetWeakPtr()),
kSharedWorkerDestructionDelay);
return;
}
DestructIfNoClients();
}
void SharedWorkerHost::DestructIfNoClients() {
// If there are no clients left, then it's cleanup time.
if (clients_.empty()) {
RecordDestructionSource(SharedWorkerHostDestructionSource::kNoClients);
Destruct();
}
}
void SharedWorkerHost::OnWorkerConnectionLost() {
// This will destroy |this| resulting in client's observing their mojo
// connection being dropped.
RecordDestructionSource(
SharedWorkerHostDestructionSource::kWorkerConnectionLost);
Destruct();
}
} // namespace content