Upstream version 9.37.197.0
[platform/framework/web/crosswalk.git] / src / content / browser / worker_host / worker_service_impl.cc
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.
4
5 #include "content/browser/worker_host/worker_service_impl.h"
6
7 #include <string>
8
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/shared_worker/shared_worker_service_impl.h"
15 #include "content/browser/worker_host/worker_message_filter.h"
16 #include "content/browser/worker_host/worker_process_host.h"
17 #include "content/common/view_messages.h"
18 #include "content/common/worker_messages.h"
19 #include "content/public/browser/child_process_data.h"
20 #include "content/public/browser/notification_service.h"
21 #include "content/public/browser/notification_types.h"
22 #include "content/public/browser/render_frame_host.h"
23 #include "content/public/browser/render_process_host.h"
24 #include "content/public/browser/render_view_host.h"
25 #include "content/public/browser/render_widget_host.h"
26 #include "content/public/browser/render_widget_host_iterator.h"
27 #include "content/public/browser/render_widget_host_view.h"
28 #include "content/public/browser/resource_context.h"
29 #include "content/public/browser/web_contents.h"
30 #include "content/public/browser/worker_service_observer.h"
31 #include "content/public/common/content_switches.h"
32 #include "content/public/common/process_type.h"
33
34 namespace content {
35
36 namespace {
37 void AddRenderFrameID(std::set<std::pair<int, int> >* visible_frame_ids,
38                       RenderFrameHost* rfh) {
39   visible_frame_ids->insert(
40       std::pair<int, int>(rfh->GetProcess()->GetID(),
41                           rfh->GetRoutingID()));
42 }
43 }
44
45 const int WorkerServiceImpl::kMaxWorkersWhenSeparate = 64;
46 const int WorkerServiceImpl::kMaxWorkersPerFrameWhenSeparate = 16;
47
48 class WorkerPrioritySetter
49     : public NotificationObserver,
50       public base::RefCountedThreadSafe<WorkerPrioritySetter,
51                                         BrowserThread::DeleteOnUIThread> {
52  public:
53   WorkerPrioritySetter();
54
55   // Posts a task to the UI thread to register to receive notifications.
56   void Initialize();
57
58   // Invoked by WorkerServiceImpl when a worker process is created.
59   void NotifyWorkerProcessCreated();
60
61  private:
62   friend class base::RefCountedThreadSafe<WorkerPrioritySetter>;
63   friend struct BrowserThread::DeleteOnThread<BrowserThread::UI>;
64   friend class base::DeleteHelper<WorkerPrioritySetter>;
65   virtual ~WorkerPrioritySetter();
66
67   // Posts a task to perform a worker priority update.
68   void PostTaskToGatherAndUpdateWorkerPriorities();
69
70   // Gathers up a list of the visible tabs and then updates priorities for
71   // all the shared workers.
72   void GatherVisibleIDsAndUpdateWorkerPriorities();
73
74   // Registers as an observer to receive notifications about
75   // widgets being shown.
76   void RegisterObserver();
77
78   // Sets priorities for shared workers given a set of visible frames (as a
79   // std::set of std::pair<render_process, render_frame> ids.
80   void UpdateWorkerPrioritiesFromVisibleSet(
81       const std::set<std::pair<int, int> >* visible);
82
83   // Called to refresh worker priorities when focus changes between tabs.
84   void OnRenderWidgetVisibilityChanged(std::pair<int, int>);
85
86   // NotificationObserver implementation.
87   virtual void Observe(int type,
88                        const NotificationSource& source,
89                        const NotificationDetails& details) OVERRIDE;
90
91   NotificationRegistrar registrar_;
92 };
93
94 WorkerPrioritySetter::WorkerPrioritySetter() {
95 }
96
97 WorkerPrioritySetter::~WorkerPrioritySetter() {
98   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
99 }
100
101 void WorkerPrioritySetter::Initialize() {
102   BrowserThread::PostTask(
103       BrowserThread::UI, FROM_HERE,
104       base::Bind(&WorkerPrioritySetter::RegisterObserver, this));
105 }
106
107 void WorkerPrioritySetter::NotifyWorkerProcessCreated() {
108   PostTaskToGatherAndUpdateWorkerPriorities();
109 }
110
111 void WorkerPrioritySetter::PostTaskToGatherAndUpdateWorkerPriorities() {
112   BrowserThread::PostTask(
113       BrowserThread::UI, FROM_HERE,
114       base::Bind(
115           &WorkerPrioritySetter::GatherVisibleIDsAndUpdateWorkerPriorities,
116           this));
117 }
118
119 void WorkerPrioritySetter::GatherVisibleIDsAndUpdateWorkerPriorities() {
120   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
121   std::set<std::pair<int, int> >* visible_frame_ids =
122       new std::set<std::pair<int, int> >();
123
124   // Gather up all the visible renderer process/view pairs
125   scoped_ptr<RenderWidgetHostIterator> widgets(
126       RenderWidgetHost::GetRenderWidgetHosts());
127   while (RenderWidgetHost* widget = widgets->GetNextHost()) {
128     if (widget->GetProcess()->VisibleWidgetCount() == 0)
129       continue;
130     if (!widget->IsRenderView())
131       continue;
132
133     RenderWidgetHostView* widget_view = widget->GetView();
134     if (!widget_view || !widget_view->IsShowing())
135       continue;
136     RenderViewHost* rvh = RenderViewHost::From(widget);
137     WebContents* web_contents = WebContents::FromRenderViewHost(rvh);
138     if (!web_contents)
139       continue;
140     web_contents->ForEachFrame(
141         base::Bind(&AddRenderFrameID, visible_frame_ids));
142   }
143
144   BrowserThread::PostTask(
145       BrowserThread::IO, FROM_HERE,
146       base::Bind(&WorkerPrioritySetter::UpdateWorkerPrioritiesFromVisibleSet,
147                  this, base::Owned(visible_frame_ids)));
148 }
149
150 void WorkerPrioritySetter::UpdateWorkerPrioritiesFromVisibleSet(
151     const std::set<std::pair<int, int> >* visible_frame_ids) {
152   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
153
154   for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
155     if (!iter->process_launched())
156       continue;
157     bool throttle = true;
158
159     for (WorkerProcessHost::Instances::const_iterator instance =
160         iter->instances().begin(); instance != iter->instances().end();
161         ++instance) {
162
163       // This code assumes one worker per process
164       WorkerProcessHost::Instances::const_iterator first_instance =
165           iter->instances().begin();
166       if (first_instance == iter->instances().end())
167         continue;
168
169       WorkerDocumentSet::DocumentInfoSet::const_iterator info =
170           first_instance->worker_document_set()->documents().begin();
171
172       for (; info != first_instance->worker_document_set()->documents().end();
173           ++info) {
174         std::pair<int, int> id(
175             info->render_process_id(), info->render_frame_id());
176         if (visible_frame_ids->find(id) != visible_frame_ids->end()) {
177           throttle = false;
178           break;
179         }
180       }
181
182       if (!throttle ) {
183         break;
184       }
185     }
186
187     iter->SetBackgrounded(throttle);
188   }
189 }
190
191 void WorkerPrioritySetter::OnRenderWidgetVisibilityChanged(
192     std::pair<int, int> id) {
193   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
194   std::set<std::pair<int, int> > visible_frame_ids;
195
196   visible_frame_ids.insert(id);
197
198   UpdateWorkerPrioritiesFromVisibleSet(&visible_frame_ids);
199 }
200
201 void WorkerPrioritySetter::RegisterObserver() {
202   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
203   registrar_.Add(this, NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED,
204                  NotificationService::AllBrowserContextsAndSources());
205   registrar_.Add(this, NOTIFICATION_RENDERER_PROCESS_CREATED,
206                  NotificationService::AllBrowserContextsAndSources());
207 }
208
209 void WorkerPrioritySetter::Observe(int type,
210     const NotificationSource& source, const NotificationDetails& details) {
211   if (type == NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED) {
212     bool visible = *Details<bool>(details).ptr();
213
214     if (visible) {
215       int render_widget_id =
216           Source<RenderWidgetHost>(source).ptr()->GetRoutingID();
217       int render_process_pid =
218           Source<RenderWidgetHost>(source).ptr()->GetProcess()->GetID();
219
220       BrowserThread::PostTask(
221           BrowserThread::IO, FROM_HERE,
222           base::Bind(&WorkerPrioritySetter::OnRenderWidgetVisibilityChanged,
223               this, std::pair<int, int>(render_process_pid, render_widget_id)));
224     }
225   }
226   else if (type == NOTIFICATION_RENDERER_PROCESS_CREATED) {
227     PostTaskToGatherAndUpdateWorkerPriorities();
228   }
229 }
230
231 WorkerService* WorkerService::GetInstance() {
232   if (EmbeddedSharedWorkerEnabled())
233     return SharedWorkerServiceImpl::GetInstance();
234   else
235     return WorkerServiceImpl::GetInstance();
236 }
237
238 bool WorkerService::EmbeddedSharedWorkerEnabled() {
239   static bool disabled = CommandLine::ForCurrentProcess()->HasSwitch(
240       switches::kDisableEmbeddedSharedWorker);
241   return !disabled;
242 }
243
244 WorkerServiceImpl* WorkerServiceImpl::GetInstance() {
245   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
246   return Singleton<WorkerServiceImpl>::get();
247 }
248
249 WorkerServiceImpl::WorkerServiceImpl()
250     : priority_setter_(new WorkerPrioritySetter()),
251       next_worker_route_id_(0) {
252   priority_setter_->Initialize();
253 }
254
255 WorkerServiceImpl::~WorkerServiceImpl() {
256   // The observers in observers_ can't be used here because they might be
257   // gone already.
258 }
259
260 void WorkerServiceImpl::PerformTeardownForTesting() {
261   priority_setter_ = NULL;
262 }
263
264 void WorkerServiceImpl::OnWorkerMessageFilterClosing(
265     WorkerMessageFilter* filter) {
266   for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
267     iter->FilterShutdown(filter);
268   }
269
270   // See if that process had any queued workers.
271   for (WorkerProcessHost::Instances::iterator i = queued_workers_.begin();
272        i != queued_workers_.end();) {
273     i->RemoveFilters(filter);
274     if (i->NumFilters() == 0) {
275       i = queued_workers_.erase(i);
276     } else {
277       ++i;
278     }
279   }
280
281   // Either a worker proceess has shut down, in which case we can start one of
282   // the queued workers, or a renderer has shut down, in which case it doesn't
283   // affect anything.  We call this function in both scenarios because then we
284   // don't have to keep track which filters are from worker processes.
285   TryStartingQueuedWorker();
286 }
287
288 void WorkerServiceImpl::CreateWorker(
289     const ViewHostMsg_CreateWorker_Params& params,
290     int route_id,
291     WorkerMessageFilter* filter,
292     ResourceContext* resource_context,
293     const WorkerStoragePartition& partition,
294     bool* url_mismatch) {
295   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
296   *url_mismatch = false;
297   WorkerProcessHost::WorkerInstance* existing_instance =
298       FindSharedWorkerInstance(
299           params.url, params.name, partition, resource_context);
300   if (existing_instance) {
301     if (params.url != existing_instance->url()) {
302       *url_mismatch = true;
303       return;
304     }
305     if (existing_instance->load_failed()) {
306       filter->Send(new ViewMsg_WorkerScriptLoadFailed(route_id));
307       return;
308     }
309     existing_instance->AddFilter(filter, route_id);
310     existing_instance->worker_document_set()->Add(
311         filter, params.document_id, filter->render_process_id(),
312         params.render_frame_route_id);
313     filter->Send(new ViewMsg_WorkerCreated(route_id));
314     return;
315   }
316   for (WorkerProcessHost::Instances::iterator i = queued_workers_.begin();
317        i != queued_workers_.end(); ++i) {
318     if (i->Matches(params.url, params.name, partition, resource_context) &&
319         params.url != i->url()) {
320       *url_mismatch = true;
321       return;
322     }
323   }
324
325   // Generate a unique route id for the browser-worker communication that's
326   // unique among all worker processes.  That way when the worker process sends
327   // a wrapped IPC message through us, we know which WorkerProcessHost to give
328   // it to.
329   WorkerProcessHost::WorkerInstance instance(
330       params.url,
331       params.name,
332       params.content_security_policy,
333       params.security_policy_type,
334       next_worker_route_id(),
335       params.render_frame_route_id,
336       resource_context,
337       partition);
338   instance.AddFilter(filter, route_id);
339   instance.worker_document_set()->Add(
340       filter, params.document_id, filter->render_process_id(),
341       params.render_frame_route_id);
342
343   CreateWorkerFromInstance(instance);
344 }
345
346 void WorkerServiceImpl::ForwardToWorker(const IPC::Message& message,
347                                         WorkerMessageFilter* filter) {
348   for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
349     if (iter->FilterMessage(message, filter))
350       return;
351   }
352
353   // TODO(jabdelmalek): tell filter that callee is gone
354 }
355
356 void WorkerServiceImpl::DocumentDetached(unsigned long long document_id,
357                                          WorkerMessageFilter* filter) {
358   // Any associated shared workers can be shut down.
359   for (WorkerProcessHostIterator iter; !iter.Done(); ++iter)
360     iter->DocumentDetached(filter, document_id);
361
362   // Remove any queued shared workers for this document.
363   for (WorkerProcessHost::Instances::iterator iter = queued_workers_.begin();
364        iter != queued_workers_.end();) {
365
366     iter->worker_document_set()->Remove(filter, document_id);
367     if (iter->worker_document_set()->IsEmpty()) {
368       iter = queued_workers_.erase(iter);
369       continue;
370     }
371     ++iter;
372   }
373 }
374
375 bool WorkerServiceImpl::CreateWorkerFromInstance(
376     WorkerProcessHost::WorkerInstance instance) {
377   if (!CanCreateWorkerProcess(instance)) {
378     queued_workers_.push_back(instance);
379     return true;
380   }
381
382   // Remove any queued instances of this worker and copy over the filter to
383   // this instance.
384   for (WorkerProcessHost::Instances::iterator iter = queued_workers_.begin();
385        iter != queued_workers_.end();) {
386     if (iter->Matches(instance.url(), instance.name(),
387                       instance.partition(), instance.resource_context())) {
388       DCHECK(iter->NumFilters() == 1);
389       DCHECK_EQ(instance.url(), iter->url());
390       WorkerProcessHost::WorkerInstance::FilterInfo filter_info =
391           iter->GetFilter();
392       instance.AddFilter(filter_info.filter(), filter_info.route_id());
393       iter = queued_workers_.erase(iter);
394     } else {
395       ++iter;
396     }
397   }
398
399   WorkerMessageFilter* first_filter = instance.filters().begin()->filter();
400   WorkerProcessHost* worker = new WorkerProcessHost(
401       instance.resource_context(), instance.partition());
402   // TODO(atwilson): This won't work if the message is from a worker process.
403   // We don't support that yet though (this message is only sent from
404   // renderers) but when we do, we'll need to add code to pass in the current
405   // worker's document set for nested workers.
406   if (!worker->Init(first_filter->render_process_id(),
407                     instance.render_frame_id())) {
408     delete worker;
409     return false;
410   }
411
412   worker->CreateWorker(
413       instance,
414       WorkerDevToolsManager::GetInstance()->WorkerCreated(worker, instance));
415   FOR_EACH_OBSERVER(
416       WorkerServiceObserver, observers_,
417       WorkerCreated(instance.url(), instance.name(), worker->GetData().id,
418                     instance.worker_route_id()));
419   return true;
420 }
421
422 bool WorkerServiceImpl::CanCreateWorkerProcess(
423     const WorkerProcessHost::WorkerInstance& instance) {
424   // Worker can be fired off if *any* parent has room.
425   const WorkerDocumentSet::DocumentInfoSet& parents =
426         instance.worker_document_set()->documents();
427
428   for (WorkerDocumentSet::DocumentInfoSet::const_iterator parent_iter =
429            parents.begin();
430        parent_iter != parents.end(); ++parent_iter) {
431     bool hit_total_worker_limit = false;
432     if (FrameCanCreateWorkerProcess(parent_iter->render_process_id(),
433                                     parent_iter->render_frame_id(),
434                                     &hit_total_worker_limit)) {
435       return true;
436     }
437     // Return false if already at the global worker limit (no need to continue
438     // checking parent tabs).
439     if (hit_total_worker_limit)
440       return false;
441   }
442   // If we've reached here, none of the parent tabs is allowed to create an
443   // instance.
444   return false;
445 }
446
447 bool WorkerServiceImpl::FrameCanCreateWorkerProcess(
448     int render_process_id,
449     int render_frame_id,
450     bool* hit_total_worker_limit) {
451   int total_workers = 0;
452   int workers_per_tab = 0;
453   *hit_total_worker_limit = false;
454   for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
455     for (WorkerProcessHost::Instances::const_iterator cur_instance =
456              iter->instances().begin();
457          cur_instance != iter->instances().end(); ++cur_instance) {
458       total_workers++;
459       if (total_workers >= kMaxWorkersWhenSeparate) {
460         *hit_total_worker_limit = true;
461         return false;
462       }
463       if (cur_instance->FrameIsParent(render_process_id, render_frame_id)) {
464         workers_per_tab++;
465         if (workers_per_tab >= kMaxWorkersPerFrameWhenSeparate)
466           return false;
467       }
468     }
469   }
470
471   return true;
472 }
473
474 void WorkerServiceImpl::TryStartingQueuedWorker() {
475   if (queued_workers_.empty())
476     return;
477
478   for (WorkerProcessHost::Instances::iterator i = queued_workers_.begin();
479        i != queued_workers_.end();) {
480     if (CanCreateWorkerProcess(*i)) {
481       WorkerProcessHost::WorkerInstance instance = *i;
482       queued_workers_.erase(i);
483       CreateWorkerFromInstance(instance);
484
485       // CreateWorkerFromInstance can modify the queued_workers_ list when it
486       // coalesces queued instances after starting a shared worker, so we
487       // have to rescan the list from the beginning (our iterator is now
488       // invalid). This is not a big deal as having any queued workers will be
489       // rare in practice so the list will be small.
490       i = queued_workers_.begin();
491     } else {
492       ++i;
493     }
494   }
495 }
496
497 bool WorkerServiceImpl::GetRendererForWorker(int worker_process_id,
498                                              int* render_process_id,
499                                              int* render_frame_id) const {
500   for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
501     if (iter.GetData().id != worker_process_id)
502       continue;
503
504     // This code assumes one worker per process, see function comment in header!
505     WorkerProcessHost::Instances::const_iterator first_instance =
506         iter->instances().begin();
507     if (first_instance == iter->instances().end())
508       return false;
509
510     WorkerDocumentSet::DocumentInfoSet::const_iterator info =
511         first_instance->worker_document_set()->documents().begin();
512     *render_process_id = info->render_process_id();
513     *render_frame_id = info->render_frame_id();
514     return true;
515   }
516   return false;
517 }
518
519 const WorkerProcessHost::WorkerInstance* WorkerServiceImpl::FindWorkerInstance(
520       int worker_process_id) {
521   for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
522     if (iter.GetData().id != worker_process_id)
523         continue;
524
525     WorkerProcessHost::Instances::const_iterator instance =
526         iter->instances().begin();
527     return instance == iter->instances().end() ? NULL : &*instance;
528   }
529   return NULL;
530 }
531
532 bool WorkerServiceImpl::TerminateWorker(int process_id, int route_id) {
533   for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
534     if (iter.GetData().id == process_id) {
535       iter->TerminateWorker(route_id);
536       return true;
537     }
538   }
539   return false;
540 }
541
542 std::vector<WorkerService::WorkerInfo> WorkerServiceImpl::GetWorkers() {
543   std::vector<WorkerService::WorkerInfo> results;
544   for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
545     const WorkerProcessHost::Instances& instances = (*iter)->instances();
546     for (WorkerProcessHost::Instances::const_iterator i = instances.begin();
547          i != instances.end(); ++i) {
548       WorkerService::WorkerInfo info;
549       info.url = i->url();
550       info.name = i->name();
551       info.route_id = i->worker_route_id();
552       info.process_id = iter.GetData().id;
553       info.handle = iter.GetData().handle;
554       results.push_back(info);
555     }
556   }
557   return results;
558 }
559
560 void WorkerServiceImpl::AddObserver(WorkerServiceObserver* observer) {
561   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
562   observers_.AddObserver(observer);
563 }
564
565 void WorkerServiceImpl::RemoveObserver(WorkerServiceObserver* observer) {
566   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
567   observers_.RemoveObserver(observer);
568 }
569
570 void WorkerServiceImpl::NotifyWorkerDestroyed(
571     WorkerProcessHost* process,
572     int worker_route_id) {
573   WorkerDevToolsManager::GetInstance()->WorkerDestroyed(
574       process, worker_route_id);
575   FOR_EACH_OBSERVER(WorkerServiceObserver, observers_,
576                     WorkerDestroyed(process->GetData().id, worker_route_id));
577 }
578
579 void WorkerServiceImpl::NotifyWorkerProcessCreated() {
580   priority_setter_->NotifyWorkerProcessCreated();
581 }
582
583 WorkerProcessHost::WorkerInstance* WorkerServiceImpl::FindSharedWorkerInstance(
584     const GURL& url,
585     const base::string16& name,
586     const WorkerStoragePartition& partition,
587     ResourceContext* resource_context) {
588   for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
589     for (WorkerProcessHost::Instances::iterator instance_iter =
590              iter->mutable_instances().begin();
591          instance_iter != iter->mutable_instances().end();
592          ++instance_iter) {
593       if (instance_iter->Matches(url, name, partition, resource_context))
594         return &(*instance_iter);
595     }
596   }
597   return NULL;
598 }
599
600 }  // namespace content