| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // 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 <utility> |
| |
| #include "base/bind.h" |
| #include "base/feature_list.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/no_destructor.h" |
| #include "base/task/post_task.h" |
| #include "base/unguessable_token.h" |
| #include "content/browser/appcache/appcache_navigation_handle.h" |
| #include "content/browser/devtools/shared_worker_devtools_manager.h" |
| #include "content/browser/frame_host/render_frame_host_impl.h" |
| #include "content/browser/interface_provider_filtering.h" |
| #include "content/browser/service_worker/service_worker_main_resource_handle.h" |
| #include "content/browser/service_worker/service_worker_object_host.h" |
| #include "content/browser/storage_partition_impl.h" |
| #include "content/browser/url_loader_factory_params_helper.h" |
| #include "content/browser/webtransport/quic_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/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/service_worker_context.h" |
| #include "content/public/common/content_client.h" |
| #include "mojo/public/cpp/bindings/pending_associated_remote.h" |
| #include "net/base/isolation_info.h" |
| #include "net/cookies/site_for_cookies.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/mojom/appcache/appcache.mojom.h" |
| #include "third_party/blink/public/mojom/renderer_preference_watcher.mojom.h" |
| #include "third_party/blink/public/mojom/web_feature/web_feature.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 content { |
| |
| // RAII helper class for talking to SharedWorkerDevToolsManager. |
| class SharedWorkerHost::ScopedDevToolsHandle { |
| public: |
| ScopedDevToolsHandle(SharedWorkerHost* owner, |
| bool* out_pause_on_start, |
| base::UnguessableToken* out_devtools_worker_token) |
| : owner_(owner) { |
| SharedWorkerDevToolsManager::GetInstance()->WorkerCreated( |
| owner, out_pause_on_start, out_devtools_worker_token); |
| } |
| |
| ~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)); |
| } |
| |
| private: |
| SharedWorkerHost* owner_; |
| DISALLOW_COPY_AND_ASSIGN(ScopedDevToolsHandle); |
| }; |
| |
| class SharedWorkerHost::ScopedProcessHostRef { |
| public: |
| explicit ScopedProcessHostRef(RenderProcessHost* render_process_host) |
| : render_process_host_(render_process_host) { |
| render_process_host_->IncrementKeepAliveRefCount(); |
| } |
| |
| ~ScopedProcessHostRef() { |
| if (!render_process_host_->IsKeepAliveRefCountDisabled()) |
| render_process_host_->DecrementKeepAliveRefCount(); |
| } |
| |
| ScopedProcessHostRef(const ScopedProcessHostRef& other) = delete; |
| |
| private: |
| RenderProcessHost* const render_process_host_; |
| }; |
| |
| SharedWorkerHost::SharedWorkerHost(SharedWorkerServiceImpl* service, |
| SharedWorkerId id, |
| const SharedWorkerInstance& instance, |
| RenderProcessHost* worker_process_host) |
| : service_(service), |
| id_(id), |
| instance_(instance), |
| worker_process_host_(worker_process_host), |
| scoped_process_host_ref_( |
| std::make_unique<ScopedProcessHostRef>(worker_process_host)), |
| scoped_process_host_observer_(this), |
| next_connection_request_id_(1) { |
| DCHECK(worker_process_host_); |
| DCHECK(worker_process_host_->IsInitializedAndNotDead()); |
| |
| // 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(); |
| |
| scoped_process_host_observer_.Add(worker_process_host_); |
| } |
| |
| SharedWorkerHost::~SharedWorkerHost() { |
| if (started_) { |
| // Attempt to notify the worker before disconnecting. |
| if (worker_) |
| worker_->Terminate(); |
| |
| // Notify the service that each client still connected will be removed and |
| // that the worker will terminate. |
| for (const auto& client : clients_) { |
| service_->NotifyClientRemoved(id_, client.render_frame_host_id); |
| } |
| service_->NotifyWorkerTerminating(id_); |
| } else { |
| // Tell clients that this worker failed to start. |
| for (const ClientInfo& info : clients_) |
| info.client->OnScriptLoadFailed(/*error_message=*/""); |
| } |
| } |
| |
| void SharedWorkerHost::Start( |
| mojo::PendingRemote<blink::mojom::SharedWorkerFactory> factory, |
| blink::mojom::WorkerMainScriptLoadParamsPtr main_script_load_params, |
| std::unique_ptr<blink::PendingURLLoaderFactoryBundle> |
| subresource_loader_factories, |
| blink::mojom::ControllerServiceWorkerInfoPtr controller, |
| base::WeakPtr<ServiceWorkerObjectHost> |
| controller_service_worker_object_host, |
| blink::mojom::FetchClientSettingsObjectPtr |
| outside_fetch_client_settings_object) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| DCHECK(!started_); |
| DCHECK(main_script_load_params); |
| DCHECK(subresource_loader_factories); |
| DCHECK(!subresource_loader_factories->pending_default_factory()); |
| |
| started_ = true; |
| |
| 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), instance_.content_security_policy(), |
| instance_.content_security_policy_type(), |
| instance_.creation_address_space(), |
| std::move(outside_fetch_client_settings_object))); |
| |
| // Register with DevTools. |
| bool pause_on_start; |
| devtools_handle_ = std::make_unique<ScopedDevToolsHandle>( |
| this, &pause_on_start, &dev_tools_token_); |
| |
| auto renderer_preferences = blink::mojom::RendererPreferences::New(); |
| GetContentClient()->browser()->UpdateRendererPreferencesForWorker( |
| worker_process_host_->GetBrowserContext(), renderer_preferences.get()); |
| |
| // 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( |
| worker_process_host_->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; |
| subresource_loader_factories->pending_default_factory() = |
| CreateNetworkFactoryForSubresources(&bypass_redirect_checks); |
| subresource_loader_factories->set_bypass_redirect_checks( |
| bypass_redirect_checks); |
| |
| // Prepare the controller service worker info to pass to the renderer. |
| // |object_info| can be nullptr when the service worker context or the service |
| // worker version is gone during shared worker startup. |
| mojo::PendingAssociatedRemote<blink::mojom::ServiceWorkerObject> |
| service_worker_remote_object; |
| blink::mojom::ServiceWorkerState service_worker_sent_state; |
| if (controller && controller->object_info) { |
| controller->object_info->receiver = |
| service_worker_remote_object.InitWithNewEndpointAndPassReceiver(); |
| service_worker_sent_state = controller->object_info->state; |
| } |
| |
| // Send the CreateSharedWorker message. |
| factory_.Bind(std::move(factory)); |
| factory_->CreateSharedWorker( |
| std::move(info), instance_.constructor_origin(), |
| GetContentClient()->browser()->GetUserAgent(), |
| GetContentClient()->browser()->GetUserAgentMetadata(), pause_on_start, |
| dev_tools_token_, std::move(renderer_preferences), |
| std::move(preference_watcher_receiver), std::move(content_settings), |
| service_worker_handle_->TakeProviderInfo(), |
| appcache_handle_ |
| ? base::make_optional(appcache_handle_->appcache_host_id()) |
| : base::nullopt, |
| std::move(main_script_load_params), |
| std::move(subresource_loader_factories), std::move(controller), |
| receiver_.BindNewPipeAndPassRemote(), std::move(worker_receiver_), |
| std::move(browser_interface_broker)); |
| |
| // |service_worker_remote_object| is an associated interface ptr, so calls |
| // can't be made on it until its request endpoint is sent. Now that the |
| // request endpoint was sent, it can be used, so add it to |
| // ServiceWorkerObjectHost. |
| if (service_worker_remote_object.is_valid()) { |
| RunOrPostTaskOnThread( |
| FROM_HERE, ServiceWorkerContext::GetCoreThreadId(), |
| base::BindOnce( |
| &ServiceWorkerObjectHost::AddRemoteObjectPtrAndUpdateState, |
| controller_service_worker_object_host, |
| std::move(service_worker_remote_object), |
| service_worker_sent_state)); |
| } |
| |
| // Monitor the lifetime of the worker. |
| worker_.set_disconnect_handler(base::BindOnce( |
| &SharedWorkerHost::OnWorkerConnectionLost, weak_factory_.GetWeakPtr())); |
| |
| // Notify the service that the worker was started and that some clients were |
| // already connected. |
| service_->NotifyWorkerStarted(id_, worker_process_host_->GetID(), |
| dev_tools_token_); |
| for (const auto& client : clients_) { |
| service_->NotifyClientAdded(id_, client.render_frame_host_id); |
| } |
| } |
| |
| // 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); |
| |
| mojo::PendingRemote<network::mojom::URLLoaderFactory> pending_default_factory; |
| mojo::PendingReceiver<network::mojom::URLLoaderFactory> |
| default_factory_receiver = |
| pending_default_factory.InitWithNewPipeAndPassReceiver(); |
| |
| url::Origin origin = url::Origin::Create(instance_.url()); |
| |
| // TODO(https://crbug.com/1060832): Implement COEP reporter for shared |
| // workers. |
| network::mojom::URLLoaderFactoryParamsPtr factory_params = |
| URLLoaderFactoryParamsHelper::CreateForWorker( |
| worker_process_host_, instance_.constructor_origin(), |
| net::IsolationInfo::Create( |
| net::IsolationInfo::RedirectMode::kUpdateNothing, origin, origin, |
| net::SiteForCookies::FromOrigin(origin)), |
| /*coep_reporter=*/mojo::NullRemote()); |
| GetContentClient()->browser()->WillCreateURLLoaderFactory( |
| worker_process_host_->GetBrowserContext(), |
| /*frame=*/nullptr, worker_process_host_->GetID(), |
| ContentBrowserClient::URLLoaderFactoryType::kWorkerSubResource, origin, |
| /*navigation_id=*/base::nullopt, &default_factory_receiver, |
| &factory_params->header_client, bypass_redirect_checks, |
| /*disable_secure_dns=*/nullptr, &factory_params->factory_override); |
| |
| // TODO(nhiroki): Call devtools_instrumentation::WillCreateURLLoaderFactory() |
| // here. |
| |
| // TODO(yhirano): Support COEP. |
| worker_process_host_->CreateURLLoaderFactory( |
| std::move(default_factory_receiver), std::move(factory_params)); |
| |
| return pending_default_factory; |
| } |
| |
| void SharedWorkerHost::AllowFileSystem( |
| const GURL& url, |
| base::OnceCallback<void(bool)> callback) { |
| GetContentClient()->browser()->AllowWorkerFileSystem( |
| url, worker_process_host_->GetBrowserContext(), |
| GetRenderFrameIDsForWorker(), std::move(callback)); |
| } |
| |
| void SharedWorkerHost::AllowIndexedDB(const GURL& url, |
| base::OnceCallback<void(bool)> callback) { |
| std::move(callback).Run(GetContentClient()->browser()->AllowWorkerIndexedDB( |
| url, worker_process_host_->GetBrowserContext(), |
| GetRenderFrameIDsForWorker())); |
| } |
| |
| void SharedWorkerHost::AllowCacheStorage( |
| const GURL& url, |
| base::OnceCallback<void(bool)> callback) { |
| std::move(callback).Run( |
| GetContentClient()->browser()->AllowWorkerCacheStorage( |
| url, worker_process_host_->GetBrowserContext(), |
| GetRenderFrameIDsForWorker())); |
| } |
| |
| void SharedWorkerHost::AllowWebLocks(const GURL& url, |
| base::OnceCallback<void(bool)> callback) { |
| std::move(callback).Run(GetContentClient()->browser()->AllowWorkerWebLocks( |
| url, worker_process_host_->GetBrowserContext(), |
| GetRenderFrameIDsForWorker())); |
| } |
| |
| void SharedWorkerHost::CreateAppCacheBackend( |
| mojo::PendingReceiver<blink::mojom::AppCacheBackend> receiver) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| auto* storage_partition_impl = static_cast<StoragePartitionImpl*>( |
| worker_process_host_->GetStoragePartition()); |
| if (!storage_partition_impl) |
| return; |
| storage_partition_impl->GetAppCacheService()->CreateBackend( |
| worker_process_host_->GetID(), MSG_ROUTING_NONE, std::move(receiver)); |
| } |
| |
| void SharedWorkerHost::CreateQuicTransportConnector( |
| mojo::PendingReceiver<blink::mojom::QuicTransportConnector> receiver) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| const url::Origin origin = url::Origin::Create(instance().url()); |
| mojo::MakeSelfOwnedReceiver( |
| std::make_unique<QuicTransportConnectorImpl>( |
| worker_process_host_->GetID(), /*frame=*/nullptr, origin, |
| net::NetworkIsolationKey(origin, origin)), |
| std::move(receiver)); |
| } |
| |
| void SharedWorkerHost::BindCacheStorage( |
| mojo::PendingReceiver<blink::mojom::CacheStorage> receiver) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| // TODO(https://crbug.com/1031542): Add support enforcing CORP in |
| // cache.match() for SharedWorker by providing the correct value here. |
| network::CrossOriginEmbedderPolicy cross_origin_embedder_policy; |
| |
| // TODO(https://crbug.com/1031542): Plumb a CrossOriginEmbedderPolicyReporter |
| // here to handle reports. |
| mojo::PendingRemote<network::mojom::CrossOriginEmbedderPolicyReporter> |
| coep_reporter; |
| |
| const url::Origin origin = url::Origin::Create(instance().url()); |
| worker_process_host_->BindCacheStorage(cross_origin_embedder_policy, |
| std::move(coep_reporter), origin, |
| 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, |
| GlobalFrameRoutingId 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_); |
| |
| Destruct(); |
| } |
| |
| void SharedWorkerHost::OnReadyForInspection( |
| mojo::PendingRemote<blink::mojom::DevToolsAgent> agent_remote, |
| mojo::PendingReceiver<blink::mojom::DevToolsAgentHost> |
| agent_host_receiver) { |
| if (devtools_handle_) { |
| 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::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::RenderProcessExited( |
| RenderProcessHost* render_process_host, |
| const ChildProcessTerminationInfo& info) { |
| DCHECK_EQ(worker_process_host_, render_process_host); |
| Destruct(); |
| } |
| |
| std::vector<GlobalFrameRoutingId> |
| SharedWorkerHost::GetRenderFrameIDsForWorker() { |
| std::vector<GlobalFrameRoutingId> 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(); |
| } |
| |
| 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, |
| GlobalFrameRoutingId client_render_frame_host_id, |
| const blink::MessagePortChannel& port) { |
| 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()); |
| |
| 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())); |
| |
| worker_->Connect(info.connection_request_id, port.ReleaseHandle()); |
| |
| // Notify that a new client was added now. If the worker is not started, the |
| // Start() function will handle sending a notification for each existing |
| // client. |
| if (started_) |
| service_->NotifyClientAdded(id_, client_render_frame_host_id); |
| } |
| |
| void SharedWorkerHost::SetAppCacheHandle( |
| std::unique_ptr<AppCacheNavigationHandle> appcache_handle) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| appcache_handle_ = std::move(appcache_handle); |
| } |
| |
| 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_); |
| |
| // It isn't necessary to send a notification to the removed clients since they |
| // are about to be destroyed anyway. |
| clients_.remove_if([](const ClientInfo& client_info) { |
| return !RenderFrameHostImpl::FromID(client_info.render_frame_host_id); |
| }); |
| } |
| |
| bool SharedWorkerHost::HasClients() const { |
| return !clients_.empty(); |
| } |
| |
| 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 a client was removed while the worker was |
| // running. |
| if (started_) { |
| service_->NotifyClientRemoved(id_, it->render_frame_host_id); |
| } |
| clients_.erase(it); |
| break; |
| } |
| } |
| // If there are no clients left, then it's cleanup time. |
| if (clients_.empty()) |
| Destruct(); |
| } |
| |
| void SharedWorkerHost::OnWorkerConnectionLost() { |
| // This will destroy |this| resulting in client's observing their mojo |
| // connection being dropped. |
| Destruct(); |
| } |
| |
| } // namespace content |