1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "content/browser/worker_host/worker_process_host.h"
11 #include "base/base_switches.h"
12 #include "base/bind.h"
13 #include "base/bind_helpers.h"
14 #include "base/callback.h"
15 #include "base/command_line.h"
16 #include "base/message_loop/message_loop.h"
17 #include "base/strings/string_util.h"
18 #include "base/strings/utf_string_conversions.h"
19 #include "content/browser/appcache/appcache_dispatcher_host.h"
20 #include "content/browser/appcache/chrome_appcache_service.h"
21 #include "content/browser/browser_child_process_host_impl.h"
22 #include "content/browser/child_process_security_policy_impl.h"
23 #include "content/browser/devtools/worker_devtools_manager.h"
24 #include "content/browser/devtools/worker_devtools_message_filter.h"
25 #include "content/browser/fileapi/fileapi_message_filter.h"
26 #include "content/browser/indexed_db/indexed_db_dispatcher_host.h"
27 #include "content/browser/loader/resource_message_filter.h"
28 #include "content/browser/message_port_message_filter.h"
29 #include "content/browser/message_port_service.h"
30 #include "content/browser/mime_registry_message_filter.h"
31 #include "content/browser/quota_dispatcher_host.h"
32 #include "content/browser/renderer_host/database_message_filter.h"
33 #include "content/browser/renderer_host/file_utilities_message_filter.h"
34 #include "content/browser/renderer_host/render_view_host_delegate.h"
35 #include "content/browser/renderer_host/render_view_host_impl.h"
36 #include "content/browser/renderer_host/socket_stream_dispatcher_host.h"
37 #include "content/browser/resource_context_impl.h"
38 #include "content/browser/worker_host/worker_message_filter.h"
39 #include "content/browser/worker_host/worker_service_impl.h"
40 #include "content/common/child_process_host_impl.h"
41 #include "content/common/view_messages.h"
42 #include "content/common/worker_messages.h"
43 #include "content/public/browser/browser_thread.h"
44 #include "content/public/browser/content_browser_client.h"
45 #include "content/public/browser/user_metrics.h"
46 #include "content/public/common/content_switches.h"
47 #include "content/public/common/result_codes.h"
48 #include "ipc/ipc_switches.h"
49 #include "net/base/mime_util.h"
50 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
51 #include "net/url_request/url_request_context_getter.h"
52 #include "ui/base/ui_base_switches.h"
53 #include "webkit/browser/fileapi/file_system_context.h"
54 #include "webkit/browser/fileapi/sandbox_file_system_backend.h"
55 #include "webkit/common/resource_type.h"
58 #include "content/common/sandbox_win.h"
59 #include "content/public/common/sandboxed_process_launcher_delegate.h"
66 // NOTE: changes to this class need to be reviewed by the security team.
67 class WorkerSandboxedProcessLauncherDelegate
68 : public content::SandboxedProcessLauncherDelegate {
70 WorkerSandboxedProcessLauncherDelegate() {}
71 virtual ~WorkerSandboxedProcessLauncherDelegate() {}
73 virtual void PreSpawnTarget(sandbox::TargetPolicy* policy,
75 AddBaseHandleClosePolicy(policy);
82 // Notifies RenderViewHost that one or more worker objects crashed.
83 void WorkerCrashCallback(int render_process_unique_id, int render_view_id) {
84 RenderViewHostImpl* host =
85 RenderViewHostImpl::FromID(render_process_unique_id, render_view_id);
87 host->GetDelegate()->WorkerCrashed();
90 WorkerProcessHost::WorkerProcessHost(
91 ResourceContext* resource_context,
92 const WorkerStoragePartition& partition)
93 : resource_context_(resource_context),
94 partition_(partition),
95 process_launched_(false) {
96 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
97 DCHECK(resource_context_);
99 new BrowserChildProcessHostImpl(PROCESS_TYPE_WORKER, this));
102 WorkerProcessHost::~WorkerProcessHost() {
103 // If we crashed, tell the RenderViewHosts.
104 for (Instances::iterator i = instances_.begin(); i != instances_.end(); ++i) {
105 const WorkerDocumentSet::DocumentInfoSet& parents =
106 i->worker_document_set()->documents();
107 for (WorkerDocumentSet::DocumentInfoSet::const_iterator parent_iter =
108 parents.begin(); parent_iter != parents.end(); ++parent_iter) {
109 BrowserThread::PostTask(
110 BrowserThread::UI, FROM_HERE,
111 base::Bind(&WorkerCrashCallback, parent_iter->render_process_id(),
112 parent_iter->render_view_id()));
114 WorkerServiceImpl::GetInstance()->NotifyWorkerDestroyed(
115 this, i->worker_route_id());
118 ChildProcessSecurityPolicyImpl::GetInstance()->Remove(
119 process_->GetData().id);
122 bool WorkerProcessHost::Send(IPC::Message* message) {
123 return process_->Send(message);
126 bool WorkerProcessHost::Init(int render_process_id) {
127 std::string channel_id = process_->GetHost()->CreateChannel();
128 if (channel_id.empty())
131 #if defined(OS_LINUX)
132 int flags = ChildProcessHost::CHILD_ALLOW_SELF;
134 int flags = ChildProcessHost::CHILD_NORMAL;
137 base::FilePath exe_path = ChildProcessHost::GetChildPath(flags);
138 if (exe_path.empty())
141 CommandLine* cmd_line = new CommandLine(exe_path);
142 cmd_line->AppendSwitchASCII(switches::kProcessType, switches::kWorkerProcess);
143 cmd_line->AppendSwitchASCII(switches::kProcessChannelID, channel_id);
144 std::string locale = GetContentClient()->browser()->GetApplicationLocale();
145 cmd_line->AppendSwitchASCII(switches::kLang, locale);
147 static const char* const kSwitchNames[] = {
148 switches::kDisableApplicationCache,
149 switches::kDisableDatabases,
151 switches::kDisableDesktopNotifications,
153 switches::kDisableFileSystem,
154 switches::kDisableSeccompFilterSandbox,
155 switches::kEnableExperimentalWebPlatformFeatures,
156 switches::kEnableServiceWorker,
157 #if defined(OS_MACOSX)
158 switches::kEnableSandboxLogging,
161 cmd_line->CopySwitchesFrom(*CommandLine::ForCurrentProcess(), kSwitchNames,
162 arraysize(kSwitchNames));
164 #if defined(OS_POSIX)
165 bool use_zygote = true;
167 if (CommandLine::ForCurrentProcess()->HasSwitch(
168 switches::kWaitForDebuggerChildren)) {
169 // Look to pass-on the kWaitForDebugger flag.
170 std::string value = CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
171 switches::kWaitForDebuggerChildren);
172 if (value.empty() || value == switches::kWorkerProcess) {
173 cmd_line->AppendSwitch(switches::kWaitForDebugger);
178 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kDebugChildren)) {
179 // Look to pass-on the kDebugOnStart flag.
180 std::string value = CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
181 switches::kDebugChildren);
182 if (value.empty() || value == switches::kWorkerProcess) {
183 // launches a new xterm, and runs the worker process in gdb, reading
184 // optional commands from gdb_chrome file in the working directory.
185 cmd_line->PrependWrapper("xterm -e gdb -x gdb_chrome --args");
193 new WorkerSandboxedProcessLauncherDelegate,
194 #elif defined(OS_POSIX)
196 base::EnvironmentMap(),
200 ChildProcessSecurityPolicyImpl::GetInstance()->AddWorker(
201 process_->GetData().id, render_process_id);
202 CreateMessageFilters(render_process_id);
207 void WorkerProcessHost::CreateMessageFilters(int render_process_id) {
208 ChromeBlobStorageContext* blob_storage_context =
209 GetChromeBlobStorageContextForResourceContext(resource_context_);
210 StreamContext* stream_context =
211 GetStreamContextForResourceContext(resource_context_);
213 net::URLRequestContextGetter* url_request_context =
214 partition_.url_request_context();
216 ResourceMessageFilter::GetContextsCallback get_contexts_callback(
217 base::Bind(&WorkerProcessHost::GetContexts,
218 base::Unretained(this)));
220 ResourceMessageFilter* resource_message_filter = new ResourceMessageFilter(
221 process_->GetData().id, PROCESS_TYPE_WORKER,
222 partition_.appcache_service(),
223 blob_storage_context,
224 partition_.filesystem_context(),
225 get_contexts_callback);
226 process_->AddFilter(resource_message_filter);
228 MessagePortMessageFilter* message_port_message_filter =
229 new MessagePortMessageFilter(
230 base::Bind(&WorkerServiceImpl::next_worker_route_id,
231 base::Unretained(WorkerServiceImpl::GetInstance())));
232 process_->AddFilter(message_port_message_filter);
233 worker_message_filter_ = new WorkerMessageFilter(render_process_id,
236 message_port_message_filter);
237 process_->AddFilter(worker_message_filter_.get());
238 process_->AddFilter(new AppCacheDispatcherHost(
239 partition_.appcache_service(), process_->GetData().id));
240 process_->AddFilter(new FileAPIMessageFilter(
241 process_->GetData().id,
243 partition_.filesystem_context(),
244 blob_storage_context,
246 process_->AddFilter(new FileUtilitiesMessageFilter(
247 process_->GetData().id));
248 process_->AddFilter(new MimeRegistryMessageFilter());
249 process_->AddFilter(new DatabaseMessageFilter(partition_.database_tracker()));
250 process_->AddFilter(new QuotaDispatcherHost(
251 process_->GetData().id,
252 partition_.quota_manager(),
253 GetContentClient()->browser()->CreateQuotaPermissionContext()));
255 SocketStreamDispatcherHost::GetRequestContextCallback
256 request_context_callback(
257 base::Bind(&WorkerProcessHost::GetRequestContext,
258 base::Unretained(this)));
260 SocketStreamDispatcherHost* socket_stream_dispatcher_host =
261 new SocketStreamDispatcherHost(
263 request_context_callback,
265 socket_stream_dispatcher_host_ = socket_stream_dispatcher_host;
266 process_->AddFilter(socket_stream_dispatcher_host);
267 process_->AddFilter(new WorkerDevToolsMessageFilter(process_->GetData().id));
268 process_->AddFilter(new IndexedDBDispatcherHost(
269 process_->GetData().id, partition_.indexed_db_context()));
272 void WorkerProcessHost::CreateWorker(const WorkerInstance& instance) {
273 ChildProcessSecurityPolicyImpl::GetInstance()->GrantRequestURL(
274 process_->GetData().id, instance.url());
276 instances_.push_back(instance);
278 WorkerProcessMsg_CreateWorker_Params params;
279 params.url = instance.url();
280 params.name = instance.name();
281 params.route_id = instance.worker_route_id();
282 params.creator_process_id = instance.parent_process_id();
283 params.shared_worker_appcache_id = instance.main_resource_appcache_id();
284 Send(new WorkerProcessMsg_CreateWorker(params));
288 // Walk all pending filters and let them know the worker has been created
289 // (could be more than one in the case where we had to queue up worker
290 // creation because the worker process limit was reached).
291 for (WorkerInstance::FilterList::const_iterator i =
292 instance.filters().begin();
293 i != instance.filters().end(); ++i) {
295 i->first->Send(new ViewMsg_WorkerCreated(i->second));
299 bool WorkerProcessHost::FilterMessage(const IPC::Message& message,
300 WorkerMessageFilter* filter) {
301 for (Instances::iterator i = instances_.begin(); i != instances_.end(); ++i) {
302 if (!i->closed() && i->HasFilter(filter, message.routing_id())) {
303 RelayMessage(message, worker_message_filter_.get(), i->worker_route_id());
311 void WorkerProcessHost::OnProcessLaunched() {
312 process_launched_ = true;
314 WorkerServiceImpl::GetInstance()->NotifyWorkerProcessCreated();
317 bool WorkerProcessHost::OnMessageReceived(const IPC::Message& message) {
318 bool msg_is_ok = true;
320 IPC_BEGIN_MESSAGE_MAP_EX(WorkerProcessHost, message, msg_is_ok)
321 IPC_MESSAGE_HANDLER(WorkerHostMsg_WorkerContextClosed,
322 OnWorkerContextClosed)
323 IPC_MESSAGE_HANDLER(WorkerProcessHostMsg_AllowDatabase, OnAllowDatabase)
324 IPC_MESSAGE_HANDLER(WorkerProcessHostMsg_AllowFileSystem, OnAllowFileSystem)
325 IPC_MESSAGE_HANDLER(WorkerProcessHostMsg_AllowIndexedDB, OnAllowIndexedDB)
326 IPC_MESSAGE_HANDLER(WorkerProcessHostMsg_ForceKillWorker,
327 OnForceKillWorkerProcess)
328 IPC_MESSAGE_UNHANDLED(handled = false)
329 IPC_END_MESSAGE_MAP_EX()
333 RecordAction(UserMetricsAction("BadMessageTerminate_WPH"));
335 process_->GetData().handle, RESULT_CODE_KILLED_BAD_MESSAGE, false);
341 if (message.type() == WorkerHostMsg_WorkerContextDestroyed::ID) {
342 WorkerServiceImpl::GetInstance()->NotifyWorkerDestroyed(
343 this, message.routing_id());
346 for (Instances::iterator i = instances_.begin(); i != instances_.end(); ++i) {
347 if (i->worker_route_id() == message.routing_id()) {
348 if (message.type() == WorkerHostMsg_WorkerContextDestroyed::ID) {
358 // Sent to notify the browser process when a worker context invokes close(), so
359 // no new connections are sent to shared workers.
360 void WorkerProcessHost::OnWorkerContextClosed(int worker_route_id) {
361 for (Instances::iterator i = instances_.begin(); i != instances_.end(); ++i) {
362 if (i->worker_route_id() == worker_route_id) {
363 // Set the closed flag - this will stop any further messages from
364 // being sent to the worker (messages can still be sent from the worker,
365 // for exception reporting, etc).
372 void WorkerProcessHost::OnAllowDatabase(int worker_route_id,
374 const string16& name,
375 const string16& display_name,
376 unsigned long estimated_size,
378 *result = GetContentClient()->browser()->AllowWorkerDatabase(
379 url, name, display_name, estimated_size, resource_context_,
380 GetRenderViewIDsForWorker(worker_route_id));
383 void WorkerProcessHost::OnAllowFileSystem(int worker_route_id,
386 *result = GetContentClient()->browser()->AllowWorkerFileSystem(
387 url, resource_context_, GetRenderViewIDsForWorker(worker_route_id));
390 void WorkerProcessHost::OnAllowIndexedDB(int worker_route_id,
392 const string16& name,
394 *result = GetContentClient()->browser()->AllowWorkerIndexedDB(
395 url, name, resource_context_, GetRenderViewIDsForWorker(worker_route_id));
398 void WorkerProcessHost::OnForceKillWorkerProcess() {
399 if (process_ && process_launched_)
401 process_->GetData().handle, RESULT_CODE_NORMAL_EXIT, false);
403 RecordAction(UserMetricsAction("WorkerProcess_BadProcessToKill"));
406 void WorkerProcessHost::RelayMessage(
407 const IPC::Message& message,
408 WorkerMessageFilter* filter,
410 if (message.type() == WorkerMsg_Connect::ID) {
411 // Crack the SharedWorker Connect message to setup routing for the port.
412 int sent_message_port_id;
414 if (!WorkerMsg_Connect::Read(
415 &message, &sent_message_port_id, &new_routing_id)) {
418 new_routing_id = filter->GetNextRoutingID();
419 MessagePortService::GetInstance()->UpdateMessagePort(
420 sent_message_port_id,
421 filter->message_port_message_filter(),
424 // Resend the message with the new routing id.
425 filter->Send(new WorkerMsg_Connect(
426 route_id, sent_message_port_id, new_routing_id));
428 // Send any queued messages for the sent port.
429 MessagePortService::GetInstance()->SendQueuedMessagesIfPossible(
430 sent_message_port_id);
432 IPC::Message* new_message = new IPC::Message(message);
433 new_message->set_routing_id(route_id);
434 filter->Send(new_message);
435 if (message.type() == WorkerMsg_StartWorkerContext::ID) {
436 WorkerDevToolsManager::GetInstance()->WorkerContextStarted(
443 void WorkerProcessHost::ShutdownSocketStreamDispatcherHostIfNecessary() {
444 if (!instances_.size() && socket_stream_dispatcher_host_.get()) {
445 // We can assume that this object is going to delete, because
446 // currently a WorkerInstance will never be added to a WorkerProcessHost
447 // once it is initialized.
449 // SocketStreamDispatcherHost should be notified now that the worker
450 // process will shutdown soon.
451 socket_stream_dispatcher_host_->Shutdown();
452 socket_stream_dispatcher_host_ = NULL;
456 void WorkerProcessHost::FilterShutdown(WorkerMessageFilter* filter) {
457 for (Instances::iterator i = instances_.begin(); i != instances_.end();) {
458 bool shutdown = false;
459 i->RemoveFilters(filter);
461 i->worker_document_set()->RemoveAll(filter);
462 if (i->worker_document_set()->IsEmpty()) {
466 Send(new WorkerMsg_TerminateWorkerContext(i->worker_route_id()));
467 i = instances_.erase(i);
472 ShutdownSocketStreamDispatcherHostIfNecessary();
475 bool WorkerProcessHost::CanShutdown() {
476 return instances_.empty();
479 void WorkerProcessHost::UpdateTitle() {
480 std::set<std::string> titles;
481 for (Instances::iterator i = instances_.begin(); i != instances_.end(); ++i) {
482 // Allow the embedder first crack at special casing the title.
483 std::string title = GetContentClient()->browser()->
484 GetWorkerProcessTitle(i->url(), resource_context_);
487 title = net::registry_controlled_domains::GetDomainAndRegistry(
489 net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES);
492 // Use the host name if the domain is empty, i.e. localhost or IP address.
494 title = i->url().host();
496 // If the host name is empty, i.e. file url, use the path.
498 title = i->url().path();
499 titles.insert(title);
502 std::string display_title;
503 for (std::set<std::string>::iterator i = titles.begin();
504 i != titles.end(); ++i) {
505 if (!display_title.empty())
506 display_title += ", ";
510 process_->SetName(UTF8ToUTF16(display_title));
513 void WorkerProcessHost::DocumentDetached(WorkerMessageFilter* filter,
514 unsigned long long document_id) {
515 // Walk all instances and remove the document from their document set.
516 for (Instances::iterator i = instances_.begin(); i != instances_.end();) {
517 i->worker_document_set()->Remove(filter, document_id);
518 if (i->worker_document_set()->IsEmpty()) {
519 // This worker has no more associated documents - shut it down.
520 Send(new WorkerMsg_TerminateWorkerContext(i->worker_route_id()));
521 i = instances_.erase(i);
526 ShutdownSocketStreamDispatcherHostIfNecessary();
529 void WorkerProcessHost::TerminateWorker(int worker_route_id) {
530 Send(new WorkerMsg_TerminateWorkerContext(worker_route_id));
533 void WorkerProcessHost::SetBackgrounded(bool backgrounded) {
534 process_->SetBackgrounded(backgrounded);
537 const ChildProcessData& WorkerProcessHost::GetData() {
538 return process_->GetData();
541 std::vector<std::pair<int, int> > WorkerProcessHost::GetRenderViewIDsForWorker(
542 int worker_route_id) {
543 std::vector<std::pair<int, int> > result;
544 WorkerProcessHost::Instances::const_iterator i;
545 for (i = instances_.begin(); i != instances_.end(); ++i) {
546 if (i->worker_route_id() != worker_route_id)
548 const WorkerDocumentSet::DocumentInfoSet& documents =
549 i->worker_document_set()->documents();
550 for (WorkerDocumentSet::DocumentInfoSet::const_iterator doc =
551 documents.begin(); doc != documents.end(); ++doc) {
553 std::make_pair(doc->render_process_id(), doc->render_view_id()));
560 void WorkerProcessHost::GetContexts(const ResourceHostMsg_Request& request,
561 ResourceContext** resource_context,
562 net::URLRequestContext** request_context) {
563 *resource_context = resource_context_;
564 *request_context = partition_.url_request_context()->GetURLRequestContext();
567 net::URLRequestContext* WorkerProcessHost::GetRequestContext(
568 ResourceType::Type resource_type) {
569 return partition_.url_request_context()->GetURLRequestContext();
572 WorkerProcessHost::WorkerInstance::WorkerInstance(
574 const string16& name,
576 int parent_process_id,
577 int64 main_resource_appcache_id,
578 ResourceContext* resource_context,
579 const WorkerStoragePartition& partition)
583 worker_route_id_(worker_route_id),
584 parent_process_id_(parent_process_id),
585 main_resource_appcache_id_(main_resource_appcache_id),
586 worker_document_set_(new WorkerDocumentSet()),
587 resource_context_(resource_context),
588 partition_(partition) {
589 DCHECK(resource_context_);
592 WorkerProcessHost::WorkerInstance::WorkerInstance(
595 const string16& name,
596 ResourceContext* resource_context,
597 const WorkerStoragePartition& partition)
601 worker_route_id_(MSG_ROUTING_NONE),
602 parent_process_id_(0),
603 main_resource_appcache_id_(0),
604 worker_document_set_(new WorkerDocumentSet()),
605 resource_context_(resource_context),
606 partition_(partition) {
607 DCHECK(resource_context_);
610 WorkerProcessHost::WorkerInstance::~WorkerInstance() {
613 // Compares an instance based on the algorithm in the WebWorkers spec - an
614 // instance matches if the origins of the URLs match, and:
615 // a) the names are non-empty and equal
617 // b) the names are both empty, and the urls are equal
618 bool WorkerProcessHost::WorkerInstance::Matches(
619 const GURL& match_url,
620 const string16& match_name,
621 const WorkerStoragePartition& partition,
622 ResourceContext* resource_context) const {
623 // Only match open shared workers.
627 // ResourceContext equivalence is being used as a proxy to ensure we only
628 // matched shared workers within the same BrowserContext.
629 if (resource_context_ != resource_context)
632 // We must be in the same storage partition otherwise sharing will violate
634 if (!partition_.Equals(partition))
637 if (url_.GetOrigin() != match_url.GetOrigin())
640 if (name_.empty() && match_name.empty())
641 return url_ == match_url;
643 return name_ == match_name;
646 void WorkerProcessHost::WorkerInstance::AddFilter(WorkerMessageFilter* filter,
649 if (!HasFilter(filter, route_id)) {
650 FilterInfo info(filter, route_id);
651 filters_.push_back(info);
655 void WorkerProcessHost::WorkerInstance::RemoveFilter(
656 WorkerMessageFilter* filter, int route_id) {
657 for (FilterList::iterator i = filters_.begin(); i != filters_.end();) {
658 if (i->first == filter && i->second == route_id)
659 i = filters_.erase(i);
663 // Should not be duplicate copies in the filter set.
664 DCHECK(!HasFilter(filter, route_id));
667 void WorkerProcessHost::WorkerInstance::RemoveFilters(
668 WorkerMessageFilter* filter) {
669 for (FilterList::iterator i = filters_.begin(); i != filters_.end();) {
670 if (i->first == filter)
671 i = filters_.erase(i);
677 bool WorkerProcessHost::WorkerInstance::HasFilter(
678 WorkerMessageFilter* filter, int route_id) const {
679 for (FilterList::const_iterator i = filters_.begin(); i != filters_.end();
681 if (i->first == filter && i->second == route_id)
687 bool WorkerProcessHost::WorkerInstance::RendererIsParent(
688 int render_process_id, int render_view_id) const {
689 const WorkerDocumentSet::DocumentInfoSet& parents =
690 worker_document_set()->documents();
691 for (WorkerDocumentSet::DocumentInfoSet::const_iterator parent_iter =
693 parent_iter != parents.end(); ++parent_iter) {
694 if (parent_iter->render_process_id() == render_process_id &&
695 parent_iter->render_view_id() == render_view_id) {
702 WorkerProcessHost::WorkerInstance::FilterInfo
703 WorkerProcessHost::WorkerInstance::GetFilter() const {
704 DCHECK(NumFilters() == 1);
705 return *filters_.begin();
708 } // namespace content