blob: 2bdc277d333cdd34371a5779a9fa4a3e86d9ebaf [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/macros.h"
#include "base/memory/ptr_util.h"
#include "base/memory/ref_counted.h"
#include "base/stl_util.h"
#include "content/browser/devtools/shared_worker_devtools_manager.h"
#include "content/browser/renderer_host/render_process_host_impl.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_message_filter.h"
#include "content/browser/shared_worker/worker_document_set.h"
#include "content/common/view_messages.h"
#include "content/common/worker_messages.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/worker_service_observer.h"
namespace content {
WorkerService* WorkerService::GetInstance() {
return SharedWorkerServiceImpl::GetInstance();
}
bool IsHostAlive(RenderProcessHostImpl* host) {
return host && !host->FastShutdownStarted() &&
!host->IsWorkerRefCountDisabled();
}
namespace {
class ScopedWorkerDependencyChecker {
public:
explicit ScopedWorkerDependencyChecker(SharedWorkerServiceImpl* service)
: service_(service) {}
ScopedWorkerDependencyChecker(SharedWorkerServiceImpl* service,
base::Closure done_closure)
: service_(service), done_closure_(done_closure) {}
~ScopedWorkerDependencyChecker() {
service_->CheckWorkerDependency();
if (!done_closure_.is_null())
done_closure_.Run();
}
private:
SharedWorkerServiceImpl* service_;
base::Closure done_closure_;
DISALLOW_COPY_AND_ASSIGN(ScopedWorkerDependencyChecker);
};
void UpdateWorkerDependencyOnUI(const std::vector<int>& added_ids,
const std::vector<int>& removed_ids) {
for (int id : added_ids) {
RenderProcessHostImpl* render_process_host_impl =
static_cast<RenderProcessHostImpl*>(RenderProcessHost::FromID(id));
if (!IsHostAlive(render_process_host_impl))
continue;
render_process_host_impl->IncrementSharedWorkerRefCount();
}
for (int id : removed_ids) {
RenderProcessHostImpl* render_process_host_impl =
static_cast<RenderProcessHostImpl*>(RenderProcessHost::FromID(id));
if (!IsHostAlive(render_process_host_impl))
continue;
render_process_host_impl->DecrementSharedWorkerRefCount();
}
}
void UpdateWorkerDependency(const std::vector<int>& added_ids,
const std::vector<int>& removed_ids) {
BrowserThread::PostTask(
BrowserThread::UI,
FROM_HERE,
base::Bind(&UpdateWorkerDependencyOnUI, added_ids, removed_ids));
}
void DecrementWorkerRefCount(int process_id) {
if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
BrowserThread::PostTask(BrowserThread::UI,
FROM_HERE,
base::Bind(&DecrementWorkerRefCount, process_id));
return;
}
RenderProcessHostImpl* render_process_host_impl =
static_cast<RenderProcessHostImpl*>(
RenderProcessHost::FromID(process_id));
if (IsHostAlive(render_process_host_impl))
render_process_host_impl->DecrementSharedWorkerRefCount();
}
bool TryIncrementWorkerRefCount(int worker_process_id) {
RenderProcessHostImpl* render_process = static_cast<RenderProcessHostImpl*>(
RenderProcessHost::FromID(worker_process_id));
if (!IsHostAlive(render_process))
return false;
render_process->IncrementSharedWorkerRefCount();
return true;
}
} // namespace
class SharedWorkerServiceImpl::SharedWorkerPendingInstance {
public:
struct SharedWorkerPendingRequest {
SharedWorkerPendingRequest(SharedWorkerMessageFilter* filter,
int route_id,
unsigned long long document_id,
int render_process_id,
int render_frame_route_id)
: filter(filter),
route_id(route_id),
document_id(document_id),
render_process_id(render_process_id),
render_frame_route_id(render_frame_route_id) {}
SharedWorkerMessageFilter* const filter;
const int route_id;
const unsigned long long document_id;
const int render_process_id;
const int render_frame_route_id;
};
using SharedWorkerPendingRequests =
std::vector<std::unique_ptr<SharedWorkerPendingRequest>>;
explicit SharedWorkerPendingInstance(
std::unique_ptr<SharedWorkerInstance> instance)
: instance_(std::move(instance)) {}
~SharedWorkerPendingInstance() {}
SharedWorkerInstance* instance() { return instance_.get(); }
SharedWorkerInstance* release_instance() { return instance_.release(); }
SharedWorkerPendingRequests* requests() { return &requests_; }
SharedWorkerMessageFilter* FindFilter(int process_id) {
for (const auto& request : requests_) {
if (request->render_process_id == process_id)
return request->filter;
}
return nullptr;
}
void AddRequest(std::unique_ptr<SharedWorkerPendingRequest> request_info) {
requests_.push_back(std::move(request_info));
}
void RemoveRequest(int process_id) {
auto to_remove = std::remove_if(
requests_.begin(), requests_.end(),
[process_id](const std::unique_ptr<SharedWorkerPendingRequest>& r) {
return r->render_process_id == process_id;
});
requests_.erase(to_remove, requests_.end());
}
void RemoveRequestFromFrame(int process_id, int frame_id) {
auto to_remove = std::remove_if(
requests_.begin(), requests_.end(),
[process_id,
frame_id](const std::unique_ptr<SharedWorkerPendingRequest>& r) {
return r->render_process_id == process_id &&
r->render_frame_route_id == frame_id;
});
requests_.erase(to_remove, requests_.end());
}
void RegisterToSharedWorkerHost(SharedWorkerHost* host) {
for (const auto& request : requests_) {
host->AddFilter(request->filter, request->route_id);
host->worker_document_set()->Add(request->filter,
request->document_id,
request->render_process_id,
request->render_frame_route_id);
}
}
void SendWorkerCreatedMessages() {
for (const auto& request : requests_)
request->filter->Send(new ViewMsg_WorkerCreated(request->route_id));
}
private:
std::unique_ptr<SharedWorkerInstance> instance_;
SharedWorkerPendingRequests requests_;
DISALLOW_COPY_AND_ASSIGN(SharedWorkerPendingInstance);
};
class SharedWorkerServiceImpl::SharedWorkerReserver
: public base::RefCountedThreadSafe<SharedWorkerReserver> {
public:
SharedWorkerReserver(int pending_instance_id,
int worker_process_id,
int worker_route_id,
bool is_new_worker,
const SharedWorkerInstance& instance)
: worker_process_id_(worker_process_id),
worker_route_id_(worker_route_id),
is_new_worker_(is_new_worker),
instance_(instance) {}
void TryReserve(const base::Callback<void(bool)>& success_cb,
const base::Closure& failure_cb,
bool (*try_increment_worker_ref_count)(int)) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!try_increment_worker_ref_count(worker_process_id_)) {
BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, failure_cb);
return;
}
bool pause_on_start = false;
if (is_new_worker_) {
pause_on_start =
SharedWorkerDevToolsManager::GetInstance()->WorkerCreated(
worker_process_id_, worker_route_id_, instance_);
}
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE, base::Bind(success_cb, pause_on_start));
}
private:
friend class base::RefCountedThreadSafe<SharedWorkerReserver>;
~SharedWorkerReserver() {}
const int worker_process_id_;
const int worker_route_id_;
const bool is_new_worker_;
const SharedWorkerInstance instance_;
};
// static
bool (*SharedWorkerServiceImpl::s_try_increment_worker_ref_count_)(int) =
TryIncrementWorkerRefCount;
SharedWorkerServiceImpl* SharedWorkerServiceImpl::GetInstance() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
return base::Singleton<SharedWorkerServiceImpl>::get();
}
SharedWorkerServiceImpl::SharedWorkerServiceImpl()
: update_worker_dependency_(UpdateWorkerDependency),
next_pending_instance_id_(0) {
}
SharedWorkerServiceImpl::~SharedWorkerServiceImpl() {}
void SharedWorkerServiceImpl::ResetForTesting() {
last_worker_depended_renderers_.clear();
worker_hosts_.clear();
observers_.Clear();
update_worker_dependency_ = UpdateWorkerDependency;
s_try_increment_worker_ref_count_ = TryIncrementWorkerRefCount;
}
bool SharedWorkerServiceImpl::TerminateWorker(int process_id, int route_id) {
SharedWorkerHost* host = FindSharedWorkerHost(process_id, route_id);
if (!host || !host->instance())
return false;
host->TerminateWorker();
return true;
}
std::vector<WorkerService::WorkerInfo> SharedWorkerServiceImpl::GetWorkers() {
std::vector<WorkerService::WorkerInfo> results;
for (const auto& iter : worker_hosts_) {
SharedWorkerHost* host = iter.second.get();
const SharedWorkerInstance* instance = host->instance();
if (instance) {
WorkerService::WorkerInfo info;
info.url = instance->url();
info.name = instance->name();
info.route_id = host->worker_route_id();
info.process_id = host->process_id();
info.handle = host->worker_render_filter()->PeerHandle();
results.push_back(info);
}
}
return results;
}
void SharedWorkerServiceImpl::AddObserver(WorkerServiceObserver* observer) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
observers_.AddObserver(observer);
}
void SharedWorkerServiceImpl::RemoveObserver(WorkerServiceObserver* observer) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
observers_.RemoveObserver(observer);
}
blink::WebWorkerCreationError SharedWorkerServiceImpl::CreateWorker(
const ViewHostMsg_CreateWorker_Params& params,
int route_id,
SharedWorkerMessageFilter* filter,
ResourceContext* resource_context,
const WorkerStoragePartitionId& partition_id) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
std::unique_ptr<SharedWorkerInstance> instance(new SharedWorkerInstance(
params.url, params.name, params.content_security_policy,
params.security_policy_type, params.creation_address_space,
resource_context, partition_id, params.creation_context_type));
std::unique_ptr<SharedWorkerPendingInstance::SharedWorkerPendingRequest>
request(new SharedWorkerPendingInstance::SharedWorkerPendingRequest(
filter, route_id, params.document_id, filter->render_process_id(),
params.render_frame_route_id));
if (SharedWorkerPendingInstance* pending = FindPendingInstance(*instance)) {
pending->AddRequest(std::move(request));
if (params.creation_context_type !=
pending->instance()->creation_context_type())
return blink::WebWorkerCreationErrorSecureContextMismatch;
return blink::WebWorkerCreationErrorNone;
}
std::unique_ptr<SharedWorkerPendingInstance> pending_instance(
new SharedWorkerPendingInstance(std::move(instance)));
pending_instance->AddRequest(std::move(request));
return ReserveRenderProcessToCreateWorker(std::move(pending_instance));
}
void SharedWorkerServiceImpl::ConnectToWorker(
int route_id,
int sent_message_port_id,
SharedWorkerMessageFilter* filter) {
for (WorkerHostMap::const_iterator iter = worker_hosts_.begin();
iter != worker_hosts_.end();
++iter) {
if (iter->second->FilterConnectionMessage(route_id, sent_message_port_id,
filter))
return;
}
}
void SharedWorkerServiceImpl::DocumentDetached(
unsigned long long document_id,
SharedWorkerMessageFilter* filter) {
ScopedWorkerDependencyChecker checker(this);
for (WorkerHostMap::const_iterator iter = worker_hosts_.begin();
iter != worker_hosts_.end();
++iter) {
iter->second->DocumentDetached(filter, document_id);
}
}
void SharedWorkerServiceImpl::WorkerContextClosed(
int worker_route_id,
SharedWorkerMessageFilter* filter) {
ScopedWorkerDependencyChecker checker(this);
if (SharedWorkerHost* host =
FindSharedWorkerHost(filter->render_process_id(), worker_route_id))
host->WorkerContextClosed();
}
void SharedWorkerServiceImpl::WorkerContextDestroyed(
int worker_route_id,
SharedWorkerMessageFilter* filter) {
ScopedWorkerDependencyChecker checker(this);
ProcessRouteIdPair key(filter->render_process_id(), worker_route_id);
worker_hosts_.erase(key);
}
void SharedWorkerServiceImpl::WorkerReadyForInspection(
int worker_route_id,
SharedWorkerMessageFilter* filter) {
if (SharedWorkerHost* host =
FindSharedWorkerHost(filter->render_process_id(), worker_route_id))
host->WorkerReadyForInspection();
}
void SharedWorkerServiceImpl::WorkerScriptLoaded(
int worker_route_id,
SharedWorkerMessageFilter* filter) {
if (SharedWorkerHost* host =
FindSharedWorkerHost(filter->render_process_id(), worker_route_id))
host->WorkerScriptLoaded();
}
void SharedWorkerServiceImpl::WorkerScriptLoadFailed(
int worker_route_id,
SharedWorkerMessageFilter* filter) {
ScopedWorkerDependencyChecker checker(this);
ProcessRouteIdPair key(filter->render_process_id(), worker_route_id);
if (!base::ContainsKey(worker_hosts_, key))
return;
std::unique_ptr<SharedWorkerHost> host(worker_hosts_[key].release());
worker_hosts_.erase(key);
host->WorkerScriptLoadFailed();
}
void SharedWorkerServiceImpl::WorkerConnected(
int message_port_id,
int worker_route_id,
SharedWorkerMessageFilter* filter) {
if (SharedWorkerHost* host =
FindSharedWorkerHost(filter->render_process_id(), worker_route_id))
host->WorkerConnected(message_port_id);
}
void SharedWorkerServiceImpl::AllowFileSystem(
int worker_route_id,
const GURL& url,
IPC::Message* reply_msg,
SharedWorkerMessageFilter* filter) {
if (SharedWorkerHost* host =
FindSharedWorkerHost(filter->render_process_id(), worker_route_id)) {
host->AllowFileSystem(url, base::WrapUnique(reply_msg));
} else {
filter->Send(reply_msg);
return;
}
}
void SharedWorkerServiceImpl::AllowIndexedDB(
int worker_route_id,
const GURL& url,
const base::string16& name,
bool* result,
SharedWorkerMessageFilter* filter) {
if (SharedWorkerHost* host =
FindSharedWorkerHost(filter->render_process_id(), worker_route_id))
host->AllowIndexedDB(url, name, result);
else
*result = false;
}
void SharedWorkerServiceImpl::OnSharedWorkerMessageFilterClosing(
SharedWorkerMessageFilter* filter) {
ScopedWorkerDependencyChecker checker(this);
std::vector<ProcessRouteIdPair> remove_list;
for (const auto& it : worker_hosts_) {
it.second->FilterShutdown(filter);
if (it.first.first == filter->render_process_id())
remove_list.push_back(it.first);
}
for (const ProcessRouteIdPair& to_remove : remove_list)
worker_hosts_.erase(to_remove);
std::vector<int> remove_pending_instance_list;
for (const auto& it : pending_instances_) {
it.second->RemoveRequest(filter->render_process_id());
if (it.second->requests()->empty())
remove_pending_instance_list.push_back(it.first);
}
for (int to_remove : remove_pending_instance_list)
pending_instances_.erase(to_remove);
}
void SharedWorkerServiceImpl::RenderFrameDetached(int render_process_id,
int render_frame_id) {
ScopedWorkerDependencyChecker checker(this);
for (const auto& it : worker_hosts_) {
it.second->RenderFrameDetached(render_process_id, render_frame_id);
}
auto it = pending_instances_.begin();
while (it != pending_instances_.end()) {
it->second->RemoveRequestFromFrame(render_process_id, render_frame_id);
if (it->second->requests()->empty())
it = pending_instances_.erase(it);
else
++it;
}
}
void SharedWorkerServiceImpl::NotifyWorkerDestroyed(int worker_process_id,
int worker_route_id) {
for (auto& observer : observers_)
observer.WorkerDestroyed(worker_process_id, worker_route_id);
}
blink::WebWorkerCreationError
SharedWorkerServiceImpl::ReserveRenderProcessToCreateWorker(
std::unique_ptr<SharedWorkerPendingInstance> pending_instance) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(!FindPendingInstance(*pending_instance->instance()));
if (!pending_instance->requests()->size())
return blink::WebWorkerCreationErrorNone;
int worker_process_id = -1;
int worker_route_id = MSG_ROUTING_NONE;
bool is_new_worker = true;
blink::WebWorkerCreationError creation_error =
blink::WebWorkerCreationErrorNone;
SharedWorkerHost* host = FindSharedWorkerHost(*pending_instance->instance());
if (host) {
if (pending_instance->instance()->creation_context_type() !=
host->instance()->creation_context_type()) {
creation_error = blink::WebWorkerCreationErrorSecureContextMismatch;
}
worker_process_id = host->process_id();
worker_route_id = host->worker_route_id();
is_new_worker = false;
} else {
SharedWorkerMessageFilter* first_filter =
(*pending_instance->requests()->begin())->filter;
worker_process_id = first_filter->render_process_id();
worker_route_id = first_filter->GetNextRoutingID();
}
const int pending_instance_id = next_pending_instance_id_++;
scoped_refptr<SharedWorkerReserver> reserver(
new SharedWorkerReserver(pending_instance_id,
worker_process_id,
worker_route_id,
is_new_worker,
*pending_instance->instance()));
BrowserThread::PostTask(
BrowserThread::UI,
FROM_HERE,
base::Bind(
&SharedWorkerReserver::TryReserve,
reserver,
base::Bind(&SharedWorkerServiceImpl::RenderProcessReservedCallback,
base::Unretained(this),
pending_instance_id,
worker_process_id,
worker_route_id,
is_new_worker),
base::Bind(
&SharedWorkerServiceImpl::RenderProcessReserveFailedCallback,
base::Unretained(this),
pending_instance_id,
worker_process_id,
worker_route_id,
is_new_worker),
s_try_increment_worker_ref_count_));
pending_instances_[pending_instance_id] = std::move(pending_instance);
return creation_error;
}
void SharedWorkerServiceImpl::RenderProcessReservedCallback(
int pending_instance_id,
int worker_process_id,
int worker_route_id,
bool is_new_worker,
bool pause_on_start) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
// To offset the TryIncrementWorkerRefCount called for the reservation,
// calls DecrementWorkerRefCount after CheckWorkerDependency in
// ScopeWorkerDependencyChecker's destructor.
ScopedWorkerDependencyChecker checker(
this, base::Bind(&DecrementWorkerRefCount, worker_process_id));
if (!base::ContainsKey(pending_instances_, pending_instance_id))
return;
std::unique_ptr<SharedWorkerPendingInstance> pending_instance(
pending_instances_[pending_instance_id].release());
pending_instances_.erase(pending_instance_id);
if (!is_new_worker) {
SharedWorkerHost* host =
FindSharedWorkerHost(worker_process_id, worker_route_id);
if (!host) {
// Retry reserving a renderer process if the existed Shared Worker was
// destroyed on IO thread while reserving the renderer process on UI
// thread.
ReserveRenderProcessToCreateWorker(std::move(pending_instance));
return;
}
pending_instance->RegisterToSharedWorkerHost(host);
pending_instance->SendWorkerCreatedMessages();
return;
}
SharedWorkerMessageFilter* filter =
pending_instance->FindFilter(worker_process_id);
if (!filter) {
pending_instance->RemoveRequest(worker_process_id);
// Retry reserving a renderer process if the requested renderer process was
// destroyed on IO thread while reserving the renderer process on UI thread.
ReserveRenderProcessToCreateWorker(std::move(pending_instance));
return;
}
std::unique_ptr<SharedWorkerHost> host(new SharedWorkerHost(
pending_instance->release_instance(), filter, worker_route_id));
pending_instance->RegisterToSharedWorkerHost(host.get());
const GURL url = host->instance()->url();
const base::string16 name = host->instance()->name();
host->Start(pause_on_start);
ProcessRouteIdPair key(worker_process_id, worker_route_id);
worker_hosts_[key] = std::move(host);
for (auto& observer : observers_)
observer.WorkerCreated(url, name, worker_process_id, worker_route_id);
}
void SharedWorkerServiceImpl::RenderProcessReserveFailedCallback(
int pending_instance_id,
int worker_process_id,
int worker_route_id,
bool is_new_worker) {
worker_hosts_.erase(std::make_pair(worker_process_id, worker_route_id));
if (!base::ContainsKey(pending_instances_, pending_instance_id))
return;
std::unique_ptr<SharedWorkerPendingInstance> pending_instance(
pending_instances_[pending_instance_id].release());
pending_instances_.erase(pending_instance_id);
pending_instance->RemoveRequest(worker_process_id);
// Retry reserving a renderer process.
ReserveRenderProcessToCreateWorker(std::move(pending_instance));
}
SharedWorkerHost* SharedWorkerServiceImpl::FindSharedWorkerHost(
int render_process_id,
int worker_route_id) {
ProcessRouteIdPair key = std::make_pair(render_process_id, worker_route_id);
if (!base::ContainsKey(worker_hosts_, key))
return nullptr;
return worker_hosts_[key].get();
}
SharedWorkerHost* SharedWorkerServiceImpl::FindSharedWorkerHost(
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;
}
SharedWorkerServiceImpl::SharedWorkerPendingInstance*
SharedWorkerServiceImpl::FindPendingInstance(
const SharedWorkerInstance& instance) {
for (const auto& iter : pending_instances_) {
if (iter.second->instance()->Matches(instance))
return iter.second.get();
}
return nullptr;
}
const std::set<int>
SharedWorkerServiceImpl::GetRenderersWithWorkerDependency() {
std::set<int> dependent_renderers;
for (WorkerHostMap::iterator host_iter = worker_hosts_.begin();
host_iter != worker_hosts_.end();
++host_iter) {
const int process_id = host_iter->first.first;
if (dependent_renderers.count(process_id))
continue;
if (host_iter->second->instance() &&
host_iter->second->worker_document_set()->ContainsExternalRenderer(
process_id)) {
dependent_renderers.insert(process_id);
}
}
return dependent_renderers;
}
void SharedWorkerServiceImpl::CheckWorkerDependency() {
const std::set<int> current_worker_depended_renderers =
GetRenderersWithWorkerDependency();
std::vector<int> added_items = base::STLSetDifference<std::vector<int> >(
current_worker_depended_renderers, last_worker_depended_renderers_);
std::vector<int> removed_items = base::STLSetDifference<std::vector<int> >(
last_worker_depended_renderers_, current_worker_depended_renderers);
if (!added_items.empty() || !removed_items.empty()) {
last_worker_depended_renderers_ = current_worker_depended_renderers;
update_worker_dependency_(added_items, removed_items);
}
}
void SharedWorkerServiceImpl::ChangeUpdateWorkerDependencyFuncForTesting(
UpdateWorkerDependencyFunc new_func) {
update_worker_dependency_ = new_func;
}
void SharedWorkerServiceImpl::ChangeTryIncrementWorkerRefCountFuncForTesting(
bool (*new_func)(int)) {
s_try_increment_worker_ref_count_ = new_func;
}
} // namespace content