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
34namespace content {
35
36namespace {
37void 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
45const int WorkerServiceImpl::kMaxWorkersWhenSeparate = 64;
46const int WorkerServiceImpl::kMaxWorkersPerFrameWhenSeparate = 16;
47
48class 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
94WorkerPrioritySetter::WorkerPrioritySetter() {
95}
96
97WorkerPrioritySetter::~WorkerPrioritySetter() {
98  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
99}
100
101void WorkerPrioritySetter::Initialize() {
102  BrowserThread::PostTask(
103      BrowserThread::UI, FROM_HERE,
104      base::Bind(&WorkerPrioritySetter::RegisterObserver, this));
105}
106
107void WorkerPrioritySetter::NotifyWorkerProcessCreated() {
108  PostTaskToGatherAndUpdateWorkerPriorities();
109}
110
111void WorkerPrioritySetter::PostTaskToGatherAndUpdateWorkerPriorities() {
112  BrowserThread::PostTask(
113      BrowserThread::UI, FROM_HERE,
114      base::Bind(
115          &WorkerPrioritySetter::GatherVisibleIDsAndUpdateWorkerPriorities,
116          this));
117}
118
119void 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
150void 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
191void 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
201void 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
209void 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
231WorkerService* WorkerService::GetInstance() {
232  if (EmbeddedSharedWorkerEnabled())
233    return SharedWorkerServiceImpl::GetInstance();
234  else
235    return WorkerServiceImpl::GetInstance();
236}
237
238bool WorkerService::EmbeddedSharedWorkerEnabled() {
239  static bool disabled = CommandLine::ForCurrentProcess()->HasSwitch(
240      switches::kDisableEmbeddedSharedWorker);
241  return !disabled;
242}
243
244WorkerServiceImpl* WorkerServiceImpl::GetInstance() {
245  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
246  return Singleton<WorkerServiceImpl>::get();
247}
248
249WorkerServiceImpl::WorkerServiceImpl()
250    : priority_setter_(new WorkerPrioritySetter()),
251      next_worker_route_id_(0) {
252  priority_setter_->Initialize();
253}
254
255WorkerServiceImpl::~WorkerServiceImpl() {
256  // The observers in observers_ can't be used here because they might be
257  // gone already.
258}
259
260void WorkerServiceImpl::PerformTeardownForTesting() {
261  priority_setter_ = NULL;
262}
263
264void 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
288void 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
346void 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
356void 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
375bool 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
422bool 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
447bool 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
474void 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
497bool 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
519const 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
532bool 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
542std::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
560void WorkerServiceImpl::AddObserver(WorkerServiceObserver* observer) {
561  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
562  observers_.AddObserver(observer);
563}
564
565void WorkerServiceImpl::RemoveObserver(WorkerServiceObserver* observer) {
566  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
567  observers_.RemoveObserver(observer);
568}
569
570void 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
579void WorkerServiceImpl::NotifyWorkerProcessCreated() {
580  priority_setter_->NotifyWorkerProcessCreated();
581}
582
583WorkerProcessHost::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
601