Upstream version 6.35.121.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 enabled = CommandLine::ForCurrentProcess()->HasSwitch(
240       switches::kEnableEmbeddedSharedWorker);
241   return enabled;
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(instance);
413   FOR_EACH_OBSERVER(
414       WorkerServiceObserver, observers_,
415       WorkerCreated(instance.url(), instance.name(), worker->GetData().id,
416                     instance.worker_route_id()));
417   WorkerDevToolsManager::GetInstance()->WorkerCreated(worker, instance);
418   return true;
419 }
420
421 bool WorkerServiceImpl::CanCreateWorkerProcess(
422     const WorkerProcessHost::WorkerInstance& instance) {
423   // Worker can be fired off if *any* parent has room.
424   const WorkerDocumentSet::DocumentInfoSet& parents =
425         instance.worker_document_set()->documents();
426
427   for (WorkerDocumentSet::DocumentInfoSet::const_iterator parent_iter =
428            parents.begin();
429        parent_iter != parents.end(); ++parent_iter) {
430     bool hit_total_worker_limit = false;
431     if (FrameCanCreateWorkerProcess(parent_iter->render_process_id(),
432                                     parent_iter->render_frame_id(),
433                                     &hit_total_worker_limit)) {
434       return true;
435     }
436     // Return false if already at the global worker limit (no need to continue
437     // checking parent tabs).
438     if (hit_total_worker_limit)
439       return false;
440   }
441   // If we've reached here, none of the parent tabs is allowed to create an
442   // instance.
443   return false;
444 }
445
446 bool WorkerServiceImpl::FrameCanCreateWorkerProcess(
447     int render_process_id,
448     int render_frame_id,
449     bool* hit_total_worker_limit) {
450   int total_workers = 0;
451   int workers_per_tab = 0;
452   *hit_total_worker_limit = false;
453   for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
454     for (WorkerProcessHost::Instances::const_iterator cur_instance =
455              iter->instances().begin();
456          cur_instance != iter->instances().end(); ++cur_instance) {
457       total_workers++;
458       if (total_workers >= kMaxWorkersWhenSeparate) {
459         *hit_total_worker_limit = true;
460         return false;
461       }
462       if (cur_instance->FrameIsParent(render_process_id, render_frame_id)) {
463         workers_per_tab++;
464         if (workers_per_tab >= kMaxWorkersPerFrameWhenSeparate)
465           return false;
466       }
467     }
468   }
469
470   return true;
471 }
472
473 void WorkerServiceImpl::TryStartingQueuedWorker() {
474   if (queued_workers_.empty())
475     return;
476
477   for (WorkerProcessHost::Instances::iterator i = queued_workers_.begin();
478        i != queued_workers_.end();) {
479     if (CanCreateWorkerProcess(*i)) {
480       WorkerProcessHost::WorkerInstance instance = *i;
481       queued_workers_.erase(i);
482       CreateWorkerFromInstance(instance);
483
484       // CreateWorkerFromInstance can modify the queued_workers_ list when it
485       // coalesces queued instances after starting a shared worker, so we
486       // have to rescan the list from the beginning (our iterator is now
487       // invalid). This is not a big deal as having any queued workers will be
488       // rare in practice so the list will be small.
489       i = queued_workers_.begin();
490     } else {
491       ++i;
492     }
493   }
494 }
495
496 bool WorkerServiceImpl::GetRendererForWorker(int worker_process_id,
497                                              int* render_process_id,
498                                              int* render_frame_id) const {
499   for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
500     if (iter.GetData().id != worker_process_id)
501       continue;
502
503     // This code assumes one worker per process, see function comment in header!
504     WorkerProcessHost::Instances::const_iterator first_instance =
505         iter->instances().begin();
506     if (first_instance == iter->instances().end())
507       return false;
508
509     WorkerDocumentSet::DocumentInfoSet::const_iterator info =
510         first_instance->worker_document_set()->documents().begin();
511     *render_process_id = info->render_process_id();
512     *render_frame_id = info->render_frame_id();
513     return true;
514   }
515   return false;
516 }
517
518 const WorkerProcessHost::WorkerInstance* WorkerServiceImpl::FindWorkerInstance(
519       int worker_process_id) {
520   for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
521     if (iter.GetData().id != worker_process_id)
522         continue;
523
524     WorkerProcessHost::Instances::const_iterator instance =
525         iter->instances().begin();
526     return instance == iter->instances().end() ? NULL : &*instance;
527   }
528   return NULL;
529 }
530
531 bool WorkerServiceImpl::TerminateWorker(int process_id, int route_id) {
532   for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
533     if (iter.GetData().id == process_id) {
534       iter->TerminateWorker(route_id);
535       return true;
536     }
537   }
538   return false;
539 }
540
541 std::vector<WorkerService::WorkerInfo> WorkerServiceImpl::GetWorkers() {
542   std::vector<WorkerService::WorkerInfo> results;
543   for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
544     const WorkerProcessHost::Instances& instances = (*iter)->instances();
545     for (WorkerProcessHost::Instances::const_iterator i = instances.begin();
546          i != instances.end(); ++i) {
547       WorkerService::WorkerInfo info;
548       info.url = i->url();
549       info.name = i->name();
550       info.route_id = i->worker_route_id();
551       info.process_id = iter.GetData().id;
552       info.handle = iter.GetData().handle;
553       results.push_back(info);
554     }
555   }
556   return results;
557 }
558
559 void WorkerServiceImpl::AddObserver(WorkerServiceObserver* observer) {
560   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
561   observers_.AddObserver(observer);
562 }
563
564 void WorkerServiceImpl::RemoveObserver(WorkerServiceObserver* observer) {
565   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
566   observers_.RemoveObserver(observer);
567 }
568
569 void WorkerServiceImpl::NotifyWorkerDestroyed(
570     WorkerProcessHost* process,
571     int worker_route_id) {
572   WorkerDevToolsManager::GetInstance()->WorkerDestroyed(
573       process, worker_route_id);
574   FOR_EACH_OBSERVER(WorkerServiceObserver, observers_,
575                     WorkerDestroyed(process->GetData().id, worker_route_id));
576 }
577
578 void WorkerServiceImpl::NotifyWorkerProcessCreated() {
579   priority_setter_->NotifyWorkerProcessCreated();
580 }
581
582 WorkerProcessHost::WorkerInstance* WorkerServiceImpl::FindSharedWorkerInstance(
583     const GURL& url,
584     const base::string16& name,
585     const WorkerStoragePartition& partition,
586     ResourceContext* resource_context) {
587   for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
588     for (WorkerProcessHost::Instances::iterator instance_iter =
589              iter->mutable_instances().begin();
590          instance_iter != iter->mutable_instances().end();
591          ++instance_iter) {
592       if (instance_iter->Matches(url, name, partition, resource_context))
593         return &(*instance_iter);
594     }
595   }
596   return NULL;
597 }
598
599 }  // namespace content