blob: 3ebab469ff75a87981ab9f28279546c5208d75c1 [file] [log] [blame]
// Copyright (c) 2012 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/worker_service_impl.h"
#include <string>
#include "base/command_line.h"
#include "base/logging.h"
#include "base/threading/thread.h"
#include "content/browser/devtools/worker_devtools_manager.h"
#include "content/browser/renderer_host/render_widget_host_impl.h"
#include "content/browser/worker_host/worker_message_filter.h"
#include "content/browser/worker_host/worker_process_host.h"
#include "content/common/view_messages.h"
#include "content/common/worker_messages.h"
#include "content/public/browser/child_process_data.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/notification_types.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/render_widget_host_iterator.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/resource_context.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/worker_service_observer.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/process_type.h"
namespace content {
const int WorkerServiceImpl::kMaxWorkersWhenSeparate = 64;
const int WorkerServiceImpl::kMaxWorkersPerTabWhenSeparate = 16;
class WorkerPrioritySetter
: public NotificationObserver,
public base::RefCountedThreadSafe<WorkerPrioritySetter,
BrowserThread::DeleteOnUIThread> {
public:
WorkerPrioritySetter();
// Posts a task to the UI thread to register to receive notifications.
void Initialize();
// Invoked by WorkerServiceImpl when a worker process is created.
void NotifyWorkerProcessCreated();
private:
friend class base::RefCountedThreadSafe<WorkerPrioritySetter>;
friend struct BrowserThread::DeleteOnThread<BrowserThread::UI>;
friend class base::DeleteHelper<WorkerPrioritySetter>;
virtual ~WorkerPrioritySetter();
// Posts a task to perform a worker priority update.
void PostTaskToGatherAndUpdateWorkerPriorities();
// Gathers up a list of the visible tabs and then updates priorities for
// all the shared workers.
void GatherVisibleIDsAndUpdateWorkerPriorities();
// Registers as an observer to receive notifications about
// widgets being shown.
void RegisterObserver();
// Sets priorities for shared workers given a set of visible tabs (as a
// std::set of std::pair<render_process, render_view> ids.
void UpdateWorkerPrioritiesFromVisibleSet(
const std::set<std::pair<int, int> >* visible);
// Called to refresh worker priorities when focus changes between tabs.
void OnRenderWidgetVisibilityChanged(std::pair<int, int>);
// NotificationObserver implementation.
virtual void Observe(int type,
const NotificationSource& source,
const NotificationDetails& details) OVERRIDE;
NotificationRegistrar registrar_;
};
WorkerPrioritySetter::WorkerPrioritySetter() {
}
WorkerPrioritySetter::~WorkerPrioritySetter() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
}
void WorkerPrioritySetter::Initialize() {
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(&WorkerPrioritySetter::RegisterObserver, this));
}
void WorkerPrioritySetter::NotifyWorkerProcessCreated() {
PostTaskToGatherAndUpdateWorkerPriorities();
}
void WorkerPrioritySetter::PostTaskToGatherAndUpdateWorkerPriorities() {
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(
&WorkerPrioritySetter::GatherVisibleIDsAndUpdateWorkerPriorities,
this));
}
void WorkerPrioritySetter::GatherVisibleIDsAndUpdateWorkerPriorities() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
std::set<std::pair<int, int> >* visible_renderer_ids =
new std::set<std::pair<int, int> >();
// Gather up all the visible renderer process/view pairs
scoped_ptr<RenderWidgetHostIterator> widgets(
RenderWidgetHost::GetRenderWidgetHosts());
while (RenderWidgetHost* widget = widgets->GetNextHost()) {
if (widget->GetProcess()->VisibleWidgetCount() == 0)
continue;
RenderWidgetHostView* render_view = widget->GetView();
if (render_view && render_view->IsShowing()) {
visible_renderer_ids->insert(
std::pair<int, int>(widget->GetProcess()->GetID(),
widget->GetRoutingID()));
}
}
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&WorkerPrioritySetter::UpdateWorkerPrioritiesFromVisibleSet,
this, base::Owned(visible_renderer_ids)));
}
void WorkerPrioritySetter::UpdateWorkerPrioritiesFromVisibleSet(
const std::set<std::pair<int, int> >* visible_renderer_ids) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
if (!iter->process_launched())
continue;
bool throttle = true;
for (WorkerProcessHost::Instances::const_iterator instance =
iter->instances().begin(); instance != iter->instances().end();
++instance) {
// This code assumes one worker per process
WorkerProcessHost::Instances::const_iterator first_instance =
iter->instances().begin();
if (first_instance == iter->instances().end())
continue;
WorkerDocumentSet::DocumentInfoSet::const_iterator info =
first_instance->worker_document_set()->documents().begin();
for (; info != first_instance->worker_document_set()->documents().end();
++info) {
std::pair<int, int> id(
info->render_process_id(), info->render_view_id());
if (visible_renderer_ids->find(id) != visible_renderer_ids->end()) {
throttle = false;
break;
}
}
if (!throttle ) {
break;
}
}
iter->SetBackgrounded(throttle);
}
}
void WorkerPrioritySetter::OnRenderWidgetVisibilityChanged(
std::pair<int, int> id) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
std::set<std::pair<int, int> > visible_renderer_ids;
visible_renderer_ids.insert(id);
UpdateWorkerPrioritiesFromVisibleSet(&visible_renderer_ids);
}
void WorkerPrioritySetter::RegisterObserver() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
registrar_.Add(this, NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED,
NotificationService::AllBrowserContextsAndSources());
registrar_.Add(this, NOTIFICATION_RENDERER_PROCESS_CREATED,
NotificationService::AllBrowserContextsAndSources());
}
void WorkerPrioritySetter::Observe(int type,
const NotificationSource& source, const NotificationDetails& details) {
if (type == NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED) {
bool visible = *Details<bool>(details).ptr();
if (visible) {
int render_widget_id =
Source<RenderWidgetHost>(source).ptr()->GetRoutingID();
int render_process_pid =
Source<RenderWidgetHost>(source).ptr()->GetProcess()->GetID();
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&WorkerPrioritySetter::OnRenderWidgetVisibilityChanged,
this, std::pair<int, int>(render_process_pid, render_widget_id)));
}
}
else if (type == NOTIFICATION_RENDERER_PROCESS_CREATED) {
PostTaskToGatherAndUpdateWorkerPriorities();
}
}
WorkerService* WorkerService::GetInstance() {
return WorkerServiceImpl::GetInstance();
}
WorkerServiceImpl* WorkerServiceImpl::GetInstance() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
return Singleton<WorkerServiceImpl>::get();
}
WorkerServiceImpl::WorkerServiceImpl()
: priority_setter_(new WorkerPrioritySetter()),
next_worker_route_id_(0) {
priority_setter_->Initialize();
}
WorkerServiceImpl::~WorkerServiceImpl() {
// The observers in observers_ can't be used here because they might be
// gone already.
}
void WorkerServiceImpl::PerformTeardownForTesting() {
priority_setter_ = NULL;
}
void WorkerServiceImpl::OnWorkerMessageFilterClosing(
WorkerMessageFilter* filter) {
for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
iter->FilterShutdown(filter);
}
// See if that process had any queued workers.
for (WorkerProcessHost::Instances::iterator i = queued_workers_.begin();
i != queued_workers_.end();) {
i->RemoveFilters(filter);
if (i->NumFilters() == 0) {
i = queued_workers_.erase(i);
} else {
++i;
}
}
for (WorkerProcessHost::Instances::iterator i =
pending_shared_workers_.begin();
i != pending_shared_workers_.end(); ) {
i->RemoveFilters(filter);
if (i->NumFilters() == 0) {
i = pending_shared_workers_.erase(i);
} else {
++i;
}
}
// Also, see if that process had any pending shared workers.
for (WorkerProcessHost::Instances::iterator iter =
pending_shared_workers_.begin();
iter != pending_shared_workers_.end(); ) {
iter->worker_document_set()->RemoveAll(filter);
if (iter->worker_document_set()->IsEmpty()) {
iter = pending_shared_workers_.erase(iter);
} else {
++iter;
}
}
// Either a worker proceess has shut down, in which case we can start one of
// the queued workers, or a renderer has shut down, in which case it doesn't
// affect anything. We call this function in both scenarios because then we
// don't have to keep track which filters are from worker processes.
TryStartingQueuedWorker();
}
void WorkerServiceImpl::CreateWorker(
const ViewHostMsg_CreateWorker_Params& params,
int route_id,
WorkerMessageFilter* filter,
ResourceContext* resource_context,
const WorkerStoragePartition& partition) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
// Generate a unique route id for the browser-worker communication that's
// unique among all worker processes. That way when the worker process sends
// a wrapped IPC message through us, we know which WorkerProcessHost to give
// it to.
WorkerProcessHost::WorkerInstance instance(
params.url,
params.name,
next_worker_route_id(),
0,
params.script_resource_appcache_id,
resource_context,
partition);
instance.AddFilter(filter, route_id);
instance.worker_document_set()->Add(
filter, params.document_id, filter->render_process_id(),
params.render_view_route_id);
CreateWorkerFromInstance(instance);
}
void WorkerServiceImpl::LookupSharedWorker(
const ViewHostMsg_CreateWorker_Params& params,
int route_id,
WorkerMessageFilter* filter,
ResourceContext* resource_context,
const WorkerStoragePartition& partition,
bool* exists,
bool* url_mismatch) {
*exists = true;
WorkerProcessHost::WorkerInstance* instance = FindSharedWorkerInstance(
params.url, params.name, partition, resource_context);
if (!instance) {
// If no worker instance currently exists, we need to create a pending
// instance - this is to make sure that any subsequent lookups passing a
// mismatched URL get the appropriate url_mismatch error at lookup time.
// Having named shared workers was a Really Bad Idea due to details like
// this.
instance = CreatePendingInstance(params.url, params.name,
resource_context, partition);
*exists = false;
}
// Make sure the passed-in instance matches the URL - if not, return an
// error.
if (params.url != instance->url()) {
*url_mismatch = true;
*exists = false;
} else {
*url_mismatch = false;
// Add our route ID to the existing instance so we can send messages to it.
instance->AddFilter(filter, route_id);
// Add the passed filter/document_id to the worker instance.
// TODO(atwilson): This won't work if the message is from a worker process.
// We don't support that yet though (this message is only sent from
// renderers) but when we do, we'll need to add code to pass in the current
// worker's document set for nested workers.
instance->worker_document_set()->Add(
filter, params.document_id, filter->render_process_id(),
params.render_view_route_id);
}
}
void WorkerServiceImpl::ForwardToWorker(const IPC::Message& message,
WorkerMessageFilter* filter) {
for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
if (iter->FilterMessage(message, filter))
return;
}
// TODO(jabdelmalek): tell filter that callee is gone
}
void WorkerServiceImpl::DocumentDetached(unsigned long long document_id,
WorkerMessageFilter* filter) {
// Any associated shared workers can be shut down.
for (WorkerProcessHostIterator iter; !iter.Done(); ++iter)
iter->DocumentDetached(filter, document_id);
// Remove any queued shared workers for this document.
for (WorkerProcessHost::Instances::iterator iter = queued_workers_.begin();
iter != queued_workers_.end();) {
iter->worker_document_set()->Remove(filter, document_id);
if (iter->worker_document_set()->IsEmpty()) {
iter = queued_workers_.erase(iter);
continue;
}
++iter;
}
// Remove the document from any pending shared workers.
for (WorkerProcessHost::Instances::iterator iter =
pending_shared_workers_.begin();
iter != pending_shared_workers_.end(); ) {
iter->worker_document_set()->Remove(filter, document_id);
if (iter->worker_document_set()->IsEmpty()) {
iter = pending_shared_workers_.erase(iter);
} else {
++iter;
}
}
}
bool WorkerServiceImpl::CreateWorkerFromInstance(
WorkerProcessHost::WorkerInstance instance) {
if (!CanCreateWorkerProcess(instance)) {
queued_workers_.push_back(instance);
return true;
}
// Check to see if this shared worker is already running (two pages may have
// tried to start up the worker simultaneously).
// See if a worker with this name already exists.
WorkerProcessHost::WorkerInstance* existing_instance =
FindSharedWorkerInstance(
instance.url(), instance.name(), instance.partition(),
instance.resource_context());
WorkerProcessHost::WorkerInstance::FilterInfo filter_info =
instance.GetFilter();
// If this worker is already running, no need to create a new copy. Just
// inform the caller that the worker has been created.
if (existing_instance) {
// Walk the worker's filter list to see if this client is listed. If not,
// then it means that the worker started by the client already exited so
// we should not attach to this new one (http://crbug.com/29243).
if (!existing_instance->HasFilter(filter_info.first, filter_info.second))
return false;
filter_info.first->Send(new ViewMsg_WorkerCreated(filter_info.second));
return true;
}
// Look to see if there's a pending instance.
WorkerProcessHost::WorkerInstance* pending = FindPendingInstance(
instance.url(), instance.name(), instance.partition(),
instance.resource_context());
// If there's no instance *and* no pending instance (or there is a pending
// instance but it does not contain our filter info), then it means the
// worker started up and exited already. Log a warning because this should
// be a very rare occurrence and is probably a bug, but it *can* happen so
// handle it gracefully.
if (!pending ||
!pending->HasFilter(filter_info.first, filter_info.second)) {
DLOG(WARNING) << "Pending worker already exited";
return false;
}
// Assign the accumulated document set and filter list for this pending
// worker to the new instance.
DCHECK(!pending->worker_document_set()->IsEmpty());
instance.ShareDocumentSet(*pending);
for (WorkerProcessHost::WorkerInstance::FilterList::const_iterator i =
pending->filters().begin();
i != pending->filters().end(); ++i) {
instance.AddFilter(i->first, i->second);
}
RemovePendingInstances(instance.url(), instance.name(),
instance.partition(), instance.resource_context());
// Remove any queued instances of this worker and copy over the filter to
// this instance.
for (WorkerProcessHost::Instances::iterator iter = queued_workers_.begin();
iter != queued_workers_.end();) {
if (iter->Matches(instance.url(), instance.name(),
instance.partition(), instance.resource_context())) {
DCHECK(iter->NumFilters() == 1);
WorkerProcessHost::WorkerInstance::FilterInfo filter_info =
iter->GetFilter();
instance.AddFilter(filter_info.first, filter_info.second);
iter = queued_workers_.erase(iter);
} else {
++iter;
}
}
WorkerMessageFilter* first_filter = instance.filters().begin()->first;
WorkerProcessHost* worker = new WorkerProcessHost(
instance.resource_context(), instance.partition());
// TODO(atwilson): This won't work if the message is from a worker process.
// We don't support that yet though (this message is only sent from
// renderers) but when we do, we'll need to add code to pass in the current
// worker's document set for nested workers.
if (!worker->Init(first_filter->render_process_id())) {
delete worker;
return false;
}
worker->CreateWorker(instance);
FOR_EACH_OBSERVER(
WorkerServiceObserver, observers_,
WorkerCreated(instance.url(), instance.name(), worker->GetData().id,
instance.worker_route_id()));
WorkerDevToolsManager::GetInstance()->WorkerCreated(worker, instance);
return true;
}
bool WorkerServiceImpl::CanCreateWorkerProcess(
const WorkerProcessHost::WorkerInstance& instance) {
// Worker can be fired off if *any* parent has room.
const WorkerDocumentSet::DocumentInfoSet& parents =
instance.worker_document_set()->documents();
for (WorkerDocumentSet::DocumentInfoSet::const_iterator parent_iter =
parents.begin();
parent_iter != parents.end(); ++parent_iter) {
bool hit_total_worker_limit = false;
if (TabCanCreateWorkerProcess(parent_iter->render_process_id(),
parent_iter->render_view_id(),
&hit_total_worker_limit)) {
return true;
}
// Return false if already at the global worker limit (no need to continue
// checking parent tabs).
if (hit_total_worker_limit)
return false;
}
// If we've reached here, none of the parent tabs is allowed to create an
// instance.
return false;
}
bool WorkerServiceImpl::TabCanCreateWorkerProcess(
int render_process_id,
int render_view_id,
bool* hit_total_worker_limit) {
int total_workers = 0;
int workers_per_tab = 0;
*hit_total_worker_limit = false;
for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
for (WorkerProcessHost::Instances::const_iterator cur_instance =
iter->instances().begin();
cur_instance != iter->instances().end(); ++cur_instance) {
total_workers++;
if (total_workers >= kMaxWorkersWhenSeparate) {
*hit_total_worker_limit = true;
return false;
}
if (cur_instance->RendererIsParent(render_process_id, render_view_id)) {
workers_per_tab++;
if (workers_per_tab >= kMaxWorkersPerTabWhenSeparate)
return false;
}
}
}
return true;
}
void WorkerServiceImpl::TryStartingQueuedWorker() {
if (queued_workers_.empty())
return;
for (WorkerProcessHost::Instances::iterator i = queued_workers_.begin();
i != queued_workers_.end();) {
if (CanCreateWorkerProcess(*i)) {
WorkerProcessHost::WorkerInstance instance = *i;
queued_workers_.erase(i);
CreateWorkerFromInstance(instance);
// CreateWorkerFromInstance can modify the queued_workers_ list when it
// coalesces queued instances after starting a shared worker, so we
// have to rescan the list from the beginning (our iterator is now
// invalid). This is not a big deal as having any queued workers will be
// rare in practice so the list will be small.
i = queued_workers_.begin();
} else {
++i;
}
}
}
bool WorkerServiceImpl::GetRendererForWorker(int worker_process_id,
int* render_process_id,
int* render_view_id) const {
for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
if (iter.GetData().id != worker_process_id)
continue;
// This code assumes one worker per process, see function comment in header!
WorkerProcessHost::Instances::const_iterator first_instance =
iter->instances().begin();
if (first_instance == iter->instances().end())
return false;
WorkerDocumentSet::DocumentInfoSet::const_iterator info =
first_instance->worker_document_set()->documents().begin();
*render_process_id = info->render_process_id();
*render_view_id = info->render_view_id();
return true;
}
return false;
}
const WorkerProcessHost::WorkerInstance* WorkerServiceImpl::FindWorkerInstance(
int worker_process_id) {
for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
if (iter.GetData().id != worker_process_id)
continue;
WorkerProcessHost::Instances::const_iterator instance =
iter->instances().begin();
return instance == iter->instances().end() ? NULL : &*instance;
}
return NULL;
}
bool WorkerServiceImpl::TerminateWorker(int process_id, int route_id) {
for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
if (iter.GetData().id == process_id) {
iter->TerminateWorker(route_id);
return true;
}
}
return false;
}
std::vector<WorkerService::WorkerInfo> WorkerServiceImpl::GetWorkers() {
std::vector<WorkerService::WorkerInfo> results;
for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
const WorkerProcessHost::Instances& instances = (*iter)->instances();
for (WorkerProcessHost::Instances::const_iterator i = instances.begin();
i != instances.end(); ++i) {
WorkerService::WorkerInfo info;
info.url = i->url();
info.name = i->name();
info.route_id = i->worker_route_id();
info.process_id = iter.GetData().id;
info.handle = iter.GetData().handle;
results.push_back(info);
}
}
return results;
}
void WorkerServiceImpl::AddObserver(WorkerServiceObserver* observer) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
observers_.AddObserver(observer);
}
void WorkerServiceImpl::RemoveObserver(WorkerServiceObserver* observer) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
observers_.RemoveObserver(observer);
}
void WorkerServiceImpl::NotifyWorkerDestroyed(
WorkerProcessHost* process,
int worker_route_id) {
WorkerDevToolsManager::GetInstance()->WorkerDestroyed(
process, worker_route_id);
FOR_EACH_OBSERVER(WorkerServiceObserver, observers_,
WorkerDestroyed(process->GetData().id, worker_route_id));
}
void WorkerServiceImpl::NotifyWorkerProcessCreated() {
priority_setter_->NotifyWorkerProcessCreated();
}
WorkerProcessHost::WorkerInstance* WorkerServiceImpl::FindSharedWorkerInstance(
const GURL& url,
const base::string16& name,
const WorkerStoragePartition& partition,
ResourceContext* resource_context) {
for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
for (WorkerProcessHost::Instances::iterator instance_iter =
iter->mutable_instances().begin();
instance_iter != iter->mutable_instances().end();
++instance_iter) {
if (instance_iter->Matches(url, name, partition, resource_context))
return &(*instance_iter);
}
}
return NULL;
}
WorkerProcessHost::WorkerInstance* WorkerServiceImpl::FindPendingInstance(
const GURL& url,
const base::string16& name,
const WorkerStoragePartition& partition,
ResourceContext* resource_context) {
// Walk the pending instances looking for a matching pending worker.
for (WorkerProcessHost::Instances::iterator iter =
pending_shared_workers_.begin();
iter != pending_shared_workers_.end();
++iter) {
if (iter->Matches(url, name, partition, resource_context))
return &(*iter);
}
return NULL;
}
void WorkerServiceImpl::RemovePendingInstances(
const GURL& url,
const base::string16& name,
const WorkerStoragePartition& partition,
ResourceContext* resource_context) {
// Walk the pending instances looking for a matching pending worker.
for (WorkerProcessHost::Instances::iterator iter =
pending_shared_workers_.begin();
iter != pending_shared_workers_.end(); ) {
if (iter->Matches(url, name, partition, resource_context)) {
iter = pending_shared_workers_.erase(iter);
} else {
++iter;
}
}
}
WorkerProcessHost::WorkerInstance* WorkerServiceImpl::CreatePendingInstance(
const GURL& url,
const base::string16& name,
ResourceContext* resource_context,
const WorkerStoragePartition& partition) {
// Look for an existing pending shared worker.
WorkerProcessHost::WorkerInstance* instance =
FindPendingInstance(url, name, partition, resource_context);
if (instance)
return instance;
// No existing pending worker - create a new one.
WorkerProcessHost::WorkerInstance pending(
url, true, name, resource_context, partition);
pending_shared_workers_.push_back(pending);
return &pending_shared_workers_.back();
}
} // namespace content