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_service_impl.h"
9 #include "base/command_line.h"
10 #include "base/logging.h"
11 #include "base/threading/thread.h"
12 #include "content/browser/devtools/worker_devtools_manager.h"
13 #include "content/browser/renderer_host/render_widget_host_impl.h"
14 #include "content/browser/worker_host/worker_message_filter.h"
15 #include "content/browser/worker_host/worker_process_host.h"
16 #include "content/common/view_messages.h"
17 #include "content/common/worker_messages.h"
18 #include "content/public/browser/child_process_data.h"
19 #include "content/public/browser/notification_service.h"
20 #include "content/public/browser/notification_types.h"
21 #include "content/public/browser/render_frame_host.h"
22 #include "content/public/browser/render_process_host.h"
23 #include "content/public/browser/render_view_host.h"
24 #include "content/public/browser/render_widget_host.h"
25 #include "content/public/browser/render_widget_host_iterator.h"
26 #include "content/public/browser/render_widget_host_view.h"
27 #include "content/public/browser/resource_context.h"
28 #include "content/public/browser/web_contents.h"
29 #include "content/public/browser/worker_service_observer.h"
30 #include "content/public/common/content_switches.h"
31 #include "content/public/common/process_type.h"
36 void AddRenderFrameID(std::set<std::pair<int, int> >* visible_frame_ids,
37 RenderFrameHost* rfh) {
38 visible_frame_ids->insert(
39 std::pair<int, int>(rfh->GetProcess()->GetID(),
40 rfh->GetRoutingID()));
44 const int WorkerServiceImpl::kMaxWorkersWhenSeparate = 64;
45 const int WorkerServiceImpl::kMaxWorkersPerFrameWhenSeparate = 16;
47 class WorkerPrioritySetter
48 : public NotificationObserver,
49 public base::RefCountedThreadSafe<WorkerPrioritySetter,
50 BrowserThread::DeleteOnUIThread> {
52 WorkerPrioritySetter();
54 // Posts a task to the UI thread to register to receive notifications.
57 // Invoked by WorkerServiceImpl when a worker process is created.
58 void NotifyWorkerProcessCreated();
61 friend class base::RefCountedThreadSafe<WorkerPrioritySetter>;
62 friend struct BrowserThread::DeleteOnThread<BrowserThread::UI>;
63 friend class base::DeleteHelper<WorkerPrioritySetter>;
64 virtual ~WorkerPrioritySetter();
66 // Posts a task to perform a worker priority update.
67 void PostTaskToGatherAndUpdateWorkerPriorities();
69 // Gathers up a list of the visible tabs and then updates priorities for
70 // all the shared workers.
71 void GatherVisibleIDsAndUpdateWorkerPriorities();
73 // Registers as an observer to receive notifications about
74 // widgets being shown.
75 void RegisterObserver();
77 // Sets priorities for shared workers given a set of visible frames (as a
78 // std::set of std::pair<render_process, render_frame> ids.
79 void UpdateWorkerPrioritiesFromVisibleSet(
80 const std::set<std::pair<int, int> >* visible);
82 // Called to refresh worker priorities when focus changes between tabs.
83 void OnRenderWidgetVisibilityChanged(std::pair<int, int>);
85 // NotificationObserver implementation.
86 virtual void Observe(int type,
87 const NotificationSource& source,
88 const NotificationDetails& details) OVERRIDE;
90 NotificationRegistrar registrar_;
93 WorkerPrioritySetter::WorkerPrioritySetter() {
96 WorkerPrioritySetter::~WorkerPrioritySetter() {
97 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
100 void WorkerPrioritySetter::Initialize() {
101 BrowserThread::PostTask(
102 BrowserThread::UI, FROM_HERE,
103 base::Bind(&WorkerPrioritySetter::RegisterObserver, this));
106 void WorkerPrioritySetter::NotifyWorkerProcessCreated() {
107 PostTaskToGatherAndUpdateWorkerPriorities();
110 void WorkerPrioritySetter::PostTaskToGatherAndUpdateWorkerPriorities() {
111 BrowserThread::PostTask(
112 BrowserThread::UI, FROM_HERE,
114 &WorkerPrioritySetter::GatherVisibleIDsAndUpdateWorkerPriorities,
118 void WorkerPrioritySetter::GatherVisibleIDsAndUpdateWorkerPriorities() {
119 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
120 std::set<std::pair<int, int> >* visible_frame_ids =
121 new std::set<std::pair<int, int> >();
123 // Gather up all the visible renderer process/view pairs
124 scoped_ptr<RenderWidgetHostIterator> widgets(
125 RenderWidgetHost::GetRenderWidgetHosts());
126 while (RenderWidgetHost* widget = widgets->GetNextHost()) {
127 if (widget->GetProcess()->VisibleWidgetCount() == 0)
129 if (!widget->IsRenderView())
132 RenderWidgetHostView* widget_view = widget->GetView();
133 if (!widget_view || !widget_view->IsShowing())
135 RenderViewHost* rvh = RenderViewHost::From(widget);
136 WebContents* web_contents = WebContents::FromRenderViewHost(rvh);
139 web_contents->ForEachFrame(
140 base::Bind(&AddRenderFrameID, visible_frame_ids));
143 BrowserThread::PostTask(
144 BrowserThread::IO, FROM_HERE,
145 base::Bind(&WorkerPrioritySetter::UpdateWorkerPrioritiesFromVisibleSet,
146 this, base::Owned(visible_frame_ids)));
149 void WorkerPrioritySetter::UpdateWorkerPrioritiesFromVisibleSet(
150 const std::set<std::pair<int, int> >* visible_frame_ids) {
151 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
153 for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
154 if (!iter->process_launched())
156 bool throttle = true;
158 for (WorkerProcessHost::Instances::const_iterator instance =
159 iter->instances().begin(); instance != iter->instances().end();
162 // This code assumes one worker per process
163 WorkerProcessHost::Instances::const_iterator first_instance =
164 iter->instances().begin();
165 if (first_instance == iter->instances().end())
168 WorkerDocumentSet::DocumentInfoSet::const_iterator info =
169 first_instance->worker_document_set()->documents().begin();
171 for (; info != first_instance->worker_document_set()->documents().end();
173 std::pair<int, int> id(
174 info->render_process_id(), info->render_frame_id());
175 if (visible_frame_ids->find(id) != visible_frame_ids->end()) {
186 iter->SetBackgrounded(throttle);
190 void WorkerPrioritySetter::OnRenderWidgetVisibilityChanged(
191 std::pair<int, int> id) {
192 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
193 std::set<std::pair<int, int> > visible_frame_ids;
195 visible_frame_ids.insert(id);
197 UpdateWorkerPrioritiesFromVisibleSet(&visible_frame_ids);
200 void WorkerPrioritySetter::RegisterObserver() {
201 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
202 registrar_.Add(this, NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED,
203 NotificationService::AllBrowserContextsAndSources());
204 registrar_.Add(this, NOTIFICATION_RENDERER_PROCESS_CREATED,
205 NotificationService::AllBrowserContextsAndSources());
208 void WorkerPrioritySetter::Observe(int type,
209 const NotificationSource& source, const NotificationDetails& details) {
210 if (type == NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED) {
211 bool visible = *Details<bool>(details).ptr();
214 int render_widget_id =
215 Source<RenderWidgetHost>(source).ptr()->GetRoutingID();
216 int render_process_pid =
217 Source<RenderWidgetHost>(source).ptr()->GetProcess()->GetID();
219 BrowserThread::PostTask(
220 BrowserThread::IO, FROM_HERE,
221 base::Bind(&WorkerPrioritySetter::OnRenderWidgetVisibilityChanged,
222 this, std::pair<int, int>(render_process_pid, render_widget_id)));
225 else if (type == NOTIFICATION_RENDERER_PROCESS_CREATED) {
226 PostTaskToGatherAndUpdateWorkerPriorities();
230 WorkerService* WorkerService::GetInstance() {
231 return WorkerServiceImpl::GetInstance();
234 WorkerServiceImpl* WorkerServiceImpl::GetInstance() {
235 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
236 return Singleton<WorkerServiceImpl>::get();
239 WorkerServiceImpl::WorkerServiceImpl()
240 : priority_setter_(new WorkerPrioritySetter()),
241 next_worker_route_id_(0) {
242 priority_setter_->Initialize();
245 WorkerServiceImpl::~WorkerServiceImpl() {
246 // The observers in observers_ can't be used here because they might be
250 void WorkerServiceImpl::PerformTeardownForTesting() {
251 priority_setter_ = NULL;
254 void WorkerServiceImpl::OnWorkerMessageFilterClosing(
255 WorkerMessageFilter* filter) {
256 for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
257 iter->FilterShutdown(filter);
260 // See if that process had any queued workers.
261 for (WorkerProcessHost::Instances::iterator i = queued_workers_.begin();
262 i != queued_workers_.end();) {
263 i->RemoveFilters(filter);
264 if (i->NumFilters() == 0) {
265 i = queued_workers_.erase(i);
271 // Either a worker proceess has shut down, in which case we can start one of
272 // the queued workers, or a renderer has shut down, in which case it doesn't
273 // affect anything. We call this function in both scenarios because then we
274 // don't have to keep track which filters are from worker processes.
275 TryStartingQueuedWorker();
278 void WorkerServiceImpl::CreateWorker(
279 const ViewHostMsg_CreateWorker_Params& params,
281 WorkerMessageFilter* filter,
282 ResourceContext* resource_context,
283 const WorkerStoragePartition& partition,
284 bool* url_mismatch) {
285 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
286 *url_mismatch = false;
287 WorkerProcessHost::WorkerInstance* existing_instance =
288 FindSharedWorkerInstance(
289 params.url, params.name, partition, resource_context);
290 if (existing_instance) {
291 if (params.url != existing_instance->url()) {
292 *url_mismatch = true;
295 if (existing_instance->load_failed()) {
296 filter->Send(new ViewMsg_WorkerScriptLoadFailed(route_id));
299 existing_instance->AddFilter(filter, route_id);
300 existing_instance->worker_document_set()->Add(
301 filter, params.document_id, filter->render_process_id(),
302 params.render_frame_route_id);
303 filter->Send(new ViewMsg_WorkerCreated(route_id));
306 for (WorkerProcessHost::Instances::iterator i = queued_workers_.begin();
307 i != queued_workers_.end(); ++i) {
308 if (i->Matches(params.url, params.name, partition, resource_context) &&
309 params.url != i->url()) {
310 *url_mismatch = true;
315 // Generate a unique route id for the browser-worker communication that's
316 // unique among all worker processes. That way when the worker process sends
317 // a wrapped IPC message through us, we know which WorkerProcessHost to give
319 WorkerProcessHost::WorkerInstance instance(
322 params.content_security_policy,
323 params.security_policy_type,
324 next_worker_route_id(),
325 params.render_frame_route_id,
328 instance.AddFilter(filter, route_id);
329 instance.worker_document_set()->Add(
330 filter, params.document_id, filter->render_process_id(),
331 params.render_frame_route_id);
333 CreateWorkerFromInstance(instance);
336 void WorkerServiceImpl::ForwardToWorker(const IPC::Message& message,
337 WorkerMessageFilter* filter) {
338 for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
339 if (iter->FilterMessage(message, filter))
343 // TODO(jabdelmalek): tell filter that callee is gone
346 void WorkerServiceImpl::DocumentDetached(unsigned long long document_id,
347 WorkerMessageFilter* filter) {
348 // Any associated shared workers can be shut down.
349 for (WorkerProcessHostIterator iter; !iter.Done(); ++iter)
350 iter->DocumentDetached(filter, document_id);
352 // Remove any queued shared workers for this document.
353 for (WorkerProcessHost::Instances::iterator iter = queued_workers_.begin();
354 iter != queued_workers_.end();) {
356 iter->worker_document_set()->Remove(filter, document_id);
357 if (iter->worker_document_set()->IsEmpty()) {
358 iter = queued_workers_.erase(iter);
365 bool WorkerServiceImpl::CreateWorkerFromInstance(
366 WorkerProcessHost::WorkerInstance instance) {
367 if (!CanCreateWorkerProcess(instance)) {
368 queued_workers_.push_back(instance);
372 // Remove any queued instances of this worker and copy over the filter to
374 for (WorkerProcessHost::Instances::iterator iter = queued_workers_.begin();
375 iter != queued_workers_.end();) {
376 if (iter->Matches(instance.url(), instance.name(),
377 instance.partition(), instance.resource_context())) {
378 DCHECK(iter->NumFilters() == 1);
379 DCHECK_EQ(instance.url(), iter->url());
380 WorkerProcessHost::WorkerInstance::FilterInfo filter_info =
382 instance.AddFilter(filter_info.filter(), filter_info.route_id());
383 iter = queued_workers_.erase(iter);
389 WorkerMessageFilter* first_filter = instance.filters().begin()->filter();
390 WorkerProcessHost* worker = new WorkerProcessHost(
391 instance.resource_context(), instance.partition());
392 // TODO(atwilson): This won't work if the message is from a worker process.
393 // We don't support that yet though (this message is only sent from
394 // renderers) but when we do, we'll need to add code to pass in the current
395 // worker's document set for nested workers.
396 if (!worker->Init(first_filter->render_process_id(),
397 instance.render_frame_id())) {
402 worker->CreateWorker(instance);
404 WorkerServiceObserver, observers_,
405 WorkerCreated(instance.url(), instance.name(), worker->GetData().id,
406 instance.worker_route_id()));
407 WorkerDevToolsManager::GetInstance()->WorkerCreated(worker, instance);
411 bool WorkerServiceImpl::CanCreateWorkerProcess(
412 const WorkerProcessHost::WorkerInstance& instance) {
413 // Worker can be fired off if *any* parent has room.
414 const WorkerDocumentSet::DocumentInfoSet& parents =
415 instance.worker_document_set()->documents();
417 for (WorkerDocumentSet::DocumentInfoSet::const_iterator parent_iter =
419 parent_iter != parents.end(); ++parent_iter) {
420 bool hit_total_worker_limit = false;
421 if (FrameCanCreateWorkerProcess(parent_iter->render_process_id(),
422 parent_iter->render_frame_id(),
423 &hit_total_worker_limit)) {
426 // Return false if already at the global worker limit (no need to continue
427 // checking parent tabs).
428 if (hit_total_worker_limit)
431 // If we've reached here, none of the parent tabs is allowed to create an
436 bool WorkerServiceImpl::FrameCanCreateWorkerProcess(
437 int render_process_id,
439 bool* hit_total_worker_limit) {
440 int total_workers = 0;
441 int workers_per_tab = 0;
442 *hit_total_worker_limit = false;
443 for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
444 for (WorkerProcessHost::Instances::const_iterator cur_instance =
445 iter->instances().begin();
446 cur_instance != iter->instances().end(); ++cur_instance) {
448 if (total_workers >= kMaxWorkersWhenSeparate) {
449 *hit_total_worker_limit = true;
452 if (cur_instance->FrameIsParent(render_process_id, render_frame_id)) {
454 if (workers_per_tab >= kMaxWorkersPerFrameWhenSeparate)
463 void WorkerServiceImpl::TryStartingQueuedWorker() {
464 if (queued_workers_.empty())
467 for (WorkerProcessHost::Instances::iterator i = queued_workers_.begin();
468 i != queued_workers_.end();) {
469 if (CanCreateWorkerProcess(*i)) {
470 WorkerProcessHost::WorkerInstance instance = *i;
471 queued_workers_.erase(i);
472 CreateWorkerFromInstance(instance);
474 // CreateWorkerFromInstance can modify the queued_workers_ list when it
475 // coalesces queued instances after starting a shared worker, so we
476 // have to rescan the list from the beginning (our iterator is now
477 // invalid). This is not a big deal as having any queued workers will be
478 // rare in practice so the list will be small.
479 i = queued_workers_.begin();
486 bool WorkerServiceImpl::GetRendererForWorker(int worker_process_id,
487 int* render_process_id,
488 int* render_frame_id) const {
489 for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
490 if (iter.GetData().id != worker_process_id)
493 // This code assumes one worker per process, see function comment in header!
494 WorkerProcessHost::Instances::const_iterator first_instance =
495 iter->instances().begin();
496 if (first_instance == iter->instances().end())
499 WorkerDocumentSet::DocumentInfoSet::const_iterator info =
500 first_instance->worker_document_set()->documents().begin();
501 *render_process_id = info->render_process_id();
502 *render_frame_id = info->render_frame_id();
508 const WorkerProcessHost::WorkerInstance* WorkerServiceImpl::FindWorkerInstance(
509 int worker_process_id) {
510 for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
511 if (iter.GetData().id != worker_process_id)
514 WorkerProcessHost::Instances::const_iterator instance =
515 iter->instances().begin();
516 return instance == iter->instances().end() ? NULL : &*instance;
521 bool WorkerServiceImpl::TerminateWorker(int process_id, int route_id) {
522 for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
523 if (iter.GetData().id == process_id) {
524 iter->TerminateWorker(route_id);
531 std::vector<WorkerService::WorkerInfo> WorkerServiceImpl::GetWorkers() {
532 std::vector<WorkerService::WorkerInfo> results;
533 for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
534 const WorkerProcessHost::Instances& instances = (*iter)->instances();
535 for (WorkerProcessHost::Instances::const_iterator i = instances.begin();
536 i != instances.end(); ++i) {
537 WorkerService::WorkerInfo info;
539 info.name = i->name();
540 info.route_id = i->worker_route_id();
541 info.process_id = iter.GetData().id;
542 info.handle = iter.GetData().handle;
543 results.push_back(info);
549 void WorkerServiceImpl::AddObserver(WorkerServiceObserver* observer) {
550 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
551 observers_.AddObserver(observer);
554 void WorkerServiceImpl::RemoveObserver(WorkerServiceObserver* observer) {
555 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
556 observers_.RemoveObserver(observer);
559 void WorkerServiceImpl::NotifyWorkerDestroyed(
560 WorkerProcessHost* process,
561 int worker_route_id) {
562 WorkerDevToolsManager::GetInstance()->WorkerDestroyed(
563 process, worker_route_id);
564 FOR_EACH_OBSERVER(WorkerServiceObserver, observers_,
565 WorkerDestroyed(process->GetData().id, worker_route_id));
568 void WorkerServiceImpl::NotifyWorkerProcessCreated() {
569 priority_setter_->NotifyWorkerProcessCreated();
572 WorkerProcessHost::WorkerInstance* WorkerServiceImpl::FindSharedWorkerInstance(
574 const base::string16& name,
575 const WorkerStoragePartition& partition,
576 ResourceContext* resource_context) {
577 for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
578 for (WorkerProcessHost::Instances::iterator instance_iter =
579 iter->mutable_instances().begin();
580 instance_iter != iter->mutable_instances().end();
582 if (instance_iter->Matches(url, name, partition, resource_context))
583 return &(*instance_iter);
589 } // namespace content