1 // Copyright 2013 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/service_worker/service_worker_version.h" 6 7 #include "base/command_line.h" 8 #include "base/stl_util.h" 9 #include "base/strings/string16.h" 10 #include "content/browser/service_worker/embedded_worker_instance.h" 11 #include "content/browser/service_worker/embedded_worker_registry.h" 12 #include "content/browser/service_worker/service_worker_context_core.h" 13 #include "content/browser/service_worker/service_worker_registration.h" 14 #include "content/browser/service_worker/service_worker_utils.h" 15 #include "content/common/service_worker/service_worker_messages.h" 16 #include "content/public/browser/browser_thread.h" 17 #include "content/public/common/content_switches.h" 18 19 namespace content { 20 21 typedef ServiceWorkerVersion::StatusCallback StatusCallback; 22 typedef ServiceWorkerVersion::MessageCallback MessageCallback; 23 24 namespace { 25 26 // Default delay to stop the worker context after all documents that 27 // are associated to the worker are closed. 28 // (Note that if all references to the version is dropped the worker 29 // is also stopped without delay) 30 const int64 kStopWorkerDelay = 30; // 30 secs. 31 32 void RunSoon(const base::Closure& callback) { 33 if (!callback.is_null()) 34 base::MessageLoop::current()->PostTask(FROM_HERE, callback); 35 } 36 37 template <typename CallbackArray, typename Arg> 38 void RunCallbacks(ServiceWorkerVersion* version, 39 CallbackArray* callbacks_ptr, 40 const Arg& arg) { 41 CallbackArray callbacks; 42 callbacks.swap(*callbacks_ptr); 43 scoped_refptr<ServiceWorkerVersion> protect(version); 44 for (typename CallbackArray::const_iterator i = callbacks.begin(); 45 i != callbacks.end(); ++i) 46 (*i).Run(arg); 47 } 48 49 template <typename IDMAP, typename Method, typename Params> 50 void RunIDMapCallbacks(IDMAP* callbacks, Method method, const Params& params) { 51 typename IDMAP::iterator iter(callbacks); 52 while (!iter.IsAtEnd()) { 53 DispatchToMethod(iter.GetCurrentValue(), method, params); 54 iter.Advance(); 55 } 56 callbacks->Clear(); 57 } 58 59 // A callback adapter to start a |task| after StartWorker. 60 void RunTaskAfterStartWorker( 61 base::WeakPtr<ServiceWorkerVersion> version, 62 const StatusCallback& error_callback, 63 const base::Closure& task, 64 ServiceWorkerStatusCode status) { 65 if (status != SERVICE_WORKER_OK) { 66 if (!error_callback.is_null()) 67 error_callback.Run(status); 68 return; 69 } 70 if (version->running_status() != ServiceWorkerVersion::RUNNING) { 71 // We've tried to start the worker (and it has succeeded), but 72 // it looks it's not running yet. 73 NOTREACHED() << "The worker's not running after successful StartWorker"; 74 if (!error_callback.is_null()) 75 error_callback.Run(SERVICE_WORKER_ERROR_START_WORKER_FAILED); 76 return; 77 } 78 task.Run(); 79 } 80 81 void RunErrorFetchCallback(const ServiceWorkerVersion::FetchCallback& callback, 82 ServiceWorkerStatusCode status) { 83 callback.Run(status, 84 SERVICE_WORKER_FETCH_EVENT_RESULT_FALLBACK, 85 ServiceWorkerResponse()); 86 } 87 88 } // namespace 89 90 ServiceWorkerVersion::ServiceWorkerVersion( 91 ServiceWorkerRegistration* registration, 92 int64 version_id, 93 base::WeakPtr<ServiceWorkerContextCore> context) 94 : version_id_(version_id), 95 registration_id_(kInvalidServiceWorkerVersionId), 96 status_(NEW), 97 context_(context), 98 script_cache_map_(this, context), 99 weak_factory_(this) { 100 DCHECK(context_); 101 DCHECK(registration); 102 if (registration) { 103 registration_id_ = registration->id(); 104 script_url_ = registration->script_url(); 105 scope_ = registration->pattern(); 106 } 107 context_->AddLiveVersion(this); 108 embedded_worker_ = context_->embedded_worker_registry()->CreateWorker(); 109 embedded_worker_->AddListener(this); 110 } 111 112 ServiceWorkerVersion::~ServiceWorkerVersion() { 113 embedded_worker_->RemoveListener(this); 114 if (context_) 115 context_->RemoveLiveVersion(version_id_); 116 // EmbeddedWorker's dtor sends StopWorker if it's still running. 117 } 118 119 void ServiceWorkerVersion::SetStatus(Status status) { 120 if (status_ == status) 121 return; 122 123 status_ = status; 124 125 std::vector<base::Closure> callbacks; 126 callbacks.swap(status_change_callbacks_); 127 for (std::vector<base::Closure>::const_iterator i = callbacks.begin(); 128 i != callbacks.end(); ++i) { 129 (*i).Run(); 130 } 131 132 FOR_EACH_OBSERVER(Listener, listeners_, OnVersionStateChanged(this)); 133 } 134 135 void ServiceWorkerVersion::RegisterStatusChangeCallback( 136 const base::Closure& callback) { 137 status_change_callbacks_.push_back(callback); 138 } 139 140 ServiceWorkerVersionInfo ServiceWorkerVersion::GetInfo() { 141 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 142 return ServiceWorkerVersionInfo( 143 running_status(), 144 status(), 145 version_id(), 146 embedded_worker()->process_id(), 147 embedded_worker()->thread_id(), 148 embedded_worker()->worker_devtools_agent_route_id()); 149 } 150 151 void ServiceWorkerVersion::StartWorker(const StatusCallback& callback) { 152 StartWorkerWithCandidateProcesses(std::vector<int>(), callback); 153 } 154 155 void ServiceWorkerVersion::StartWorkerWithCandidateProcesses( 156 const std::vector<int>& possible_process_ids, 157 const StatusCallback& callback) { 158 switch (running_status()) { 159 case RUNNING: 160 RunSoon(base::Bind(callback, SERVICE_WORKER_OK)); 161 return; 162 case STOPPING: 163 RunSoon(base::Bind(callback, SERVICE_WORKER_ERROR_START_WORKER_FAILED)); 164 return; 165 case STOPPED: 166 case STARTING: 167 start_callbacks_.push_back(callback); 168 if (running_status() == STOPPED) { 169 embedded_worker_->Start( 170 version_id_, 171 scope_, 172 script_url_, 173 possible_process_ids, 174 base::Bind(&ServiceWorkerVersion::RunStartWorkerCallbacksOnError, 175 weak_factory_.GetWeakPtr())); 176 } 177 return; 178 } 179 } 180 181 void ServiceWorkerVersion::StopWorker(const StatusCallback& callback) { 182 if (running_status() == STOPPED) { 183 RunSoon(base::Bind(callback, SERVICE_WORKER_OK)); 184 return; 185 } 186 if (stop_callbacks_.empty()) { 187 ServiceWorkerStatusCode status = embedded_worker_->Stop(); 188 if (status != SERVICE_WORKER_OK) { 189 RunSoon(base::Bind(callback, status)); 190 return; 191 } 192 } 193 stop_callbacks_.push_back(callback); 194 } 195 196 void ServiceWorkerVersion::SendMessage( 197 const IPC::Message& message, const StatusCallback& callback) { 198 if (running_status() != RUNNING) { 199 // Schedule calling this method after starting the worker. 200 StartWorker(base::Bind(&RunTaskAfterStartWorker, 201 weak_factory_.GetWeakPtr(), callback, 202 base::Bind(&self::SendMessage, 203 weak_factory_.GetWeakPtr(), 204 message, callback))); 205 return; 206 } 207 208 ServiceWorkerStatusCode status = embedded_worker_->SendMessage(message); 209 RunSoon(base::Bind(callback, status)); 210 } 211 212 void ServiceWorkerVersion::DispatchInstallEvent( 213 int active_version_id, 214 const StatusCallback& callback) { 215 DCHECK_EQ(NEW, status()) << status(); 216 SetStatus(INSTALLING); 217 218 if (running_status() != RUNNING) { 219 // Schedule calling this method after starting the worker. 220 StartWorker( 221 base::Bind(&RunTaskAfterStartWorker, 222 weak_factory_.GetWeakPtr(), 223 callback, 224 base::Bind(&self::DispatchInstallEventAfterStartWorker, 225 weak_factory_.GetWeakPtr(), 226 active_version_id, 227 callback))); 228 } else { 229 DispatchInstallEventAfterStartWorker(active_version_id, callback); 230 } 231 } 232 233 void ServiceWorkerVersion::DispatchActivateEvent( 234 const StatusCallback& callback) { 235 DCHECK_EQ(INSTALLED, status()) << status(); 236 SetStatus(ACTIVATING); 237 238 if (running_status() != RUNNING) { 239 // Schedule calling this method after starting the worker. 240 StartWorker( 241 base::Bind(&RunTaskAfterStartWorker, 242 weak_factory_.GetWeakPtr(), 243 callback, 244 base::Bind(&self::DispatchActivateEventAfterStartWorker, 245 weak_factory_.GetWeakPtr(), 246 callback))); 247 } else { 248 DispatchActivateEventAfterStartWorker(callback); 249 } 250 } 251 252 void ServiceWorkerVersion::DispatchFetchEvent( 253 const ServiceWorkerFetchRequest& request, 254 const FetchCallback& callback) { 255 DCHECK_EQ(ACTIVE, status()) << status(); 256 257 if (running_status() != RUNNING) { 258 // Schedule calling this method after starting the worker. 259 StartWorker(base::Bind(&RunTaskAfterStartWorker, 260 weak_factory_.GetWeakPtr(), 261 base::Bind(&RunErrorFetchCallback, callback), 262 base::Bind(&self::DispatchFetchEvent, 263 weak_factory_.GetWeakPtr(), 264 request, callback))); 265 return; 266 } 267 268 int request_id = fetch_callbacks_.Add(new FetchCallback(callback)); 269 ServiceWorkerStatusCode status = embedded_worker_->SendMessage( 270 ServiceWorkerMsg_FetchEvent(request_id, request)); 271 if (status != SERVICE_WORKER_OK) { 272 fetch_callbacks_.Remove(request_id); 273 RunSoon(base::Bind(&RunErrorFetchCallback, 274 callback, 275 SERVICE_WORKER_ERROR_FAILED)); 276 } 277 } 278 279 void ServiceWorkerVersion::DispatchSyncEvent(const StatusCallback& callback) { 280 DCHECK_EQ(ACTIVE, status()) << status(); 281 282 if (!CommandLine::ForCurrentProcess()->HasSwitch( 283 switches::kEnableServiceWorkerSync)) { 284 callback.Run(SERVICE_WORKER_ERROR_ABORT); 285 return; 286 } 287 288 if (running_status() != RUNNING) { 289 // Schedule calling this method after starting the worker. 290 StartWorker(base::Bind(&RunTaskAfterStartWorker, 291 weak_factory_.GetWeakPtr(), callback, 292 base::Bind(&self::DispatchSyncEvent, 293 weak_factory_.GetWeakPtr(), 294 callback))); 295 return; 296 } 297 298 int request_id = sync_callbacks_.Add(new StatusCallback(callback)); 299 ServiceWorkerStatusCode status = embedded_worker_->SendMessage( 300 ServiceWorkerMsg_SyncEvent(request_id)); 301 if (status != SERVICE_WORKER_OK) { 302 sync_callbacks_.Remove(request_id); 303 RunSoon(base::Bind(callback, status)); 304 } 305 } 306 307 void ServiceWorkerVersion::DispatchPushEvent(const StatusCallback& callback, 308 const std::string& data) { 309 DCHECK_EQ(ACTIVE, status()) << status(); 310 311 if (!CommandLine::ForCurrentProcess()->HasSwitch( 312 switches::kEnableExperimentalWebPlatformFeatures)) { 313 callback.Run(SERVICE_WORKER_ERROR_ABORT); 314 return; 315 } 316 317 if (running_status() != RUNNING) { 318 // Schedule calling this method after starting the worker. 319 StartWorker(base::Bind(&RunTaskAfterStartWorker, 320 weak_factory_.GetWeakPtr(), callback, 321 base::Bind(&self::DispatchPushEvent, 322 weak_factory_.GetWeakPtr(), 323 callback, data))); 324 return; 325 } 326 327 int request_id = push_callbacks_.Add(new StatusCallback(callback)); 328 ServiceWorkerStatusCode status = embedded_worker_->SendMessage( 329 ServiceWorkerMsg_PushEvent(request_id, data)); 330 if (status != SERVICE_WORKER_OK) { 331 push_callbacks_.Remove(request_id); 332 RunSoon(base::Bind(callback, status)); 333 } 334 } 335 336 void ServiceWorkerVersion::AddProcessToWorker(int process_id) { 337 embedded_worker_->AddProcessReference(process_id); 338 } 339 340 void ServiceWorkerVersion::RemoveProcessFromWorker(int process_id) { 341 embedded_worker_->ReleaseProcessReference(process_id); 342 } 343 344 bool ServiceWorkerVersion::HasProcessToRun() const { 345 return embedded_worker_->HasProcessToRun(); 346 } 347 348 void ServiceWorkerVersion::AddControllee( 349 ServiceWorkerProviderHost* provider_host) { 350 DCHECK(!ContainsKey(controllee_map_, provider_host)); 351 int controllee_id = controllee_by_id_.Add(provider_host); 352 controllee_map_[provider_host] = controllee_id; 353 AddProcessToWorker(provider_host->process_id()); 354 if (stop_worker_timer_.IsRunning()) 355 stop_worker_timer_.Stop(); 356 } 357 358 void ServiceWorkerVersion::RemoveControllee( 359 ServiceWorkerProviderHost* provider_host) { 360 ControlleeMap::iterator found = controllee_map_.find(provider_host); 361 DCHECK(found != controllee_map_.end()); 362 controllee_by_id_.Remove(found->second); 363 controllee_map_.erase(found); 364 RemoveProcessFromWorker(provider_host->process_id()); 365 if (!HasControllee()) 366 ScheduleStopWorker(); 367 // TODO(kinuko): Fire NoControllees notification when the # of controllees 368 // reaches 0, so that a new pending version can be activated (which will 369 // deactivate this version). 370 // TODO(michaeln): On no controllees call storage DeleteVersionResources 371 // if this version has been deactivated. Probably storage can listen for 372 // NoControllees for versions that have been deleted. 373 } 374 375 void ServiceWorkerVersion::AddWaitingControllee( 376 ServiceWorkerProviderHost* provider_host) { 377 AddProcessToWorker(provider_host->process_id()); 378 } 379 380 void ServiceWorkerVersion::RemoveWaitingControllee( 381 ServiceWorkerProviderHost* provider_host) { 382 RemoveProcessFromWorker(provider_host->process_id()); 383 } 384 385 void ServiceWorkerVersion::AddListener(Listener* listener) { 386 listeners_.AddObserver(listener); 387 } 388 389 void ServiceWorkerVersion::RemoveListener(Listener* listener) { 390 listeners_.RemoveObserver(listener); 391 } 392 393 void ServiceWorkerVersion::OnStarted() { 394 DCHECK_EQ(RUNNING, running_status()); 395 // Fire all start callbacks. 396 RunCallbacks(this, &start_callbacks_, SERVICE_WORKER_OK); 397 FOR_EACH_OBSERVER(Listener, listeners_, OnWorkerStarted(this)); 398 } 399 400 void ServiceWorkerVersion::OnStopped() { 401 DCHECK_EQ(STOPPED, running_status()); 402 scoped_refptr<ServiceWorkerVersion> protect(this); 403 404 // Fire all stop callbacks. 405 RunCallbacks(this, &stop_callbacks_, SERVICE_WORKER_OK); 406 407 // Let all start callbacks fail. 408 RunCallbacks( 409 this, &start_callbacks_, SERVICE_WORKER_ERROR_START_WORKER_FAILED); 410 411 // Let all message callbacks fail (this will also fire and clear all 412 // callbacks for events). 413 // TODO(kinuko): Consider if we want to add queue+resend mechanism here. 414 RunIDMapCallbacks(&activate_callbacks_, 415 &StatusCallback::Run, 416 MakeTuple(SERVICE_WORKER_ERROR_ACTIVATE_WORKER_FAILED)); 417 RunIDMapCallbacks(&install_callbacks_, 418 &StatusCallback::Run, 419 MakeTuple(SERVICE_WORKER_ERROR_INSTALL_WORKER_FAILED)); 420 RunIDMapCallbacks(&fetch_callbacks_, 421 &FetchCallback::Run, 422 MakeTuple(SERVICE_WORKER_ERROR_FAILED, 423 SERVICE_WORKER_FETCH_EVENT_RESULT_FALLBACK, 424 ServiceWorkerResponse())); 425 RunIDMapCallbacks(&sync_callbacks_, 426 &StatusCallback::Run, 427 MakeTuple(SERVICE_WORKER_ERROR_FAILED)); 428 RunIDMapCallbacks(&push_callbacks_, 429 &StatusCallback::Run, 430 MakeTuple(SERVICE_WORKER_ERROR_FAILED)); 431 432 FOR_EACH_OBSERVER(Listener, listeners_, OnWorkerStopped(this)); 433 } 434 435 void ServiceWorkerVersion::OnReportException( 436 const base::string16& error_message, 437 int line_number, 438 int column_number, 439 const GURL& source_url) { 440 FOR_EACH_OBSERVER( 441 Listener, 442 listeners_, 443 OnErrorReported( 444 this, error_message, line_number, column_number, source_url)); 445 } 446 447 void ServiceWorkerVersion::OnReportConsoleMessage(int source_identifier, 448 int message_level, 449 const base::string16& message, 450 int line_number, 451 const GURL& source_url) { 452 FOR_EACH_OBSERVER(Listener, 453 listeners_, 454 OnReportConsoleMessage(this, 455 source_identifier, 456 message_level, 457 message, 458 line_number, 459 source_url)); 460 } 461 462 bool ServiceWorkerVersion::OnMessageReceived(const IPC::Message& message) { 463 bool handled = true; 464 IPC_BEGIN_MESSAGE_MAP(ServiceWorkerVersion, message) 465 IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_GetClientDocuments, 466 OnGetClientDocuments) 467 IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_ActivateEventFinished, 468 OnActivateEventFinished) 469 IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_InstallEventFinished, 470 OnInstallEventFinished) 471 IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_FetchEventFinished, 472 OnFetchEventFinished) 473 IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_SyncEventFinished, 474 OnSyncEventFinished) 475 IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_PushEventFinished, 476 OnPushEventFinished) 477 IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_PostMessageToDocument, 478 OnPostMessageToDocument) 479 IPC_MESSAGE_UNHANDLED(handled = false) 480 IPC_END_MESSAGE_MAP() 481 return handled; 482 } 483 484 void ServiceWorkerVersion::RunStartWorkerCallbacksOnError( 485 ServiceWorkerStatusCode status) { 486 if (status != SERVICE_WORKER_OK) 487 RunCallbacks(this, &start_callbacks_, status); 488 } 489 490 void ServiceWorkerVersion::DispatchInstallEventAfterStartWorker( 491 int active_version_id, 492 const StatusCallback& callback) { 493 DCHECK_EQ(RUNNING, running_status()) 494 << "Worker stopped too soon after it was started."; 495 int request_id = install_callbacks_.Add(new StatusCallback(callback)); 496 ServiceWorkerStatusCode status = embedded_worker_->SendMessage( 497 ServiceWorkerMsg_InstallEvent(request_id, active_version_id)); 498 if (status != SERVICE_WORKER_OK) { 499 install_callbacks_.Remove(request_id); 500 RunSoon(base::Bind(callback, status)); 501 } 502 } 503 504 void ServiceWorkerVersion::DispatchActivateEventAfterStartWorker( 505 const StatusCallback& callback) { 506 DCHECK_EQ(RUNNING, running_status()) 507 << "Worker stopped too soon after it was started."; 508 int request_id = activate_callbacks_.Add(new StatusCallback(callback)); 509 ServiceWorkerStatusCode status = 510 embedded_worker_->SendMessage(ServiceWorkerMsg_ActivateEvent(request_id)); 511 if (status != SERVICE_WORKER_OK) { 512 activate_callbacks_.Remove(request_id); 513 RunSoon(base::Bind(callback, status)); 514 } 515 } 516 517 void ServiceWorkerVersion::OnGetClientDocuments(int request_id) { 518 std::vector<int> client_ids; 519 ControlleeByIDMap::iterator it(&controllee_by_id_); 520 while (!it.IsAtEnd()) { 521 client_ids.push_back(it.GetCurrentKey()); 522 it.Advance(); 523 } 524 // Don't bother if it's no longer running. 525 if (running_status() == RUNNING) { 526 embedded_worker_->SendMessage( 527 ServiceWorkerMsg_DidGetClientDocuments(request_id, client_ids)); 528 } 529 } 530 531 void ServiceWorkerVersion::OnActivateEventFinished( 532 int request_id, 533 blink::WebServiceWorkerEventResult result) { 534 StatusCallback* callback = activate_callbacks_.Lookup(request_id); 535 if (!callback) { 536 NOTREACHED() << "Got unexpected message: " << request_id; 537 return; 538 } 539 ServiceWorkerStatusCode status = SERVICE_WORKER_OK; 540 if (result == blink::WebServiceWorkerEventResultRejected) 541 status = SERVICE_WORKER_ERROR_ACTIVATE_WORKER_FAILED; 542 else 543 SetStatus(ACTIVE); 544 545 scoped_refptr<ServiceWorkerVersion> protect(this); 546 callback->Run(status); 547 activate_callbacks_.Remove(request_id); 548 } 549 550 void ServiceWorkerVersion::OnInstallEventFinished( 551 int request_id, 552 blink::WebServiceWorkerEventResult result) { 553 StatusCallback* callback = install_callbacks_.Lookup(request_id); 554 if (!callback) { 555 NOTREACHED() << "Got unexpected message: " << request_id; 556 return; 557 } 558 ServiceWorkerStatusCode status = SERVICE_WORKER_OK; 559 if (result == blink::WebServiceWorkerEventResultRejected) 560 status = SERVICE_WORKER_ERROR_INSTALL_WORKER_FAILED; 561 else 562 SetStatus(INSTALLED); 563 564 scoped_refptr<ServiceWorkerVersion> protect(this); 565 callback->Run(status); 566 install_callbacks_.Remove(request_id); 567 } 568 569 void ServiceWorkerVersion::OnFetchEventFinished( 570 int request_id, 571 ServiceWorkerFetchEventResult result, 572 const ServiceWorkerResponse& response) { 573 FetchCallback* callback = fetch_callbacks_.Lookup(request_id); 574 if (!callback) { 575 NOTREACHED() << "Got unexpected message: " << request_id; 576 return; 577 } 578 579 scoped_refptr<ServiceWorkerVersion> protect(this); 580 callback->Run(SERVICE_WORKER_OK, result, response); 581 fetch_callbacks_.Remove(request_id); 582 } 583 584 void ServiceWorkerVersion::OnSyncEventFinished( 585 int request_id) { 586 StatusCallback* callback = sync_callbacks_.Lookup(request_id); 587 if (!callback) { 588 NOTREACHED() << "Got unexpected message: " << request_id; 589 return; 590 } 591 592 scoped_refptr<ServiceWorkerVersion> protect(this); 593 callback->Run(SERVICE_WORKER_OK); 594 sync_callbacks_.Remove(request_id); 595 } 596 597 void ServiceWorkerVersion::OnPushEventFinished( 598 int request_id) { 599 StatusCallback* callback = push_callbacks_.Lookup(request_id); 600 if (!callback) { 601 NOTREACHED() << "Got unexpected message: " << request_id; 602 return; 603 } 604 605 scoped_refptr<ServiceWorkerVersion> protect(this); 606 callback->Run(SERVICE_WORKER_OK); 607 push_callbacks_.Remove(request_id); 608 } 609 610 void ServiceWorkerVersion::OnPostMessageToDocument( 611 int client_id, 612 const base::string16& message, 613 const std::vector<int>& sent_message_port_ids) { 614 ServiceWorkerProviderHost* provider_host = 615 controllee_by_id_.Lookup(client_id); 616 if (!provider_host) { 617 // The client may already have been closed, just ignore. 618 return; 619 } 620 provider_host->PostMessage(message, sent_message_port_ids); 621 } 622 623 void ServiceWorkerVersion::ScheduleStopWorker() { 624 if (running_status() != RUNNING) 625 return; 626 if (stop_worker_timer_.IsRunning()) { 627 stop_worker_timer_.Reset(); 628 return; 629 } 630 stop_worker_timer_.Start( 631 FROM_HERE, base::TimeDelta::FromSeconds(kStopWorkerDelay), 632 base::Bind(&ServiceWorkerVersion::StopWorker, 633 weak_factory_.GetWeakPtr(), 634 base::Bind(&ServiceWorkerUtils::NoOpStatusCallback))); 635 } 636 637 } // namespace content 638