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