f2ac3735ae789c4dca1d7338ad2d67c6510d52c0
[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/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"
32
33 namespace content {
34
35 namespace {
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()));
41 }
42 }
43
44 const int WorkerServiceImpl::kMaxWorkersWhenSeparate = 64;
45 const int WorkerServiceImpl::kMaxWorkersPerFrameWhenSeparate = 16;
46
47 class WorkerPrioritySetter
48     : public NotificationObserver,
49       public base::RefCountedThreadSafe<WorkerPrioritySetter,
50                                         BrowserThread::DeleteOnUIThread> {
51  public:
52   WorkerPrioritySetter();
53
54   // Posts a task to the UI thread to register to receive notifications.
55   void Initialize();
56
57   // Invoked by WorkerServiceImpl when a worker process is created.
58   void NotifyWorkerProcessCreated();
59
60  private:
61   friend class base::RefCountedThreadSafe<WorkerPrioritySetter>;
62   friend struct BrowserThread::DeleteOnThread<BrowserThread::UI>;
63   friend class base::DeleteHelper<WorkerPrioritySetter>;
64   virtual ~WorkerPrioritySetter();
65
66   // Posts a task to perform a worker priority update.
67   void PostTaskToGatherAndUpdateWorkerPriorities();
68
69   // Gathers up a list of the visible tabs and then updates priorities for
70   // all the shared workers.
71   void GatherVisibleIDsAndUpdateWorkerPriorities();
72
73   // Registers as an observer to receive notifications about
74   // widgets being shown.
75   void RegisterObserver();
76
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);
81
82   // Called to refresh worker priorities when focus changes between tabs.
83   void OnRenderWidgetVisibilityChanged(std::pair<int, int>);
84
85   // NotificationObserver implementation.
86   virtual void Observe(int type,
87                        const NotificationSource& source,
88                        const NotificationDetails& details) OVERRIDE;
89
90   NotificationRegistrar registrar_;
91 };
92
93 WorkerPrioritySetter::WorkerPrioritySetter() {
94 }
95
96 WorkerPrioritySetter::~WorkerPrioritySetter() {
97   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
98 }
99
100 void WorkerPrioritySetter::Initialize() {
101   BrowserThread::PostTask(
102       BrowserThread::UI, FROM_HERE,
103       base::Bind(&WorkerPrioritySetter::RegisterObserver, this));
104 }
105
106 void WorkerPrioritySetter::NotifyWorkerProcessCreated() {
107   PostTaskToGatherAndUpdateWorkerPriorities();
108 }
109
110 void WorkerPrioritySetter::PostTaskToGatherAndUpdateWorkerPriorities() {
111   BrowserThread::PostTask(
112       BrowserThread::UI, FROM_HERE,
113       base::Bind(
114           &WorkerPrioritySetter::GatherVisibleIDsAndUpdateWorkerPriorities,
115           this));
116 }
117
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> >();
122
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)
128       continue;
129     if (!widget->IsRenderView())
130       continue;
131
132     RenderWidgetHostView* widget_view = widget->GetView();
133     if (!widget_view || !widget_view->IsShowing())
134       continue;
135     RenderViewHost* rvh = RenderViewHost::From(widget);
136     WebContents* web_contents = WebContents::FromRenderViewHost(rvh);
137     if (!web_contents)
138       continue;
139     web_contents->ForEachFrame(
140         base::Bind(&AddRenderFrameID, visible_frame_ids));
141   }
142
143   BrowserThread::PostTask(
144       BrowserThread::IO, FROM_HERE,
145       base::Bind(&WorkerPrioritySetter::UpdateWorkerPrioritiesFromVisibleSet,
146                  this, base::Owned(visible_frame_ids)));
147 }
148
149 void WorkerPrioritySetter::UpdateWorkerPrioritiesFromVisibleSet(
150     const std::set<std::pair<int, int> >* visible_frame_ids) {
151   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
152
153   for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
154     if (!iter->process_launched())
155       continue;
156     bool throttle = true;
157
158     for (WorkerProcessHost::Instances::const_iterator instance =
159         iter->instances().begin(); instance != iter->instances().end();
160         ++instance) {
161
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())
166         continue;
167
168       WorkerDocumentSet::DocumentInfoSet::const_iterator info =
169           first_instance->worker_document_set()->documents().begin();
170
171       for (; info != first_instance->worker_document_set()->documents().end();
172           ++info) {
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()) {
176           throttle = false;
177           break;
178         }
179       }
180
181       if (!throttle ) {
182         break;
183       }
184     }
185
186     iter->SetBackgrounded(throttle);
187   }
188 }
189
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;
194
195   visible_frame_ids.insert(id);
196
197   UpdateWorkerPrioritiesFromVisibleSet(&visible_frame_ids);
198 }
199
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());
206 }
207
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();
212
213     if (visible) {
214       int render_widget_id =
215           Source<RenderWidgetHost>(source).ptr()->GetRoutingID();
216       int render_process_pid =
217           Source<RenderWidgetHost>(source).ptr()->GetProcess()->GetID();
218
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)));
223     }
224   }
225   else if (type == NOTIFICATION_RENDERER_PROCESS_CREATED) {
226     PostTaskToGatherAndUpdateWorkerPriorities();
227   }
228 }
229
230 WorkerService* WorkerService::GetInstance() {
231   return WorkerServiceImpl::GetInstance();
232 }
233
234 WorkerServiceImpl* WorkerServiceImpl::GetInstance() {
235   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
236   return Singleton<WorkerServiceImpl>::get();
237 }
238
239 WorkerServiceImpl::WorkerServiceImpl()
240     : priority_setter_(new WorkerPrioritySetter()),
241       next_worker_route_id_(0) {
242   priority_setter_->Initialize();
243 }
244
245 WorkerServiceImpl::~WorkerServiceImpl() {
246   // The observers in observers_ can't be used here because they might be
247   // gone already.
248 }
249
250 void WorkerServiceImpl::PerformTeardownForTesting() {
251   priority_setter_ = NULL;
252 }
253
254 void WorkerServiceImpl::OnWorkerMessageFilterClosing(
255     WorkerMessageFilter* filter) {
256   for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
257     iter->FilterShutdown(filter);
258   }
259
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);
266     } else {
267       ++i;
268     }
269   }
270
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();
276 }
277
278 void WorkerServiceImpl::CreateWorker(
279     const ViewHostMsg_CreateWorker_Params& params,
280     int route_id,
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;
293       return;
294     }
295     if (existing_instance->load_failed()) {
296       filter->Send(new ViewMsg_WorkerScriptLoadFailed(route_id));
297       return;
298     }
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));
304     return;
305   }
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;
311       return;
312     }
313   }
314
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
318   // it to.
319   WorkerProcessHost::WorkerInstance instance(
320       params.url,
321       params.name,
322       params.content_security_policy,
323       params.security_policy_type,
324       next_worker_route_id(),
325       params.render_frame_route_id,
326       resource_context,
327       partition);
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);
332
333   CreateWorkerFromInstance(instance);
334 }
335
336 void WorkerServiceImpl::ForwardToWorker(const IPC::Message& message,
337                                         WorkerMessageFilter* filter) {
338   for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
339     if (iter->FilterMessage(message, filter))
340       return;
341   }
342
343   // TODO(jabdelmalek): tell filter that callee is gone
344 }
345
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);
351
352   // Remove any queued shared workers for this document.
353   for (WorkerProcessHost::Instances::iterator iter = queued_workers_.begin();
354        iter != queued_workers_.end();) {
355
356     iter->worker_document_set()->Remove(filter, document_id);
357     if (iter->worker_document_set()->IsEmpty()) {
358       iter = queued_workers_.erase(iter);
359       continue;
360     }
361     ++iter;
362   }
363 }
364
365 bool WorkerServiceImpl::CreateWorkerFromInstance(
366     WorkerProcessHost::WorkerInstance instance) {
367   if (!CanCreateWorkerProcess(instance)) {
368     queued_workers_.push_back(instance);
369     return true;
370   }
371
372   // Remove any queued instances of this worker and copy over the filter to
373   // this instance.
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 =
381           iter->GetFilter();
382       instance.AddFilter(filter_info.filter(), filter_info.route_id());
383       iter = queued_workers_.erase(iter);
384     } else {
385       ++iter;
386     }
387   }
388
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())) {
398     delete worker;
399     return false;
400   }
401
402   worker->CreateWorker(instance);
403   FOR_EACH_OBSERVER(
404       WorkerServiceObserver, observers_,
405       WorkerCreated(instance.url(), instance.name(), worker->GetData().id,
406                     instance.worker_route_id()));
407   WorkerDevToolsManager::GetInstance()->WorkerCreated(worker, instance);
408   return true;
409 }
410
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();
416
417   for (WorkerDocumentSet::DocumentInfoSet::const_iterator parent_iter =
418            parents.begin();
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)) {
424       return true;
425     }
426     // Return false if already at the global worker limit (no need to continue
427     // checking parent tabs).
428     if (hit_total_worker_limit)
429       return false;
430   }
431   // If we've reached here, none of the parent tabs is allowed to create an
432   // instance.
433   return false;
434 }
435
436 bool WorkerServiceImpl::FrameCanCreateWorkerProcess(
437     int render_process_id,
438     int render_frame_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) {
447       total_workers++;
448       if (total_workers >= kMaxWorkersWhenSeparate) {
449         *hit_total_worker_limit = true;
450         return false;
451       }
452       if (cur_instance->FrameIsParent(render_process_id, render_frame_id)) {
453         workers_per_tab++;
454         if (workers_per_tab >= kMaxWorkersPerFrameWhenSeparate)
455           return false;
456       }
457     }
458   }
459
460   return true;
461 }
462
463 void WorkerServiceImpl::TryStartingQueuedWorker() {
464   if (queued_workers_.empty())
465     return;
466
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);
473
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();
480     } else {
481       ++i;
482     }
483   }
484 }
485
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)
491       continue;
492
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())
497       return false;
498
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();
503     return true;
504   }
505   return false;
506 }
507
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)
512         continue;
513
514     WorkerProcessHost::Instances::const_iterator instance =
515         iter->instances().begin();
516     return instance == iter->instances().end() ? NULL : &*instance;
517   }
518   return NULL;
519 }
520
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);
525       return true;
526     }
527   }
528   return false;
529 }
530
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;
538       info.url = i->url();
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);
544     }
545   }
546   return results;
547 }
548
549 void WorkerServiceImpl::AddObserver(WorkerServiceObserver* observer) {
550   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
551   observers_.AddObserver(observer);
552 }
553
554 void WorkerServiceImpl::RemoveObserver(WorkerServiceObserver* observer) {
555   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
556   observers_.RemoveObserver(observer);
557 }
558
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));
566 }
567
568 void WorkerServiceImpl::NotifyWorkerProcessCreated() {
569   priority_setter_->NotifyWorkerProcessCreated();
570 }
571
572 WorkerProcessHost::WorkerInstance* WorkerServiceImpl::FindSharedWorkerInstance(
573     const GURL& url,
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();
581          ++instance_iter) {
582       if (instance_iter->Matches(url, name, partition, resource_context))
583         return &(*instance_iter);
584     }
585   }
586   return NULL;
587 }
588
589 }  // namespace content