diff options
author | jam@chromium.org <jam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-02-28 21:30:40 +0000 |
---|---|---|
committer | jam@chromium.org <jam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-02-28 21:30:40 +0000 |
commit | 1b8af731dccaaf3b040e32481aedf9e113f32854 (patch) | |
tree | 24e10ac7e200637d362171c9b3b51f158fb3c660 /content | |
parent | d45271ca30365429b189bc71217349cb2ea5f852 (diff) | |
download | chromium_src-1b8af731dccaaf3b040e32481aedf9e113f32854.zip chromium_src-1b8af731dccaaf3b040e32481aedf9e113f32854.tar.gz chromium_src-1b8af731dccaaf3b040e32481aedf9e113f32854.tar.bz2 |
Move worker_host code to content.
TBR=avi
Review URL: http://codereview.chromium.org/6592041
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@76263 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'content')
-rw-r--r-- | content/browser/renderer_host/resource_dispatcher_host.cc | 2 | ||||
-rw-r--r-- | content/browser/renderer_host/resource_request_details.cc | 2 | ||||
-rw-r--r-- | content/browser/worker_host/message_port_service.cc | 236 | ||||
-rw-r--r-- | content/browser/worker_host/message_port_service.h | 75 | ||||
-rw-r--r-- | content/browser/worker_host/worker_document_set.cc | 69 | ||||
-rw-r--r-- | content/browser/worker_host/worker_document_set.h | 90 | ||||
-rw-r--r-- | content/browser/worker_host/worker_message_filter.cc | 116 | ||||
-rw-r--r-- | content/browser/worker_host/worker_message_filter.h | 62 | ||||
-rw-r--r-- | content/browser/worker_host/worker_process_host.cc | 611 | ||||
-rw-r--r-- | content/browser/worker_host/worker_process_host.h | 187 | ||||
-rw-r--r-- | content/browser/worker_host/worker_service.cc | 558 | ||||
-rw-r--r-- | content/browser/worker_host/worker_service.h | 121 | ||||
-rw-r--r-- | content/content_browser.gypi | 10 |
13 files changed, 2137 insertions, 2 deletions
diff --git a/content/browser/renderer_host/resource_dispatcher_host.cc b/content/browser/renderer_host/resource_dispatcher_host.cc index 89440cb..a4238a1 100644 --- a/content/browser/renderer_host/resource_dispatcher_host.cc +++ b/content/browser/renderer_host/resource_dispatcher_host.cc @@ -37,7 +37,6 @@ #include "chrome/browser/ssl/ssl_client_auth_handler.h" #include "chrome/browser/ssl/ssl_manager.h" #include "chrome/browser/ui/login/login_prompt.h" -#include "chrome/browser/worker_host/worker_service.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/notification_service.h" #include "chrome/common/render_messages.h" @@ -61,6 +60,7 @@ #include "content/browser/renderer_host/resource_queue.h" #include "content/browser/renderer_host/resource_request_details.h" #include "content/browser/renderer_host/sync_resource_handler.h" +#include "content/browser/worker_host/worker_service.h" #include "net/base/auth.h" #include "net/base/cert_status_flags.h" #include "net/base/cookie_monster.h" diff --git a/content/browser/renderer_host/resource_request_details.cc b/content/browser/renderer_host/resource_request_details.cc index 5570181..151268c 100644 --- a/content/browser/renderer_host/resource_request_details.cc +++ b/content/browser/renderer_host/resource_request_details.cc @@ -6,7 +6,7 @@ #include "content/browser/renderer_host/resource_dispatcher_host.h" #include "content/browser/renderer_host/resource_dispatcher_host_request_info.h" -#include "chrome/browser/worker_host/worker_service.h" +#include "content/browser/worker_host/worker_service.h" ResourceRequestDetails::ResourceRequestDetails(const net::URLRequest* request, int cert_id) diff --git a/content/browser/worker_host/message_port_service.cc b/content/browser/worker_host/message_port_service.cc new file mode 100644 index 0000000..93b5f3d --- /dev/null +++ b/content/browser/worker_host/message_port_service.cc @@ -0,0 +1,236 @@ +// Copyright (c) 2009 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/message_port_service.h" + +#include "chrome/common/worker_messages.h" +#include "content/browser/worker_host/worker_message_filter.h" + +struct MessagePortService::MessagePort { + // |filter| and |route_id| are what we need to send messages to the port. + // |filter| is just a weak pointer since we get notified when its process has + // gone away and remove it. + WorkerMessageFilter* filter; + int route_id; + // A globally unique id for this message port. + int message_port_id; + // The globally unique id of the entangled message port. + int entangled_message_port_id; + // If true, all messages to this message port are queued and not delivered. + bool queue_messages; + QueuedMessages queued_messages; +}; + +MessagePortService* MessagePortService::GetInstance() { + return Singleton<MessagePortService>::get(); +} + +MessagePortService::MessagePortService() + : next_message_port_id_(0) { +} + +MessagePortService::~MessagePortService() { +} + +void MessagePortService::UpdateMessagePort( + int message_port_id, + WorkerMessageFilter* filter, + int routing_id) { + if (!message_ports_.count(message_port_id)) { + NOTREACHED(); + return; + } + + MessagePort& port = message_ports_[message_port_id]; + port.filter = filter; + port.route_id = routing_id; +} + +void MessagePortService::OnWorkerMessageFilterClosing( + WorkerMessageFilter* filter) { + // Check if the (possibly) crashed process had any message ports. + for (MessagePorts::iterator iter = message_ports_.begin(); + iter != message_ports_.end();) { + MessagePorts::iterator cur_item = iter++; + if (cur_item->second.filter == filter) { + Erase(cur_item->first); + } + } +} + +void MessagePortService::Create(int route_id, + WorkerMessageFilter* filter, + int* message_port_id) { + *message_port_id = ++next_message_port_id_; + + MessagePort port; + port.filter = filter; + port.route_id = route_id; + port.message_port_id = *message_port_id; + port.entangled_message_port_id = MSG_ROUTING_NONE; + port.queue_messages = false; + message_ports_[*message_port_id] = port; +} + +void MessagePortService::Destroy(int message_port_id) { + if (!message_ports_.count(message_port_id)) { + NOTREACHED(); + return; + } + + DCHECK(message_ports_[message_port_id].queued_messages.empty()); + Erase(message_port_id); +} + +void MessagePortService::Entangle(int local_message_port_id, + int remote_message_port_id) { + if (!message_ports_.count(local_message_port_id) || + !message_ports_.count(remote_message_port_id)) { + NOTREACHED(); + return; + } + + DCHECK(message_ports_[remote_message_port_id].entangled_message_port_id == + MSG_ROUTING_NONE); + message_ports_[remote_message_port_id].entangled_message_port_id = + local_message_port_id; +} + +void MessagePortService::PostMessage( + int sender_message_port_id, + const string16& message, + const std::vector<int>& sent_message_port_ids) { + if (!message_ports_.count(sender_message_port_id)) { + NOTREACHED(); + return; + } + + int entangled_message_port_id = + message_ports_[sender_message_port_id].entangled_message_port_id; + if (entangled_message_port_id == MSG_ROUTING_NONE) + return; // Process could have crashed. + + if (!message_ports_.count(entangled_message_port_id)) { + NOTREACHED(); + return; + } + + PostMessageTo(entangled_message_port_id, message, sent_message_port_ids); +} + +void MessagePortService::PostMessageTo( + int message_port_id, + const string16& message, + const std::vector<int>& sent_message_port_ids) { + if (!message_ports_.count(message_port_id)) { + NOTREACHED(); + return; + } + for (size_t i = 0; i < sent_message_port_ids.size(); ++i) { + if (!message_ports_.count(sent_message_port_ids[i])) { + NOTREACHED(); + return; + } + } + + MessagePort& entangled_port = message_ports_[message_port_id]; + + std::vector<MessagePort*> sent_ports(sent_message_port_ids.size()); + for (size_t i = 0; i < sent_message_port_ids.size(); ++i) { + sent_ports[i] = &message_ports_[sent_message_port_ids[i]]; + sent_ports[i]->queue_messages = true; + } + + if (entangled_port.queue_messages) { + entangled_port.queued_messages.push_back( + std::make_pair(message, sent_message_port_ids)); + return; + } + + if (!entangled_port.filter) { + NOTREACHED(); + return; + } + + // If a message port was sent around, the new location will need a routing + // id. Instead of having the created port send us a sync message to get it, + // send along with the message. + std::vector<int> new_routing_ids(sent_message_port_ids.size()); + for (size_t i = 0; i < sent_message_port_ids.size(); ++i) { + new_routing_ids[i] = entangled_port.filter->GetNextRoutingID(); + sent_ports[i]->filter = entangled_port.filter; + + // Update the entry for the sent port as it can be in a different process. + sent_ports[i]->route_id = new_routing_ids[i]; + } + + // Now send the message to the entangled port. + entangled_port.filter->Send(new WorkerProcessMsg_Message( + entangled_port.route_id, message, sent_message_port_ids, + new_routing_ids)); +} + +void MessagePortService::QueueMessages(int message_port_id) { + if (!message_ports_.count(message_port_id)) { + NOTREACHED(); + return; + } + + MessagePort& port = message_ports_[message_port_id]; + if (port.filter) { + port.filter->Send(new WorkerProcessMsg_MessagesQueued(port.route_id)); + port.queue_messages = true; + port.filter = NULL; + } +} + +void MessagePortService::SendQueuedMessages( + int message_port_id, + const QueuedMessages& queued_messages) { + if (!message_ports_.count(message_port_id)) { + NOTREACHED(); + return; + } + + // Send the queued messages to the port again. This time they'll reach the + // new location. + MessagePort& port = message_ports_[message_port_id]; + port.queue_messages = false; + port.queued_messages.insert(port.queued_messages.begin(), + queued_messages.begin(), + queued_messages.end()); + SendQueuedMessagesIfPossible(message_port_id); +} + +void MessagePortService::SendQueuedMessagesIfPossible(int message_port_id) { + if (!message_ports_.count(message_port_id)) { + NOTREACHED(); + return; + } + + MessagePort& port = message_ports_[message_port_id]; + if (port.queue_messages || !port.filter) + return; + + for (QueuedMessages::iterator iter = port.queued_messages.begin(); + iter != port.queued_messages.end(); ++iter) { + PostMessageTo(message_port_id, iter->first, iter->second); + } + port.queued_messages.clear(); +} + +void MessagePortService::Erase(int message_port_id) { + MessagePorts::iterator erase_item = message_ports_.find(message_port_id); + DCHECK(erase_item != message_ports_.end()); + + int entangled_id = erase_item->second.entangled_message_port_id; + if (entangled_id != MSG_ROUTING_NONE) { + // Do the disentanglement (and be paranoid about the other side existing + // just in case something unusual happened during entanglement). + if (message_ports_.count(entangled_id)) { + message_ports_[entangled_id].entangled_message_port_id = MSG_ROUTING_NONE; + } + } + message_ports_.erase(erase_item); +} diff --git a/content/browser/worker_host/message_port_service.h b/content/browser/worker_host/message_port_service.h new file mode 100644 index 0000000..0d5321a --- /dev/null +++ b/content/browser/worker_host/message_port_service.h @@ -0,0 +1,75 @@ +// Copyright (c) 2009 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. + +#ifndef CONTENT_BROWSER_WORKER_HOST_MESSAGE_PORT_SERVICE_H_ +#define CONTENT_BROWSER_WORKER_HOST_MESSAGE_PORT_SERVICE_H_ +#pragma once + +#include <map> +#include <utility> +#include <vector> + +#include "base/basictypes.h" +#include "base/singleton.h" +#include "base/string16.h" +#include "base/task.h" +#include "ipc/ipc_message.h" + +class WorkerMessageFilter; + +class MessagePortService { + public: + typedef std::vector<std::pair<string16, std::vector<int> > > QueuedMessages; + + // Returns the MessagePortService singleton. + static MessagePortService* GetInstance(); + + // These methods correspond to the message port related IPCs. + void Create(int route_id, WorkerMessageFilter* filter, int* message_port_id); + void Destroy(int message_port_id); + void Entangle(int local_message_port_id, int remote_message_port_id); + void PostMessage(int sender_message_port_id, + const string16& message, + const std::vector<int>& sent_message_port_ids); + void QueueMessages(int message_port_id); + void SendQueuedMessages(int message_port_id, + const QueuedMessages& queued_messages); + + // Updates the information needed to reach a message port when it's sent to a + // (possibly different) process. + void UpdateMessagePort( + int message_port_id, + WorkerMessageFilter* filter, + int routing_id); + + void OnWorkerMessageFilterClosing(WorkerMessageFilter* filter); + + // Attempts to send the queued messages for a message port. + void SendQueuedMessagesIfPossible(int message_port_id); + + private: + friend struct DefaultSingletonTraits<MessagePortService>; + + MessagePortService(); + ~MessagePortService(); + + void PostMessageTo(int message_port_id, + const string16& message, + const std::vector<int>& sent_message_port_ids); + + // Handles the details of removing a message port id. Before calling this, + // verify that the message port id exists. + void Erase(int message_port_id); + + struct MessagePort; + typedef std::map<int, MessagePort> MessagePorts; + MessagePorts message_ports_; + + // We need globally unique identifiers for each message port. + int next_message_port_id_; + + DISALLOW_COPY_AND_ASSIGN(MessagePortService); +}; + +#endif // CONTENT_BROWSER_WORKER_HOST_MESSAGE_PORT_SERVICE_H_ diff --git a/content/browser/worker_host/worker_document_set.cc b/content/browser/worker_host/worker_document_set.cc new file mode 100644 index 0000000..06a3907 --- /dev/null +++ b/content/browser/worker_host/worker_document_set.cc @@ -0,0 +1,69 @@ +// Copyright (c) 2009 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_document_set.h" + +#include "base/logging.h" + +WorkerDocumentSet::WorkerDocumentSet() { +} + +void WorkerDocumentSet::Add(WorkerMessageFilter* parent, + unsigned long long document_id, + int render_process_id, + int render_view_id) { + DocumentInfo info(parent, document_id, render_process_id, render_view_id); + document_set_.insert(info); +} + +bool WorkerDocumentSet::Contains(WorkerMessageFilter* parent, + unsigned long long document_id) const { + for (DocumentInfoSet::const_iterator i = document_set_.begin(); + i != document_set_.end(); ++i) { + if (i->filter() == parent && i->document_id() == document_id) + return true; + } + return false; +} + +void WorkerDocumentSet::Remove(WorkerMessageFilter* parent, + unsigned long long document_id) { + for (DocumentInfoSet::iterator i = document_set_.begin(); + i != document_set_.end(); i++) { + if (i->filter() == parent && i->document_id() == document_id) { + document_set_.erase(i); + break; + } + } + // Should not be duplicate copies in the document set. + DCHECK(!Contains(parent, document_id)); +} + +void WorkerDocumentSet::RemoveAll(WorkerMessageFilter* parent) { + for (DocumentInfoSet::iterator i = document_set_.begin(); + i != document_set_.end();) { + + // Note this idiom is somewhat tricky - calling document_set_.erase(iter) + // invalidates any iterators that point to the element being removed, so + // bump the iterator beyond the item being removed before calling erase. + if (i->filter() == parent) { + DocumentInfoSet::iterator item_to_delete = i++; + document_set_.erase(item_to_delete); + } else { + ++i; + } + } +} + +WorkerDocumentSet::DocumentInfo::DocumentInfo( + WorkerMessageFilter* filter, unsigned long long document_id, + int render_process_id, int render_view_id) + : filter_(filter), + document_id_(document_id), + render_process_id_(render_process_id), + render_view_id_(render_view_id) { +} + +WorkerDocumentSet::~WorkerDocumentSet() { +} diff --git a/content/browser/worker_host/worker_document_set.h b/content/browser/worker_host/worker_document_set.h new file mode 100644 index 0000000..45666c4 --- /dev/null +++ b/content/browser/worker_host/worker_document_set.h @@ -0,0 +1,90 @@ +// Copyright (c) 2009 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. + +#ifndef CONTENT_BROWSER_WORKER_HOST_WORKER_DOCUMENT_SET_H_ +#define CONTENT_BROWSER_WORKER_HOST_WORKER_DOCUMENT_SET_H_ +#pragma once + +#include <set> + +#include "base/basictypes.h" +#include "base/ref_counted.h" + +class WorkerMessageFilter; + +// The WorkerDocumentSet tracks all of the DOM documents associated with a +// set of workers. With nested workers, multiple workers can share the same +// WorkerDocumentSet (meaning that they all share the same lifetime, and will +// all exit when the last document in that set exits, per the WebWorkers spec). +class WorkerDocumentSet : public base::RefCounted<WorkerDocumentSet> { + public: + WorkerDocumentSet(); + + // The information we track for each document + class DocumentInfo { + public: + DocumentInfo(WorkerMessageFilter* filter, unsigned long long document_id, + int renderer_process_id, int render_view_id); + WorkerMessageFilter* filter() const { return filter_; } + unsigned long long document_id() const { return document_id_; } + int render_process_id() const { return render_process_id_; } + int render_view_id() const { return render_view_id_; } + + // Define operator "<", which is used to determine uniqueness within + // the set. + bool operator <(const DocumentInfo& other) const { + // Items are identical if the sender and document_id are identical, + // otherwise create an arbitrary stable ordering based on the document + // id/filter. + if (filter() == other.filter()) { + return document_id() < other.document_id(); + } else { + return reinterpret_cast<unsigned long long>(filter()) < + reinterpret_cast<unsigned long long>(other.filter()); + } + } + + private: + WorkerMessageFilter* filter_; + unsigned long long document_id_; + int render_process_id_; + int render_view_id_; + }; + + // Adds a document to a shared worker's document set. Also includes the + // associated render_process_id the document is associated with, to enable + // communication with the parent tab for things like http auth dialogs. + void Add(WorkerMessageFilter* parent, + unsigned long long document_id, + int render_process_id, + int render_view_id); + + // Checks to see if a document is in a shared worker's document set. + bool Contains(WorkerMessageFilter* parent, + unsigned long long document_id) const; + + // Removes a specific document from a worker's document set when that document + // is detached. + void Remove(WorkerMessageFilter* parent, unsigned long long document_id); + + // Invoked when a render process exits, to remove all associated documents + // from a worker's document set. + void RemoveAll(WorkerMessageFilter* parent); + + bool IsEmpty() const { return document_set_.empty(); } + + // Define a typedef for convenience here when declaring iterators, etc. + typedef std::set<DocumentInfo> DocumentInfoSet; + + // Returns the set of documents associated with this worker. + const DocumentInfoSet& documents() { return document_set_; } + + private: + friend class base::RefCounted<WorkerDocumentSet>; + virtual ~WorkerDocumentSet(); + + DocumentInfoSet document_set_; +}; + +#endif // CONTENT_BROWSER_WORKER_HOST_WORKER_DOCUMENT_SET_H_ diff --git a/content/browser/worker_host/worker_message_filter.cc b/content/browser/worker_host/worker_message_filter.cc new file mode 100644 index 0000000..abfef0a --- /dev/null +++ b/content/browser/worker_host/worker_message_filter.cc @@ -0,0 +1,116 @@ +// Copyright (c) 2010 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_message_filter.h" + +#include "chrome/browser/net/chrome_url_request_context.h" +#include "chrome/common/net/url_request_context_getter.h" +#include "chrome/common/render_messages.h" +#include "chrome/common/render_messages_params.h" +#include "chrome/common/worker_messages.h" +#include "content/browser/worker_host/message_port_service.h" +#include "content/browser/worker_host/worker_service.h" + +WorkerMessageFilter::WorkerMessageFilter( + int render_process_id, + URLRequestContextGetter* request_context, + ResourceDispatcherHost* resource_dispatcher_host, + CallbackWithReturnValue<int>::Type* next_routing_id) + : render_process_id_(render_process_id), + request_context_(request_context), + resource_dispatcher_host_(resource_dispatcher_host), + next_routing_id_(next_routing_id) { +} + +WorkerMessageFilter::~WorkerMessageFilter() { +} + +void WorkerMessageFilter::OnChannelClosing() { + BrowserMessageFilter::OnChannelClosing(); + + MessagePortService::GetInstance()->OnWorkerMessageFilterClosing(this); + WorkerService::GetInstance()->OnWorkerMessageFilterClosing(this); +} + +bool WorkerMessageFilter::OnMessageReceived(const IPC::Message& message, + bool* message_was_ok) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP_EX(WorkerMessageFilter, message, *message_was_ok) + // Worker messages. + // Only sent from renderer for now, until we have nested workers. + IPC_MESSAGE_HANDLER(ViewHostMsg_CreateWorker, OnCreateWorker) + // Only sent from renderer for now, until we have nested workers. + IPC_MESSAGE_HANDLER(ViewHostMsg_LookupSharedWorker, OnLookupSharedWorker) + IPC_MESSAGE_HANDLER(ViewHostMsg_CancelCreateDedicatedWorker, + OnCancelCreateDedicatedWorker) + IPC_MESSAGE_HANDLER(ViewHostMsg_ForwardToWorker, OnForwardToWorker) + // Only sent from renderer. + IPC_MESSAGE_HANDLER(ViewHostMsg_DocumentDetached, OnDocumentDetached) + // Message Port related messages. + IPC_MESSAGE_HANDLER(WorkerProcessHostMsg_CreateMessagePort, + OnCreateMessagePort) + IPC_MESSAGE_FORWARD(WorkerProcessHostMsg_DestroyMessagePort, + MessagePortService::GetInstance(), + MessagePortService::Destroy) + IPC_MESSAGE_FORWARD(WorkerProcessHostMsg_Entangle, + MessagePortService::GetInstance(), + MessagePortService::Entangle) + IPC_MESSAGE_FORWARD(WorkerProcessHostMsg_PostMessage, + MessagePortService::GetInstance(), + MessagePortService::PostMessage) + IPC_MESSAGE_FORWARD(WorkerProcessHostMsg_QueueMessages, + MessagePortService::GetInstance(), + MessagePortService::QueueMessages) + IPC_MESSAGE_FORWARD(WorkerProcessHostMsg_SendQueuedMessages, + MessagePortService::GetInstance(), + MessagePortService::SendQueuedMessages) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP_EX() + + return handled; +} + +int WorkerMessageFilter::GetNextRoutingID() { + return next_routing_id_->Run(); +} + +void WorkerMessageFilter::OnCreateWorker( + const ViewHostMsg_CreateWorker_Params& params, + int* route_id) { + *route_id = params.route_id != MSG_ROUTING_NONE ? + params.route_id : next_routing_id_->Run(); + WorkerService::GetInstance()->CreateWorker( + params, *route_id, this, request_context_); +} + +void WorkerMessageFilter::OnLookupSharedWorker( + const ViewHostMsg_CreateWorker_Params& params, + bool* exists, + int* route_id, + bool* url_error) { + *route_id = next_routing_id_->Run(); + + bool off_the_record = static_cast<ChromeURLRequestContext*>( + request_context_->GetURLRequestContext())->is_off_the_record(); + WorkerService::GetInstance()->LookupSharedWorker( + params, *route_id, this, off_the_record, exists, url_error); +} + +void WorkerMessageFilter::OnCancelCreateDedicatedWorker(int route_id) { + WorkerService::GetInstance()->CancelCreateDedicatedWorker(route_id, this); +} + +void WorkerMessageFilter::OnForwardToWorker(const IPC::Message& message) { + WorkerService::GetInstance()->ForwardToWorker(message, this); +} + +void WorkerMessageFilter::OnDocumentDetached(unsigned long long document_id) { + WorkerService::GetInstance()->DocumentDetached(document_id, this); +} + +void WorkerMessageFilter::OnCreateMessagePort(int *route_id, + int* message_port_id) { + *route_id = next_routing_id_->Run(); + MessagePortService::GetInstance()->Create(*route_id, this, message_port_id); +} diff --git a/content/browser/worker_host/worker_message_filter.h b/content/browser/worker_host/worker_message_filter.h new file mode 100644 index 0000000..f2de440 --- /dev/null +++ b/content/browser/worker_host/worker_message_filter.h @@ -0,0 +1,62 @@ +// Copyright (c) 2010 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. + +#ifndef CONTENT_BROWSER_WORKER_HOST_WORKER_MESSAGE_FILTER_H_ +#define CONTENT_BROWSER_WORKER_HOST_WORKER_MESSAGE_FILTER_H_ + +#include "base/callback.h" +#include "content/browser/browser_message_filter.h" + +class ResourceDispatcherHost; +class URLRequestContextGetter; +struct ViewHostMsg_CreateWorker_Params; + +class WorkerMessageFilter : public BrowserMessageFilter { + public: + // |next_routing_id| is owned by this object. It can be used up until + // OnChannelClosing. + WorkerMessageFilter( + int render_process_id, + URLRequestContextGetter* request_context, + ResourceDispatcherHost* resource_dispatcher_host, + CallbackWithReturnValue<int>::Type* next_routing_id); + + // BrowserMessageFilter implementation. + virtual void OnChannelClosing(); + virtual bool OnMessageReceived(const IPC::Message& message, + bool* message_was_ok); + + int GetNextRoutingID(); + int render_process_id() const { return render_process_id_; } + ResourceDispatcherHost* resource_dispatcher_host() const { + return resource_dispatcher_host_; + } + + private: + ~WorkerMessageFilter(); + + // Message handlers. + void OnCreateWorker(const ViewHostMsg_CreateWorker_Params& params, + int* route_id); + void OnLookupSharedWorker(const ViewHostMsg_CreateWorker_Params& params, + bool* exists, + int* route_id, + bool* url_error); + void OnCancelCreateDedicatedWorker(int route_id); + void OnForwardToWorker(const IPC::Message& message); + void OnDocumentDetached(unsigned long long document_id); + void OnCreateMessagePort(int* route_id, int* message_port_id); + + int render_process_id_; + scoped_refptr<URLRequestContextGetter> request_context_; + ResourceDispatcherHost* resource_dispatcher_host_; + + // This is guaranteed to be valid until OnChannelClosing is closed, and it's + // not used after. + scoped_ptr<CallbackWithReturnValue<int>::Type> next_routing_id_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(WorkerMessageFilter); +}; + +#endif // CONTENT_BROWSER_WORKER_HOST_WORKER_MESSAGE_FILTER_H_ diff --git a/content/browser/worker_host/worker_process_host.cc b/content/browser/worker_host/worker_process_host.cc new file mode 100644 index 0000000..0a38d50 --- /dev/null +++ b/content/browser/worker_host/worker_process_host.cc @@ -0,0 +1,611 @@ +// Copyright (c) 2011 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_process_host.h" + +#include <set> +#include <vector> + +#include "base/callback.h" +#include "base/command_line.h" +#include "base/message_loop.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/content_settings/host_content_settings_map.h" +#include "chrome/browser/metrics/user_metrics.h" +#include "chrome/browser/net/chrome_url_request_context.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/debug_flags.h" +#include "chrome/common/render_messages.h" +#include "chrome/common/render_messages_params.h" +#include "chrome/common/result_codes.h" +#include "chrome/common/worker_messages.h" +#include "content/browser/appcache/appcache_dispatcher_host.h" +#include "content/browser/browser_thread.h" +#include "content/browser/child_process_security_policy.h" +#include "content/browser/file_system/file_system_dispatcher_host.h" +#include "content/browser/mime_registry_message_filter.h" +#include "content/browser/renderer_host/blob_message_filter.h" +#include "content/browser/renderer_host/database_message_filter.h" +#include "content/browser/renderer_host/file_utilities_message_filter.h" +#include "content/browser/renderer_host/render_view_host.h" +#include "content/browser/renderer_host/render_view_host_delegate.h" +#include "content/browser/renderer_host/render_view_host_notification_task.h" +#include "content/browser/renderer_host/socket_stream_dispatcher_host.h" +#include "content/browser/worker_host/message_port_service.h" +#include "content/browser/worker_host/worker_message_filter.h" +#include "content/browser/worker_host/worker_service.h" +#include "net/base/mime_util.h" +#include "ipc/ipc_switches.h" +#include "net/base/registry_controlled_domain.h" +#include "webkit/fileapi/file_system_path_manager.h" + +namespace { + +// Helper class that we pass to SocketStreamDispatcherHost so that it can find +// the right net::URLRequestContext for a request. +class URLRequestContextOverride + : public ResourceMessageFilter::URLRequestContextOverride { + public: + explicit URLRequestContextOverride( + net::URLRequestContext* url_request_context) + : url_request_context_(url_request_context) { + } + virtual ~URLRequestContextOverride() {} + + virtual net::URLRequestContext* GetRequestContext( + const ViewHostMsg_Resource_Request& resource_request) { + return url_request_context_; + } + + private: + net::URLRequestContext* url_request_context_; +}; + +} // namespace + +// Notifies RenderViewHost that one or more worker objects crashed. +class WorkerCrashTask : public Task { + public: + WorkerCrashTask(int render_process_unique_id, int render_view_id) + : render_process_unique_id_(render_process_unique_id), + render_view_id_(render_view_id) { } + + void Run() { + RenderViewHost* host = + RenderViewHost::FromID(render_process_unique_id_, render_view_id_); + if (host) + host->delegate()->WorkerCrashed(); + } + + private: + int render_process_unique_id_; + int render_view_id_; +}; + +WorkerProcessHost::WorkerProcessHost( + ResourceDispatcherHost* resource_dispatcher_host, + URLRequestContextGetter* request_context) + : BrowserChildProcessHost(WORKER_PROCESS, resource_dispatcher_host), + request_context_(request_context) { +} + +WorkerProcessHost::~WorkerProcessHost() { + // If we crashed, tell the RenderViewHosts. + for (Instances::iterator i = instances_.begin(); i != instances_.end(); ++i) { + const WorkerDocumentSet::DocumentInfoSet& parents = + i->worker_document_set()->documents(); + for (WorkerDocumentSet::DocumentInfoSet::const_iterator parent_iter = + parents.begin(); parent_iter != parents.end(); ++parent_iter) { + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + new WorkerCrashTask(parent_iter->render_process_id(), + parent_iter->render_view_id())); + } + } + + ChildProcessSecurityPolicy::GetInstance()->Remove(id()); +} + +bool WorkerProcessHost::Init(int render_process_id) { + if (!CreateChannel()) + return false; + + FilePath exe_path = GetChildPath(true); + if (exe_path.empty()) + return false; + + CommandLine* cmd_line = new CommandLine(exe_path); + cmd_line->AppendSwitchASCII(switches::kProcessType, switches::kWorkerProcess); + cmd_line->AppendSwitchASCII(switches::kProcessChannelID, channel_id()); + SetCrashReporterCommandLine(cmd_line); + + static const char* const kSwitchNames[] = { + switches::kWebWorkerShareProcesses, + switches::kDisableApplicationCache, + switches::kDisableDatabases, + switches::kEnableLogging, + switches::kLoggingLevel, + switches::kDisableWebSockets, +#if defined(OS_WIN) + switches::kDisableDesktopNotifications, +#endif + switches::kDisableFileSystem, + }; + cmd_line->CopySwitchesFrom(*CommandLine::ForCurrentProcess(), kSwitchNames, + arraysize(kSwitchNames)); + +#if defined(OS_POSIX) + bool use_zygote = true; + + if (CommandLine::ForCurrentProcess()->HasSwitch( + switches::kWaitForDebuggerChildren)) { + // Look to pass-on the kWaitForDebugger flag. + std::string value = CommandLine::ForCurrentProcess()->GetSwitchValueASCII( + switches::kWaitForDebuggerChildren); + if (value.empty() || value == switches::kWorkerProcess) { + cmd_line->AppendSwitch(switches::kWaitForDebugger); + use_zygote = false; + } + } + + if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kDebugChildren)) { + // Look to pass-on the kDebugOnStart flag. + std::string value = CommandLine::ForCurrentProcess()->GetSwitchValueASCII( + switches::kDebugChildren); + if (value.empty() || value == switches::kWorkerProcess) { + // launches a new xterm, and runs the worker process in gdb, reading + // optional commands from gdb_chrome file in the working directory. + cmd_line->PrependWrapper("xterm -e gdb -x gdb_chrome --args"); + use_zygote = false; + } + } +#endif + + Launch( +#if defined(OS_WIN) + FilePath(), +#elif defined(OS_POSIX) + use_zygote, + base::environment_vector(), +#endif + cmd_line); + + ChildProcessSecurityPolicy::GetInstance()->Add(id()); + if (!CommandLine::ForCurrentProcess()->HasSwitch( + switches::kDisableFileSystem)) { + // Grant most file permissions to this worker. + // PLATFORM_FILE_TEMPORARY, PLATFORM_FILE_HIDDEN and + // PLATFORM_FILE_DELETE_ON_CLOSE are not granted, because no existing API + // requests them. + ChildProcessSecurityPolicy::GetInstance()->GrantPermissionsForFile( + id(), + GetChromeURLRequestContext()->file_system_context()-> + path_manager()->base_path(), + base::PLATFORM_FILE_OPEN | + base::PLATFORM_FILE_CREATE | + base::PLATFORM_FILE_OPEN_ALWAYS | + base::PLATFORM_FILE_CREATE_ALWAYS | + base::PLATFORM_FILE_READ | + base::PLATFORM_FILE_WRITE | + base::PLATFORM_FILE_EXCLUSIVE_READ | + base::PLATFORM_FILE_EXCLUSIVE_WRITE | + base::PLATFORM_FILE_ASYNC | + base::PLATFORM_FILE_TRUNCATE | + base::PLATFORM_FILE_WRITE_ATTRIBUTES); + } + + CreateMessageFilters(render_process_id); + + return true; +} + +void WorkerProcessHost::CreateMessageFilters(int render_process_id) { + ChromeURLRequestContext* chrome_url_context = GetChromeURLRequestContext(); + + worker_message_filter_= new WorkerMessageFilter( + render_process_id, + request_context_, + resource_dispatcher_host(), + NewCallbackWithReturnValue( + WorkerService::GetInstance(), &WorkerService::next_worker_route_id)); + AddFilter(worker_message_filter_); + AddFilter(new AppCacheDispatcherHost(chrome_url_context, id())); + AddFilter(new FileSystemDispatcherHost(chrome_url_context)); + AddFilter(new FileUtilitiesMessageFilter(id())); + AddFilter( + new BlobMessageFilter(id(), chrome_url_context->blob_storage_context())); + AddFilter(new MimeRegistryMessageFilter()); + AddFilter(new DatabaseMessageFilter( + chrome_url_context->database_tracker(), + chrome_url_context->host_content_settings_map())); + + SocketStreamDispatcherHost* socket_stream_dispatcher_host = + new SocketStreamDispatcherHost(); + socket_stream_dispatcher_host->set_url_request_context_override( + new URLRequestContextOverride(chrome_url_context)); + AddFilter(socket_stream_dispatcher_host); +} + +void WorkerProcessHost::CreateWorker(const WorkerInstance& instance) { + ChildProcessSecurityPolicy::GetInstance()->GrantRequestURL( + id(), instance.url()); + + instances_.push_back(instance); + + WorkerProcessMsg_CreateWorker_Params params; + params.url = instance.url(); + params.is_shared = instance.shared(); + params.name = instance.name(); + params.route_id = instance.worker_route_id(); + params.creator_process_id = instance.parent_process_id(); + params.creator_appcache_host_id = instance.parent_appcache_host_id(); + params.shared_worker_appcache_id = instance.main_resource_appcache_id(); + Send(new WorkerProcessMsg_CreateWorker(params)); + + UpdateTitle(); + + // Walk all pending filters and let them know the worker has been created + // (could be more than one in the case where we had to queue up worker + // creation because the worker process limit was reached). + for (WorkerInstance::FilterList::const_iterator i = + instance.filters().begin(); + i != instance.filters().end(); ++i) { + i->first->Send(new ViewMsg_WorkerCreated(i->second)); + } +} + +bool WorkerProcessHost::FilterMessage(const IPC::Message& message, + WorkerMessageFilter* filter) { + for (Instances::iterator i = instances_.begin(); i != instances_.end(); ++i) { + if (!i->closed() && i->HasFilter(filter, message.routing_id())) { + RelayMessage(message, worker_message_filter_, i->worker_route_id()); + return true; + } + } + + return false; +} + +void WorkerProcessHost::OnProcessLaunched() { +} + +bool WorkerProcessHost::OnMessageReceived(const IPC::Message& message) { + bool msg_is_ok = true; + bool handled = true; + IPC_BEGIN_MESSAGE_MAP_EX(WorkerProcessHost, message, msg_is_ok) + IPC_MESSAGE_HANDLER(WorkerHostMsg_WorkerContextClosed, + OnWorkerContextClosed) + IPC_MESSAGE_HANDLER(WorkerProcessHostMsg_AllowDatabase, OnAllowDatabase) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP_EX() + + if (!msg_is_ok) { + NOTREACHED(); + UserMetrics::RecordAction(UserMetricsAction("BadMessageTerminate_WPH")); + base::KillProcess(handle(), ResultCodes::KILLED_BAD_MESSAGE, false); + } + + if (handled) + return true; + + for (Instances::iterator i = instances_.begin(); i != instances_.end(); ++i) { + if (i->worker_route_id() == message.routing_id()) { + if (!i->shared()) { + // Don't relay messages from shared workers (all communication is via + // the message port). + WorkerInstance::FilterInfo info = i->GetFilter(); + RelayMessage(message, info.first, info.second); + } + + if (message.type() == WorkerHostMsg_WorkerContextDestroyed::ID) { + instances_.erase(i); + UpdateTitle(); + } + return true; + } + } + return false; +} + +// Sent to notify the browser process when a worker context invokes close(), so +// no new connections are sent to shared workers. +void WorkerProcessHost::OnWorkerContextClosed(int worker_route_id) { + for (Instances::iterator i = instances_.begin(); i != instances_.end(); ++i) { + if (i->worker_route_id() == worker_route_id) { + // Set the closed flag - this will stop any further messages from + // being sent to the worker (messages can still be sent from the worker, + // for exception reporting, etc). + i->set_closed(true); + break; + } + } +} + +void WorkerProcessHost::OnAllowDatabase(int worker_route_id, + const GURL& url, + const string16& name, + const string16& display_name, + unsigned long estimated_size, + bool* result) { + ContentSetting content_setting = GetChromeURLRequestContext()-> + host_content_settings_map()->GetContentSetting( + url, CONTENT_SETTINGS_TYPE_COOKIES, ""); + + *result = content_setting != CONTENT_SETTING_BLOCK; + + // Find the worker instance and forward the message to all attached documents. + for (Instances::iterator i = instances_.begin(); i != instances_.end(); ++i) { + if (i->worker_route_id() != worker_route_id) + continue; + const WorkerDocumentSet::DocumentInfoSet& documents = + i->worker_document_set()->documents(); + for (WorkerDocumentSet::DocumentInfoSet::const_iterator doc = + documents.begin(); doc != documents.end(); ++doc) { + CallRenderViewHostContentSettingsDelegate( + doc->render_process_id(), doc->render_view_id(), + &RenderViewHostDelegate::ContentSettings::OnWebDatabaseAccessed, + url, name, display_name, estimated_size, !*result); + } + break; + } +} + +void WorkerProcessHost::RelayMessage( + const IPC::Message& message, + WorkerMessageFilter* filter, + int route_id) { + if (message.type() == WorkerMsg_PostMessage::ID) { + // We want to send the receiver a routing id for the new channel, so + // crack the message first. + string16 msg; + std::vector<int> sent_message_port_ids; + std::vector<int> new_routing_ids; + if (!WorkerMsg_PostMessage::Read( + &message, &msg, &sent_message_port_ids, &new_routing_ids)) { + return; + } + if (sent_message_port_ids.size() != new_routing_ids.size()) + return; + + for (size_t i = 0; i < sent_message_port_ids.size(); ++i) { + new_routing_ids[i] = filter->GetNextRoutingID(); + MessagePortService::GetInstance()->UpdateMessagePort( + sent_message_port_ids[i], filter, new_routing_ids[i]); + } + + filter->Send(new WorkerMsg_PostMessage( + route_id, msg, sent_message_port_ids, new_routing_ids)); + + // Send any queued messages to the sent message ports. We can only do this + // after sending the above message, since it's the one that sets up the + // message port route which the queued messages are sent to. + for (size_t i = 0; i < sent_message_port_ids.size(); ++i) { + MessagePortService::GetInstance()-> + SendQueuedMessagesIfPossible(sent_message_port_ids[i]); + } + } else if (message.type() == WorkerMsg_Connect::ID) { + // Crack the SharedWorker Connect message to setup routing for the port. + int sent_message_port_id; + int new_routing_id; + if (!WorkerMsg_Connect::Read( + &message, &sent_message_port_id, &new_routing_id)) { + return; + } + new_routing_id = filter->GetNextRoutingID(); + MessagePortService::GetInstance()->UpdateMessagePort( + sent_message_port_id, filter, new_routing_id); + + // Resend the message with the new routing id. + filter->Send(new WorkerMsg_Connect( + route_id, sent_message_port_id, new_routing_id)); + + // Send any queued messages for the sent port. + MessagePortService::GetInstance()->SendQueuedMessagesIfPossible( + sent_message_port_id); + } else { + IPC::Message* new_message = new IPC::Message(message); + new_message->set_routing_id(route_id); + filter->Send(new_message); + return; + } +} + +void WorkerProcessHost::FilterShutdown(WorkerMessageFilter* filter) { + for (Instances::iterator i = instances_.begin(); i != instances_.end();) { + bool shutdown = false; + i->RemoveFilters(filter); + if (i->shared()) { + i->worker_document_set()->RemoveAll(filter); + if (i->worker_document_set()->IsEmpty()) { + shutdown = true; + } + } else if (i->NumFilters() == 0) { + shutdown = true; + } + if (shutdown) { + Send(new WorkerMsg_TerminateWorkerContext(i->worker_route_id())); + i = instances_.erase(i); + } else { + ++i; + } + } +} + +bool WorkerProcessHost::CanShutdown() { + return instances_.empty(); +} + +void WorkerProcessHost::UpdateTitle() { + std::set<std::string> titles; + for (Instances::iterator i = instances_.begin(); i != instances_.end(); ++i) { + std::string title = + net::RegistryControlledDomainService::GetDomainAndRegistry(i->url()); + // Use the host name if the domain is empty, i.e. localhost or IP address. + if (title.empty()) + title = i->url().host(); + + // Check if it's an extension-created worker, in which case we want to use + // the name of the extension. + std::string extension_name = GetChromeURLRequestContext()-> + extension_info_map()->GetNameForExtension(title); + if (!extension_name.empty()) { + titles.insert(extension_name); + continue; + } + + // If the host name is empty, i.e. file url, use the path. + if (title.empty()) + title = i->url().path(); + titles.insert(title); + } + + std::string display_title; + for (std::set<std::string>::iterator i = titles.begin(); + i != titles.end(); ++i) { + if (!display_title.empty()) + display_title += ", "; + display_title += *i; + } + + set_name(ASCIIToWide(display_title)); +} + +ChromeURLRequestContext* WorkerProcessHost::GetChromeURLRequestContext() { + return static_cast<ChromeURLRequestContext*>( + request_context_->GetURLRequestContext()); +} + +void WorkerProcessHost::DocumentDetached(WorkerMessageFilter* filter, + unsigned long long document_id) { + // Walk all instances and remove the document from their document set. + for (Instances::iterator i = instances_.begin(); i != instances_.end();) { + if (!i->shared()) { + ++i; + } else { + i->worker_document_set()->Remove(filter, document_id); + if (i->worker_document_set()->IsEmpty()) { + // This worker has no more associated documents - shut it down. + Send(new WorkerMsg_TerminateWorkerContext(i->worker_route_id())); + i = instances_.erase(i); + } else { + ++i; + } + } + } +} + +WorkerProcessHost::WorkerInstance::WorkerInstance( + const GURL& url, + bool shared, + bool off_the_record, + const string16& name, + int worker_route_id, + int parent_process_id, + int parent_appcache_host_id, + int64 main_resource_appcache_id, + URLRequestContextGetter* request_context) + : url_(url), + shared_(shared), + off_the_record_(off_the_record), + closed_(false), + name_(name), + worker_route_id_(worker_route_id), + parent_process_id_(parent_process_id), + parent_appcache_host_id_(parent_appcache_host_id), + main_resource_appcache_id_(main_resource_appcache_id), + request_context_(request_context), + worker_document_set_(new WorkerDocumentSet()) { +} + +WorkerProcessHost::WorkerInstance::~WorkerInstance() { +} + +// Compares an instance based on the algorithm in the WebWorkers spec - an +// instance matches if the origins of the URLs match, and: +// a) the names are non-empty and equal +// -or- +// b) the names are both empty, and the urls are equal +bool WorkerProcessHost::WorkerInstance::Matches( + const GURL& match_url, const string16& match_name, + bool off_the_record) const { + // Only match open shared workers. + if (!shared_ || closed_) + return false; + + // Incognito workers don't match non-incognito workers. + if (off_the_record_ != off_the_record) + return false; + + if (url_.GetOrigin() != match_url.GetOrigin()) + return false; + + if (name_.empty() && match_name.empty()) + return url_ == match_url; + + return name_ == match_name; +} + +void WorkerProcessHost::WorkerInstance::AddFilter(WorkerMessageFilter* filter, + int route_id) { + if (!HasFilter(filter, route_id)) { + FilterInfo info(filter, route_id); + filters_.push_back(info); + } + // Only shared workers can have more than one associated filter. + DCHECK(shared_ || filters_.size() == 1); +} + +void WorkerProcessHost::WorkerInstance::RemoveFilter( + WorkerMessageFilter* filter, int route_id) { + for (FilterList::iterator i = filters_.begin(); i != filters_.end();) { + if (i->first == filter && i->second == route_id) + i = filters_.erase(i); + else + ++i; + } + // Should not be duplicate copies in the filter set. + DCHECK(!HasFilter(filter, route_id)); +} + +void WorkerProcessHost::WorkerInstance::RemoveFilters( + WorkerMessageFilter* filter) { + for (FilterList::iterator i = filters_.begin(); i != filters_.end();) { + if (i->first == filter) + i = filters_.erase(i); + else + ++i; + } +} + +bool WorkerProcessHost::WorkerInstance::HasFilter( + WorkerMessageFilter* filter, int route_id) const { + for (FilterList::const_iterator i = filters_.begin(); i != filters_.end(); + ++i) { + if (i->first == filter && i->second == route_id) + return true; + } + return false; +} + +bool WorkerProcessHost::WorkerInstance::RendererIsParent( + int render_process_id, int render_view_id) const { + const WorkerDocumentSet::DocumentInfoSet& parents = + worker_document_set()->documents(); + for (WorkerDocumentSet::DocumentInfoSet::const_iterator parent_iter = + parents.begin(); + parent_iter != parents.end(); ++parent_iter) { + if (parent_iter->render_process_id() == render_process_id && + parent_iter->render_view_id() == render_view_id) { + return true; + } + } + return false; +} + +WorkerProcessHost::WorkerInstance::FilterInfo +WorkerProcessHost::WorkerInstance::GetFilter() const { + DCHECK(NumFilters() == 1); + return *filters_.begin(); +} diff --git a/content/browser/worker_host/worker_process_host.h b/content/browser/worker_host/worker_process_host.h new file mode 100644 index 0000000..af446f0 --- /dev/null +++ b/content/browser/worker_host/worker_process_host.h @@ -0,0 +1,187 @@ +// Copyright (c) 2011 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. + +#ifndef CONTENT_BROWSER_WORKER_HOST_WORKER_PROCESS_HOST_H_ +#define CONTENT_BROWSER_WORKER_HOST_WORKER_PROCESS_HOST_H_ +#pragma once + +#include <list> + +#include "base/basictypes.h" +#include "base/file_path.h" +#include "chrome/browser/net/chrome_url_request_context.h" +#include "content/browser/browser_child_process_host.h" +#include "content/browser/worker_host/worker_document_set.h" +#include "googleurl/src/gurl.h" + +class URLRequestContextGetter; + +// The WorkerProcessHost is the interface that represents the browser side of +// the browser <-> worker communication channel. There will be one +// WorkerProcessHost per worker process. Currently each worker runs in its own +// process, but that may change. However, we do assume (by storing a +// net::URLRequestContext) that a WorkerProcessHost serves a single Profile. +class WorkerProcessHost : public BrowserChildProcessHost { + public: + + // Contains information about each worker instance, needed to forward messages + // between the renderer and worker processes. + class WorkerInstance { + public: + WorkerInstance(const GURL& url, + bool shared, + bool off_the_record, + const string16& name, + int worker_route_id, + int parent_process_id, + int parent_appcache_host_id, + int64 main_resource_appcache_id, + URLRequestContextGetter* request_context); + ~WorkerInstance(); + + // Unique identifier for a worker client. + typedef std::pair<WorkerMessageFilter*, int> FilterInfo; + + // APIs to manage the filter list for a given instance. + void AddFilter(WorkerMessageFilter* filter, int route_id); + void RemoveFilter(WorkerMessageFilter* filter, int route_id); + void RemoveFilters(WorkerMessageFilter* filter); + bool HasFilter(WorkerMessageFilter* filter, int route_id) const; + bool RendererIsParent(int render_process_id, int render_view_id) const; + int NumFilters() const { return filters_.size(); } + // Returns the single filter (must only be one). + FilterInfo GetFilter() const; + + typedef std::list<FilterInfo> FilterList; + const FilterList& filters() const { return filters_; } + + // Checks if this WorkerInstance matches the passed url/name params + // (per the comparison algorithm in the WebWorkers spec). This API only + // applies to shared workers. + bool Matches( + const GURL& url, const string16& name, bool off_the_record) const; + + // Shares the passed instance's WorkerDocumentSet with this instance. This + // instance's current WorkerDocumentSet is dereferenced (and freed if this + // is the only reference) as a result. + void ShareDocumentSet(const WorkerInstance& instance) { + worker_document_set_ = instance.worker_document_set_; + }; + + // Accessors + bool shared() const { return shared_; } + bool off_the_record() const { return off_the_record_; } + bool closed() const { return closed_; } + void set_closed(bool closed) { closed_ = closed; } + const GURL& url() const { return url_; } + const string16 name() const { return name_; } + int worker_route_id() const { return worker_route_id_; } + int parent_process_id() const { return parent_process_id_; } + int parent_appcache_host_id() const { return parent_appcache_host_id_; } + int64 main_resource_appcache_id() const { + return main_resource_appcache_id_; + } + WorkerDocumentSet* worker_document_set() const { + return worker_document_set_; + } + URLRequestContextGetter* request_context() const { + return request_context_; + } + + private: + // Set of all filters (clients) associated with this worker. + GURL url_; + bool shared_; + bool off_the_record_; + bool closed_; + string16 name_; + int worker_route_id_; + int parent_process_id_; + int parent_appcache_host_id_; + int64 main_resource_appcache_id_; + scoped_refptr<URLRequestContextGetter> request_context_; + FilterList filters_; + scoped_refptr<WorkerDocumentSet> worker_document_set_; + }; + + WorkerProcessHost( + ResourceDispatcherHost* resource_dispatcher_host, + URLRequestContextGetter* request_context); + ~WorkerProcessHost(); + + // Starts the process. Returns true iff it succeeded. + // |render_process_id| is the renderer process responsible for starting this + // worker. + bool Init(int render_process_id); + + // Creates a worker object in the process. + void CreateWorker(const WorkerInstance& instance); + + // Returns true iff the given message from a renderer process was forwarded to + // the worker. + bool FilterMessage(const IPC::Message& message, WorkerMessageFilter* filter); + + void FilterShutdown(WorkerMessageFilter* filter); + + // Shuts down any shared workers that are no longer referenced by active + // documents. + void DocumentDetached(WorkerMessageFilter* filter, + unsigned long long document_id); + + URLRequestContextGetter* request_context() const { + return request_context_; + } + + protected: + friend class WorkerService; + + typedef std::list<WorkerInstance> Instances; + const Instances& instances() const { return instances_; } + Instances& mutable_instances() { return instances_; } + + private: + // Called when the process has been launched successfully. + virtual void OnProcessLaunched(); + + // Creates and adds the message filters. + void CreateMessageFilters(int render_process_id); + + // IPC::Channel::Listener implementation: + // Called when a message arrives from the worker process. + virtual bool OnMessageReceived(const IPC::Message& message); + + void OnWorkerContextClosed(int worker_route_id); + void OnAllowDatabase(int worker_route_id, + const GURL& url, + const string16& name, + const string16& display_name, + unsigned long estimated_size, + bool* result); + + // Relays a message to the given endpoint. Takes care of parsing the message + // if it contains a message port and sending it a valid route id. + static void RelayMessage(const IPC::Message& message, + WorkerMessageFilter* filter, + int route_id); + + virtual bool CanShutdown(); + + // Updates the title shown in the task manager. + void UpdateTitle(); + + ChromeURLRequestContext* GetChromeURLRequestContext(); + + Instances instances_; + + scoped_refptr<URLRequestContextGetter> request_context_; + + // A reference to the filter associated with this worker process. We need to + // keep this around since we'll use it when forward messages to the worker + // process. + scoped_refptr<WorkerMessageFilter> worker_message_filter_; + + DISALLOW_COPY_AND_ASSIGN(WorkerProcessHost); +}; + +#endif // CONTENT_BROWSER_WORKER_HOST_WORKER_PROCESS_HOST_H_ diff --git a/content/browser/worker_host/worker_service.cc b/content/browser/worker_host/worker_service.cc new file mode 100644 index 0000000..ac4c979 --- /dev/null +++ b/content/browser/worker_host/worker_service.cc @@ -0,0 +1,558 @@ +// Copyright (c) 2009 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.h" + +#include <string> + +#include "base/command_line.h" +#include "base/sys_info.h" +#include "base/threading/thread.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/render_messages.h" +#include "chrome/common/render_messages_params.h" +#include "chrome/common/worker_messages.h" +#include "content/browser/worker_host/worker_message_filter.h" +#include "content/browser/worker_host/worker_process_host.h" +#include "net/base/registry_controlled_domain.h" + +const int WorkerService::kMaxWorkerProcessesWhenSharing = 10; +const int WorkerService::kMaxWorkersWhenSeparate = 64; +const int WorkerService::kMaxWorkersPerTabWhenSeparate = 16; + +WorkerService* WorkerService::GetInstance() { + return Singleton<WorkerService>::get(); +} + +WorkerService::WorkerService() : next_worker_route_id_(0) { +} + +WorkerService::~WorkerService() { +} + +void WorkerService::OnWorkerMessageFilterClosing(WorkerMessageFilter* filter) { + for (BrowserChildProcessHost::Iterator iter(ChildProcessInfo::WORKER_PROCESS); + !iter.Done(); ++iter) { + WorkerProcessHost* worker = static_cast<WorkerProcessHost*>(*iter); + worker->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; + } + } + + // 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 WorkerService::CreateWorker(const ViewHostMsg_CreateWorker_Params& params, + int route_id, + WorkerMessageFilter* filter, + URLRequestContextGetter* request_context) { + + ChromeURLRequestContext* context = static_cast<ChromeURLRequestContext*>( + request_context->GetURLRequestContext()); + + // 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.is_shared, + context->is_off_the_record(), + params.name, + next_worker_route_id(), + params.is_shared ? 0 : filter->render_process_id(), + params.is_shared ? 0 : params.parent_appcache_host_id, + params.is_shared ? params.script_resource_appcache_id : 0, + request_context); + 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 WorkerService::LookupSharedWorker( + const ViewHostMsg_CreateWorker_Params& params, + int route_id, + WorkerMessageFilter* filter, + bool off_the_record, + bool* exists, + bool* url_mismatch) { + + *exists = true; + WorkerProcessHost::WorkerInstance* instance = FindSharedWorkerInstance( + params.url, params.name, off_the_record); + + 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, off_the_record); + *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 WorkerService::CancelCreateDedicatedWorker( + int route_id, + WorkerMessageFilter* filter) { + for (WorkerProcessHost::Instances::iterator i = queued_workers_.begin(); + i != queued_workers_.end(); ++i) { + if (i->HasFilter(filter, route_id)) { + DCHECK(!i->shared()); + queued_workers_.erase(i); + return; + } + } + + // There could be a race condition where the WebWorkerProxy told us to cancel + // the worker right as we sent it a message say it's been created. Look at + // the running workers. + for (BrowserChildProcessHost::Iterator iter(ChildProcessInfo::WORKER_PROCESS); + !iter.Done(); ++iter) { + WorkerProcessHost* worker = static_cast<WorkerProcessHost*>(*iter); + for (WorkerProcessHost::Instances::const_iterator instance = + worker->instances().begin(); + instance != worker->instances().end(); ++instance) { + if (instance->HasFilter(filter, route_id)) { + // Fake a worker destroyed message so that WorkerProcessHost cleans up + // properly. + WorkerMsg_TerminateWorkerContext message(route_id); + ForwardToWorker(message, filter); + return; + } + } + } + + DCHECK(false) << "Couldn't find worker to cancel"; +} + +void WorkerService::ForwardToWorker(const IPC::Message& message, + WorkerMessageFilter* filter) { + for (BrowserChildProcessHost::Iterator iter(ChildProcessInfo::WORKER_PROCESS); + !iter.Done(); ++iter) { + WorkerProcessHost* worker = static_cast<WorkerProcessHost*>(*iter); + if (worker->FilterMessage(message, filter)) + return; + } + + // TODO(jabdelmalek): tell filter that callee is gone +} + +void WorkerService::DocumentDetached(unsigned long long document_id, + WorkerMessageFilter* filter) { + // Any associated shared workers can be shut down. + for (BrowserChildProcessHost::Iterator iter(ChildProcessInfo::WORKER_PROCESS); + !iter.Done(); ++iter) { + WorkerProcessHost* worker = static_cast<WorkerProcessHost*>(*iter); + worker->DocumentDetached(filter, document_id); + } + + // Remove any queued shared workers for this document. + for (WorkerProcessHost::Instances::iterator iter = queued_workers_.begin(); + iter != queued_workers_.end();) { + if (iter->shared()) { + 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 WorkerService::CreateWorkerFromInstance( + WorkerProcessHost::WorkerInstance instance) { + // TODO(michaeln): We need to ensure that a process is working + // on behalf of a single profile. The process sharing logic below + // does not ensure that. Consider making WorkerService a per profile + // object to help with this. + WorkerProcessHost* worker = NULL; + if (CommandLine::ForCurrentProcess()->HasSwitch( + switches::kWebWorkerProcessPerCore)) { + worker = GetProcessToFillUpCores(); + } else if (CommandLine::ForCurrentProcess()->HasSwitch( + switches::kWebWorkerShareProcesses)) { + worker = GetProcessForDomain(instance.url()); + } else { // One process per worker. + 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). + if (instance.shared()) { + // See if a worker with this name already exists. + WorkerProcessHost::WorkerInstance* existing_instance = + FindSharedWorkerInstance( + instance.url(), instance.name(), instance.off_the_record()); + 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.off_the_record()); + // 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.off_the_record()); + + // 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.off_the_record())) { + 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; + } + } + } + + if (!worker) { + WorkerMessageFilter* first_filter = instance.filters().begin()->first; + worker = new WorkerProcessHost( + first_filter->resource_dispatcher_host(), + instance.request_context()); + // 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; + } + } + + // TODO(michaeln): As written, test can fail per my earlier comment in + // this method, but that's a bug. + // DCHECK(worker->request_context() == instance.request_context()); + + worker->CreateWorker(instance); + return true; +} + +WorkerProcessHost* WorkerService::GetProcessForDomain(const GURL& url) { + int num_processes = 0; + std::string domain = + net::RegistryControlledDomainService::GetDomainAndRegistry(url); + for (BrowserChildProcessHost::Iterator iter(ChildProcessInfo::WORKER_PROCESS); + !iter.Done(); ++iter) { + num_processes++; + WorkerProcessHost* worker = static_cast<WorkerProcessHost*>(*iter); + for (WorkerProcessHost::Instances::const_iterator instance = + worker->instances().begin(); + instance != worker->instances().end(); ++instance) { + if (net::RegistryControlledDomainService::GetDomainAndRegistry( + instance->url()) == domain) { + return worker; + } + } + } + + if (num_processes >= kMaxWorkerProcessesWhenSharing) + return GetLeastLoadedWorker(); + + return NULL; +} + +WorkerProcessHost* WorkerService::GetProcessToFillUpCores() { + int num_processes = 0; + BrowserChildProcessHost::Iterator iter(ChildProcessInfo::WORKER_PROCESS); + for (; !iter.Done(); ++iter) + num_processes++; + + if (num_processes >= base::SysInfo::NumberOfProcessors()) + return GetLeastLoadedWorker(); + + return NULL; +} + +WorkerProcessHost* WorkerService::GetLeastLoadedWorker() { + WorkerProcessHost* smallest = NULL; + for (BrowserChildProcessHost::Iterator iter(ChildProcessInfo::WORKER_PROCESS); + !iter.Done(); ++iter) { + WorkerProcessHost* worker = static_cast<WorkerProcessHost*>(*iter); + if (!smallest || worker->instances().size() < smallest->instances().size()) + smallest = worker; + } + + return smallest; +} + +bool WorkerService::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 WorkerService::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 (BrowserChildProcessHost::Iterator iter(ChildProcessInfo::WORKER_PROCESS); + !iter.Done(); ++iter) { + WorkerProcessHost* worker = static_cast<WorkerProcessHost*>(*iter); + for (WorkerProcessHost::Instances::const_iterator cur_instance = + worker->instances().begin(); + cur_instance != worker->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 WorkerService::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 WorkerService::GetRendererForWorker(int worker_process_id, + int* render_process_id, + int* render_view_id) const { + for (BrowserChildProcessHost::Iterator iter(ChildProcessInfo::WORKER_PROCESS); + !iter.Done(); ++iter) { + if (iter->id() != worker_process_id) + continue; + + // This code assumes one worker per process, see function comment in header! + WorkerProcessHost* worker = static_cast<WorkerProcessHost*>(*iter); + WorkerProcessHost::Instances::const_iterator first_instance = + worker->instances().begin(); + if (first_instance == worker->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* WorkerService::FindWorkerInstance( + int worker_process_id) { + for (BrowserChildProcessHost::Iterator iter(ChildProcessInfo::WORKER_PROCESS); + !iter.Done(); ++iter) { + if (iter->id() != worker_process_id) + continue; + + WorkerProcessHost* worker = static_cast<WorkerProcessHost*>(*iter); + WorkerProcessHost::Instances::const_iterator instance = + worker->instances().begin(); + return instance == worker->instances().end() ? NULL : &*instance; + } + return NULL; +} + +WorkerProcessHost::WorkerInstance* +WorkerService::FindSharedWorkerInstance(const GURL& url, const string16& name, + bool off_the_record) { + for (BrowserChildProcessHost::Iterator iter(ChildProcessInfo::WORKER_PROCESS); + !iter.Done(); ++iter) { + WorkerProcessHost* worker = static_cast<WorkerProcessHost*>(*iter); + for (WorkerProcessHost::Instances::iterator instance_iter = + worker->mutable_instances().begin(); + instance_iter != worker->mutable_instances().end(); + ++instance_iter) { + if (instance_iter->Matches(url, name, off_the_record)) + return &(*instance_iter); + } + } + return NULL; +} + +WorkerProcessHost::WorkerInstance* +WorkerService::FindPendingInstance(const GURL& url, const string16& name, + bool off_the_record) { + // 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, off_the_record)) { + return &(*iter); + } + } + return NULL; +} + + +void WorkerService::RemovePendingInstances(const GURL& url, + const string16& name, + bool off_the_record) { + // 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, off_the_record)) { + iter = pending_shared_workers_.erase(iter); + } else { + ++iter; + } + } +} + +WorkerProcessHost::WorkerInstance* +WorkerService::CreatePendingInstance(const GURL& url, + const string16& name, + bool off_the_record) { + // Look for an existing pending shared worker. + WorkerProcessHost::WorkerInstance* instance = + FindPendingInstance(url, name, off_the_record); + if (instance) + return instance; + + // No existing pending worker - create a new one. + WorkerProcessHost::WorkerInstance pending( + url, true, off_the_record, name, MSG_ROUTING_NONE, 0, 0, 0, NULL); + pending_shared_workers_.push_back(pending); + return &pending_shared_workers_.back(); +} diff --git a/content/browser/worker_host/worker_service.h b/content/browser/worker_host/worker_service.h new file mode 100644 index 0000000..2b054aa --- /dev/null +++ b/content/browser/worker_host/worker_service.h @@ -0,0 +1,121 @@ +// Copyright (c) 2009 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. + +#ifndef CONTENT_BROWSER_WORKER_HOST_WORKER_SERVICE_H_ +#define CONTENT_BROWSER_WORKER_HOST_WORKER_SERVICE_H_ +#pragma once + +#include "base/basictypes.h" +#include "base/singleton.h" +#include "content/browser/worker_host/worker_process_host.h" +#include "googleurl/src/gurl.h" + +class URLRequestContextGetter; +struct ViewHostMsg_CreateWorker_Params; + +// A singelton for managing HTML5 web workers. +class WorkerService { + public: + // Returns the WorkerService singleton. + static WorkerService* GetInstance(); + + // These methods correspond to worker related IPCs. + void CreateWorker(const ViewHostMsg_CreateWorker_Params& params, + int route_id, + WorkerMessageFilter* filter, + URLRequestContextGetter* request_context); + void LookupSharedWorker(const ViewHostMsg_CreateWorker_Params& params, + int route_id, + WorkerMessageFilter* filter, + bool off_the_record, + bool* exists, + bool* url_error); + void CancelCreateDedicatedWorker(int route_id, WorkerMessageFilter* filter); + void ForwardToWorker(const IPC::Message& message, + WorkerMessageFilter* filter); + void DocumentDetached(unsigned long long document_id, + WorkerMessageFilter* filter); + + void OnWorkerMessageFilterClosing(WorkerMessageFilter* filter); + + int next_worker_route_id() { return ++next_worker_route_id_; } + + // Given a worker's process id, return the IDs of the renderer process and + // render view that created it. For shared workers, this returns the first + // parent. + // TODO(dimich): This code assumes there is 1 worker per worker process, which + // is how it is today until V8 can run in separate threads. + bool GetRendererForWorker(int worker_process_id, + int* render_process_id, + int* render_view_id) const; + const WorkerProcessHost::WorkerInstance* FindWorkerInstance( + int worker_process_id); + + // Used when multiple workers can run in the same process. + static const int kMaxWorkerProcessesWhenSharing; + + // Used when we run each worker in a separate process. + static const int kMaxWorkersWhenSeparate; + static const int kMaxWorkersPerTabWhenSeparate; + + private: + friend struct DefaultSingletonTraits<WorkerService>; + + WorkerService(); + ~WorkerService(); + + // Given a WorkerInstance, create an associated worker process. + bool CreateWorkerFromInstance(WorkerProcessHost::WorkerInstance instance); + + // Returns a WorkerProcessHost object if one exists for the given domain, or + // NULL if there are no such workers yet. + WorkerProcessHost* GetProcessForDomain(const GURL& url); + + // Returns a WorkerProcessHost based on a strategy of creating one worker per + // core. + WorkerProcessHost* GetProcessToFillUpCores(); + + // Returns the WorkerProcessHost from the existing set that has the least + // number of worker instance running. + WorkerProcessHost* GetLeastLoadedWorker(); + + // Checks if we can create a worker process based on the process limit when + // we're using a strategy of one process per core. + bool CanCreateWorkerProcess( + const WorkerProcessHost::WorkerInstance& instance); + + // Checks if the tab associated with the passed RenderView can create a + // worker process based on the process limit when we're using a strategy of + // one worker per process. + bool TabCanCreateWorkerProcess( + int render_process_id, int render_route_id, bool* hit_total_worker_limit); + + // Tries to see if any of the queued workers can be created. + void TryStartingQueuedWorker(); + + // APIs for manipulating our set of pending shared worker instances. + WorkerProcessHost::WorkerInstance* CreatePendingInstance( + const GURL& url, const string16& name, bool off_the_record); + WorkerProcessHost::WorkerInstance* FindPendingInstance( + const GURL& url, const string16& name, bool off_the_record); + void RemovePendingInstances( + const GURL& url, const string16& name, bool off_the_record); + + WorkerProcessHost::WorkerInstance* FindSharedWorkerInstance( + const GURL& url, const string16& name, bool off_the_record); + + NotificationRegistrar registrar_; + int next_worker_route_id_; + + WorkerProcessHost::Instances queued_workers_; + + // These are shared workers that have been looked up, but not created yet. + // We need to keep a list of these to synchronously detect shared worker + // URL mismatches when two pages launch shared workers simultaneously. + WorkerProcessHost::Instances pending_shared_workers_; + + DISALLOW_COPY_AND_ASSIGN(WorkerService); +}; + +#endif // CONTENT_BROWSER_WORKER_HOST_WORKER_SERVICE_H_ diff --git a/content/content_browser.gypi b/content/content_browser.gypi index 8324a8a..eafd1a2 100644 --- a/content/content_browser.gypi +++ b/content/content_browser.gypi @@ -299,6 +299,16 @@ 'browser/webui/web_ui_factory.h', 'browser/webui/web_ui_util.cc', 'browser/webui/web_ui_util.h', + 'browser/worker_host/message_port_service.cc', + 'browser/worker_host/message_port_service.h', + 'browser/worker_host/worker_document_set.cc', + 'browser/worker_host/worker_document_set.h', + 'browser/worker_host/worker_message_filter.cc', + 'browser/worker_host/worker_message_filter.h', + 'browser/worker_host/worker_process_host.cc', + 'browser/worker_host/worker_process_host.h', + 'browser/worker_host/worker_service.cc', + 'browser/worker_host/worker_service.h', 'browser/zygote_host_linux.cc', 'browser/zygote_host_linux.h', 'browser/zygote_main_linux.cc', |