blob: ecf29e7851da8b3416a081632a0c9f5f16dc5bf1 [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/memory/ptr_util.h"
#include "content/browser/devtools/shared_worker_devtools_manager.h"
#include "content/browser/shared_worker/shared_worker_host.h"
#include "content/browser/shared_worker/shared_worker_instance.h"
#include "content/browser/storage_partition_impl.h"
#include "content/browser/web_contents/web_contents_impl.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 "third_party/WebKit/common/message_port/message_port_channel.h"
#include "url/origin.h"
namespace content {
namespace {
bool IsShuttingDown(RenderProcessHost* host) {
return !host || host->FastShutdownStarted() ||
host->IsKeepAliveRefCountDisabled();
}
} // namespace
// static
SharedWorkerService* SharedWorkerService::GetInstance() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// OK to just leak the instance.
// TODO(darin): Consider hanging instances off of StoragePartitionImpl.
static SharedWorkerServiceImpl* instance = nullptr;
if (!instance)
instance = new SharedWorkerServiceImpl();
return instance;
}
SharedWorkerServiceImpl::SharedWorkerServiceImpl() {}
SharedWorkerServiceImpl::~SharedWorkerServiceImpl() {}
void SharedWorkerServiceImpl::ResetForTesting() {
worker_hosts_.clear();
}
bool SharedWorkerServiceImpl::TerminateWorker(
const GURL& url,
const std::string& name,
const url::Origin& constructor_origin,
StoragePartition* storage_partition,
ResourceContext* resource_context) {
StoragePartitionImpl* storage_partition_impl =
static_cast<StoragePartitionImpl*>(storage_partition);
WorkerStoragePartitionId partition_id(WorkerStoragePartition(
storage_partition_impl->GetURLRequestContext(),
storage_partition_impl->GetMediaURLRequestContext(),
storage_partition_impl->GetAppCacheService(),
storage_partition_impl->GetQuotaManager(),
storage_partition_impl->GetFileSystemContext(),
storage_partition_impl->GetDatabaseTracker(),
storage_partition_impl->GetIndexedDBContext(),
storage_partition_impl->GetServiceWorkerContext()));
for (const auto& iter : worker_hosts_) {
SharedWorkerHost* host = iter.second.get();
if (host->IsAvailable() &&
host->instance()->Matches(url, name, constructor_origin, partition_id,
resource_context)) {
host->TerminateWorker();
return true;
}
}
return false;
}
bool SharedWorkerServiceImpl::TerminateWorkerById(int process_id,
int route_id) {
SharedWorkerHost* host = FindSharedWorkerHost(process_id, route_id);
if (!host || !host->instance())
return false;
host->TerminateWorker();
return true;
}
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& iter : worker_hosts_)
iter.second->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,
ResourceContext* resource_context,
const WorkerStoragePartitionId& partition_id) {
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, resource_context, partition_id,
creation_context_type, base::UnguessableToken::Create());
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->process_id(), host->route_id());
}
CreateWorker(std::move(instance), std::move(client), process_id, frame_id,
message_port);
}
void SharedWorkerServiceImpl::DestroyHost(int process_id, int route_id) {
worker_hosts_.erase(WorkerID(process_id, route_id));
// Complete the call to TerminateAllWorkersForTesting if no more workers.
if (worker_hosts_.empty() && terminate_all_workers_callback_)
std::move(terminate_all_workers_callback_).Run();
RenderProcessHost* process_host = RenderProcessHost::FromID(process_id);
if (!IsShuttingDown(process_host))
process_host->DecrementKeepAliveRefCount();
}
void SharedWorkerServiceImpl::CreateWorker(
std::unique_ptr<SharedWorkerInstance> instance,
mojom::SharedWorkerClientPtr client,
int process_id,
int frame_id,
const blink::MessagePortChannel& message_port) {
// Re-use the process that requested the shared worker.
int worker_process_id = process_id;
RenderProcessHost* process_host = RenderProcessHost::FromID(process_id);
// If the requesting process is shutting down, then just drop this request on
// the floor. The client is not going to be around much longer anyways.
if (IsShuttingDown(process_host))
return;
// Keep the renderer process alive that will be hosting the shared worker.
process_host->IncrementKeepAliveRefCount();
// TODO(darin): Eliminate the need for shared workers to have routing IDs.
// Dev Tools will need to be modified to use something else as an identifier.
int worker_route_id = process_host->GetNextRoutingID();
bool pause_on_start =
SharedWorkerDevToolsManager::GetInstance()->WorkerCreated(
worker_process_id, worker_route_id, *instance);
auto host = std::make_unique<SharedWorkerHost>(
std::move(instance), worker_process_id, worker_route_id);
// 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), pause_on_start);
host->AddClient(std::move(client), process_id, frame_id, message_port);
const GURL url = host->instance()->url();
const std::string name = host->instance()->name();
worker_hosts_[WorkerID(worker_process_id, worker_route_id)] = std::move(host);
}
SharedWorkerHost* SharedWorkerServiceImpl::FindSharedWorkerHost(int process_id,
int route_id) {
auto iter = worker_hosts_.find(WorkerID(process_id, route_id));
if (iter == worker_hosts_.end())
return nullptr;
return iter->second.get();
}
SharedWorkerHost* SharedWorkerServiceImpl::FindAvailableSharedWorkerHost(
const SharedWorkerInstance& instance) {
for (const auto& iter : worker_hosts_) {
SharedWorkerHost* host = iter.second.get();
if (host->IsAvailable() && host->instance()->Matches(instance))
return host;
}
return nullptr;
}
} // namespace content