blob: 0727104684b18d30b76212643c1579e061db05e3 [file] [log] [blame]
// 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/shared_worker/shared_worker_service_impl.h"
#include <stddef.h>
#include <algorithm>
#include <iterator>
#include "base/callback.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/task_scheduler/post_task.h"
#include "content/browser/file_url_loader_factory.h"
#include "content/browser/shared_worker/shared_worker_host.h"
#include "content/browser/shared_worker/shared_worker_instance.h"
#include "content/browser/shared_worker/shared_worker_script_loader_factory.h"
#include "content/browser/storage_partition_impl.h"
#include "content/browser/url_loader_factory_getter.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/common/service_worker/service_worker_provider.mojom.h"
#include "content/common/shared_worker/shared_worker_client.mojom.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/common/bind_interface_helpers.h"
#include "mojo/public/cpp/bindings/strong_associated_binding.h"
#include "mojo/public/cpp/bindings/strong_binding.h"
#include "third_party/blink/public/common/message_port/message_port_channel.h"
#include "third_party/blink/public/common/service_worker/service_worker_utils.h"
#include "url/origin.h"
namespace content {
namespace {
bool IsShuttingDown(RenderProcessHost* host) {
return !host || host->FastShutdownStarted() ||
host->IsKeepAliveRefCountDisabled();
}
std::unique_ptr<URLLoaderFactoryBundleInfo> CreateFactoryBundle(
int process_id,
StoragePartitionImpl* storage_partition,
bool file_support) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
ContentBrowserClient::NonNetworkURLLoaderFactoryMap factories;
GetContentClient()
->browser()
->RegisterNonNetworkSubresourceURLLoaderFactories(
process_id, MSG_ROUTING_NONE, &factories);
auto factory_bundle = std::make_unique<URLLoaderFactoryBundleInfo>();
for (auto& pair : factories) {
const std::string& scheme = pair.first;
std::unique_ptr<network::mojom::URLLoaderFactory> factory =
std::move(pair.second);
network::mojom::URLLoaderFactoryPtr factory_ptr;
mojo::MakeStrongBinding(std::move(factory),
mojo::MakeRequest(&factory_ptr));
factory_bundle->factories_info().emplace(scheme,
factory_ptr.PassInterface());
}
if (file_support) {
auto file_factory = std::make_unique<FileURLLoaderFactory>(
storage_partition->browser_context()->GetPath(),
base::CreateSequencedTaskRunnerWithTraits(
{base::MayBlock(), base::TaskPriority::BACKGROUND,
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN}));
network::mojom::URLLoaderFactoryPtr file_factory_ptr;
mojo::MakeStrongBinding(std::move(file_factory),
mojo::MakeRequest(&file_factory_ptr));
factory_bundle->factories_info().emplace(url::kFileScheme,
file_factory_ptr.PassInterface());
}
return factory_bundle;
}
void CreateScriptLoaderOnIO(
scoped_refptr<URLLoaderFactoryGetter> loader_factory_getter,
std::unique_ptr<URLLoaderFactoryBundleInfo> factory_bundle_for_browser_info,
std::unique_ptr<URLLoaderFactoryBundleInfo>
factory_bundle_for_renderer_info,
scoped_refptr<ServiceWorkerContextWrapper> context,
std::unique_ptr<network::SharedURLLoaderFactoryInfo>
blob_url_loader_factory_info,
int process_id,
base::OnceCallback<void(mojom::ServiceWorkerProviderInfoForSharedWorkerPtr,
network::mojom::URLLoaderFactoryAssociatedPtrInfo,
std::unique_ptr<URLLoaderFactoryBundleInfo>)>
callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
// Set up for service worker.
auto provider_info = mojom::ServiceWorkerProviderInfoForSharedWorker::New();
base::WeakPtr<ServiceWorkerProviderHost> host =
context->PreCreateHostForSharedWorker(process_id, &provider_info);
// Create the URL loader factory for SharedWorkerScriptLoaderFactory to use to
// load the main script.
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory;
if (blob_url_loader_factory_info) {
// If we have a blob_url_loader_factory just use that directly rather than
// creating a new URLLoaderFactoryBundle.
url_loader_factory = network::SharedURLLoaderFactory::Create(
std::move(blob_url_loader_factory_info));
} else {
// Create a factory bundle to use.
scoped_refptr<URLLoaderFactoryBundle> factory_bundle =
base::MakeRefCounted<URLLoaderFactoryBundle>(
std::move(factory_bundle_for_browser_info));
url_loader_factory = factory_bundle;
// Add the network factory to the bundle. The factory from
// CloneNetworkFactory() doesn't support reconnection to the network service
// after a crash, but it's OK since it's used for a single shared worker
// startup.
network::mojom::URLLoaderFactoryPtr network_factory_ptr;
loader_factory_getter->CloneNetworkFactory(
mojo::MakeRequest(&network_factory_ptr));
factory_bundle->SetDefaultFactory(std::move(network_factory_ptr));
}
// Create the SharedWorkerScriptLoaderFactory.
network::mojom::URLLoaderFactoryAssociatedPtrInfo script_loader_factory;
mojo::MakeStrongAssociatedBinding(
std::make_unique<SharedWorkerScriptLoaderFactory>(
context.get(), host->AsWeakPtr(), context->resource_context(),
std::move(url_loader_factory)),
mojo::MakeRequest(&script_loader_factory));
// We continue in StartWorker.
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::BindOnce(std::move(callback), std::move(provider_info),
std::move(script_loader_factory),
std::move(factory_bundle_for_renderer_info)));
}
} // namespace
SharedWorkerServiceImpl::SharedWorkerServiceImpl(
StoragePartition* storage_partition,
scoped_refptr<ServiceWorkerContextWrapper> service_worker_context)
: storage_partition_(storage_partition),
service_worker_context_(std::move(service_worker_context)),
weak_factory_(this) {}
SharedWorkerServiceImpl::~SharedWorkerServiceImpl() {}
bool SharedWorkerServiceImpl::TerminateWorker(
const GURL& url,
const std::string& name,
const url::Origin& constructor_origin) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
for (auto& host : worker_hosts_) {
if (host->IsAvailable() &&
host->instance()->Matches(url, name, constructor_origin)) {
host->TerminateWorker();
return true;
}
}
return false;
}
void SharedWorkerServiceImpl::TerminateAllWorkersForTesting(
base::OnceClosure callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(!terminate_all_workers_callback_);
if (worker_hosts_.empty()) {
// Run callback asynchronously to avoid re-entering the caller.
base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
std::move(callback));
} else {
terminate_all_workers_callback_ = std::move(callback);
for (auto& host : worker_hosts_)
host->TerminateWorker();
// Monitor for actual termination in DestroyHost.
}
}
void SharedWorkerServiceImpl::ConnectToWorker(
int process_id,
int frame_id,
mojom::SharedWorkerInfoPtr info,
mojom::SharedWorkerClientPtr client,
blink::mojom::SharedWorkerCreationContextType creation_context_type,
const blink::MessagePortChannel& message_port,
scoped_refptr<network::SharedURLLoaderFactory> blob_url_loader_factory) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
RenderFrameHostImpl* render_frame_host =
RenderFrameHostImpl::FromID(process_id, frame_id);
if (!render_frame_host) {
// TODO(nhiroki): Support the case where the requester is a worker (i.e.,
// nested worker) (https://crbug.com/31666).
client->OnScriptLoadFailed();
return;
}
RenderFrameHost* main_frame =
render_frame_host->frame_tree_node()->frame_tree()->GetMainFrame();
if (!GetContentClient()->browser()->AllowSharedWorker(
info->url, main_frame->GetLastCommittedURL(), info->name,
render_frame_host->GetLastCommittedOrigin(),
WebContentsImpl::FromRenderFrameHostID(process_id, frame_id)
->GetBrowserContext(),
process_id, frame_id)) {
client->OnScriptLoadFailed();
return;
}
auto instance = std::make_unique<SharedWorkerInstance>(
info->url, info->name, render_frame_host->GetLastCommittedOrigin(),
info->content_security_policy, info->content_security_policy_type,
info->creation_address_space, creation_context_type);
SharedWorkerHost* host = FindAvailableSharedWorkerHost(*instance);
if (host) {
// Non-secure contexts cannot connect to secure workers, and secure contexts
// cannot connect to non-secure workers:
if (host->instance()->creation_context_type() != creation_context_type) {
client->OnScriptLoadFailed();
return;
}
// The process may be shutting down, in which case we will try to create a
// new shared worker instead.
if (!IsShuttingDown(RenderProcessHost::FromID(host->process_id()))) {
host->AddClient(std::move(client), process_id, frame_id, message_port);
return;
}
// Cleanup the existing shared worker now, to avoid having two matching
// instances. This host would likely be observing the destruction of the
// child process shortly, but we can clean this up now to avoid some
// complexity.
DestroyHost(host);
}
CreateWorker(std::move(instance), std::move(client), process_id, frame_id,
message_port, std::move(blob_url_loader_factory));
}
void SharedWorkerServiceImpl::DestroyHost(SharedWorkerHost* host) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
RenderProcessHost* process_host =
RenderProcessHost::FromID(host->process_id());
worker_hosts_.erase(worker_hosts_.find(host));
// Complete the call to TerminateAllWorkersForTesting if no more workers.
if (worker_hosts_.empty() && terminate_all_workers_callback_)
std::move(terminate_all_workers_callback_).Run();
if (!IsShuttingDown(process_host))
process_host->DecrementKeepAliveRefCount(
RenderProcessHost::KeepAliveClientType::kSharedWorker);
}
void SharedWorkerServiceImpl::CreateWorker(
std::unique_ptr<SharedWorkerInstance> instance,
mojom::SharedWorkerClientPtr client,
int process_id,
int frame_id,
const blink::MessagePortChannel& message_port,
scoped_refptr<network::SharedURLLoaderFactory> blob_url_loader_factory) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(!blob_url_loader_factory || instance->url().SchemeIsBlob());
bool constructor_uses_file_url =
instance->constructor_origin().scheme() == url::kFileScheme;
// Create the host. We need to do this even before starting the worker,
// because we are about to bounce to the IO thread. If another ConnectToWorker
// request arrives in the meantime, it finds and reuses the host instead of
// creating a new host and therefore new SharedWorker thread.
auto host =
std::make_unique<SharedWorkerHost>(this, std::move(instance), process_id);
auto weak_host = host->AsWeakPtr();
worker_hosts_.insert(std::move(host));
StoragePartitionImpl* storage_partition =
service_worker_context_->storage_partition();
// Bounce to the IO thread to setup service worker support in case the request
// for the worker script will need to be intercepted by service workers.
if (blink::ServiceWorkerUtils::IsServicificationEnabled()) {
if (!storage_partition) {
// The context is shutting down. Just drop the request.
return;
}
// Set up the factory bundle for non-NetworkService URLs, e.g.,
// chrome-extension:// URLs. One factory bundle is consumed by the browser
// for SharedWorkerScriptLoaderFactory, and one is sent to the renderer.
std::unique_ptr<URLLoaderFactoryBundleInfo> factory_bundle_for_browser =
CreateFactoryBundle(process_id, storage_partition,
constructor_uses_file_url);
std::unique_ptr<URLLoaderFactoryBundleInfo> factory_bundle_for_renderer =
CreateFactoryBundle(process_id, storage_partition,
constructor_uses_file_url);
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::BindOnce(
&CreateScriptLoaderOnIO,
service_worker_context_->storage_partition()
->url_loader_factory_getter(),
std::move(factory_bundle_for_browser),
std::move(factory_bundle_for_renderer), service_worker_context_,
blob_url_loader_factory ? blob_url_loader_factory->Clone()
: nullptr,
process_id,
base::BindOnce(&SharedWorkerServiceImpl::StartWorker,
weak_factory_.GetWeakPtr(), std::move(instance),
weak_host, std::move(client), process_id, frame_id,
message_port)));
return;
}
StartWorker(std::move(instance), weak_host, std::move(client), process_id,
frame_id, message_port, nullptr, {}, nullptr);
}
void SharedWorkerServiceImpl::StartWorker(
std::unique_ptr<SharedWorkerInstance> instance,
base::WeakPtr<SharedWorkerHost> host,
mojom::SharedWorkerClientPtr client,
int process_id,
int frame_id,
const blink::MessagePortChannel& message_port,
mojom::ServiceWorkerProviderInfoForSharedWorkerPtr
service_worker_provider_info,
network::mojom::URLLoaderFactoryAssociatedPtrInfo
script_loader_factory_info,
std::unique_ptr<URLLoaderFactoryBundleInfo> factory_bundle) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// The host may already be gone if something forcibly terminated the worker
// before it could start (e.g., in tests or a UI action). Just fail.
if (!host)
return;
RenderProcessHost* process_host = RenderProcessHost::FromID(process_id);
// If the target process is shutting down, then just drop this request and
// tell the host to destruct. This also means clients that were still waiting
// for the shared worker to start will fail.
if (!process_host || IsShuttingDown(process_host)) {
host->TerminateWorker();
return;
}
// Keep the renderer process alive that will be hosting the shared worker.
process_host->IncrementKeepAliveRefCount(
RenderProcessHost::KeepAliveClientType::kSharedWorker);
// Get the factory used to instantiate the new shared worker instance in
// the target process.
mojom::SharedWorkerFactoryPtr factory;
BindInterface(process_host, &factory);
host->Start(std::move(factory), std::move(service_worker_provider_info),
std::move(script_loader_factory_info), std::move(factory_bundle));
host->AddClient(std::move(client), process_id, frame_id, message_port);
}
SharedWorkerHost* SharedWorkerServiceImpl::FindAvailableSharedWorkerHost(
const SharedWorkerInstance& instance) {
for (auto& host : worker_hosts_) {
if (host->IsAvailable() && host->instance()->Matches(instance))
return host.get();
}
return nullptr;
}
} // namespace content