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_register_job.h" 6 7#include <vector> 8 9#include "base/message_loop/message_loop.h" 10#include "content/browser/service_worker/service_worker_context_core.h" 11#include "content/browser/service_worker/service_worker_job_coordinator.h" 12#include "content/browser/service_worker/service_worker_registration.h" 13#include "content/browser/service_worker/service_worker_storage.h" 14#include "content/browser/service_worker/service_worker_utils.h" 15#include "net/base/net_errors.h" 16 17namespace content { 18 19namespace { 20 21void RunSoon(const base::Closure& closure) { 22 base::MessageLoop::current()->PostTask(FROM_HERE, closure); 23} 24 25} // namespace 26 27typedef ServiceWorkerRegisterJobBase::RegistrationJobType RegistrationJobType; 28 29ServiceWorkerRegisterJob::ServiceWorkerRegisterJob( 30 base::WeakPtr<ServiceWorkerContextCore> context, 31 const GURL& pattern, 32 const GURL& script_url) 33 : context_(context), 34 job_type_(REGISTRATION_JOB), 35 pattern_(pattern), 36 script_url_(script_url), 37 phase_(INITIAL), 38 is_promise_resolved_(false), 39 promise_resolved_status_(SERVICE_WORKER_OK), 40 weak_factory_(this) {} 41 42ServiceWorkerRegisterJob::ServiceWorkerRegisterJob( 43 base::WeakPtr<ServiceWorkerContextCore> context, 44 ServiceWorkerRegistration* registration) 45 : context_(context), 46 job_type_(UPDATE_JOB), 47 pattern_(registration->pattern()), 48 script_url_(registration->GetNewestVersion()->script_url()), 49 phase_(INITIAL), 50 is_promise_resolved_(false), 51 promise_resolved_status_(SERVICE_WORKER_OK), 52 weak_factory_(this) { 53 internal_.registration = registration; 54} 55 56ServiceWorkerRegisterJob::~ServiceWorkerRegisterJob() { 57 DCHECK(!context_ || 58 phase_ == INITIAL || phase_ == COMPLETE || phase_ == ABORT) 59 << "Jobs should only be interrupted during shutdown."; 60} 61 62void ServiceWorkerRegisterJob::AddCallback( 63 const RegistrationCallback& callback, 64 ServiceWorkerProviderHost* provider_host) { 65 if (!is_promise_resolved_) { 66 callbacks_.push_back(callback); 67 if (provider_host) 68 provider_host->AddScopedProcessReferenceToPattern(pattern_); 69 return; 70 } 71 RunSoon(base::Bind( 72 callback, promise_resolved_status_, 73 promise_resolved_registration_, promise_resolved_version_)); 74} 75 76void ServiceWorkerRegisterJob::Start() { 77 SetPhase(START); 78 ServiceWorkerStorage::FindRegistrationCallback next_step; 79 if (job_type_ == REGISTRATION_JOB) { 80 next_step = base::Bind( 81 &ServiceWorkerRegisterJob::ContinueWithRegistration, 82 weak_factory_.GetWeakPtr()); 83 } else { 84 next_step = base::Bind( 85 &ServiceWorkerRegisterJob::ContinueWithUpdate, 86 weak_factory_.GetWeakPtr()); 87 } 88 89 scoped_refptr<ServiceWorkerRegistration> registration = 90 context_->storage()->GetUninstallingRegistration(pattern_); 91 if (registration.get()) 92 RunSoon(base::Bind(next_step, SERVICE_WORKER_OK, registration)); 93 else 94 context_->storage()->FindRegistrationForPattern(pattern_, next_step); 95} 96 97void ServiceWorkerRegisterJob::Abort() { 98 SetPhase(ABORT); 99 CompleteInternal(SERVICE_WORKER_ERROR_ABORT); 100 // Don't have to call FinishJob() because the caller takes care of removing 101 // the jobs from the queue. 102} 103 104bool ServiceWorkerRegisterJob::Equals(ServiceWorkerRegisterJobBase* job) { 105 if (job->GetType() != GetType()) 106 return false; 107 ServiceWorkerRegisterJob* register_job = 108 static_cast<ServiceWorkerRegisterJob*>(job); 109 return register_job->pattern_ == pattern_ && 110 register_job->script_url_ == script_url_; 111} 112 113RegistrationJobType ServiceWorkerRegisterJob::GetType() { 114 return job_type_; 115} 116 117ServiceWorkerRegisterJob::Internal::Internal() {} 118 119ServiceWorkerRegisterJob::Internal::~Internal() {} 120 121void ServiceWorkerRegisterJob::set_registration( 122 const scoped_refptr<ServiceWorkerRegistration>& registration) { 123 DCHECK(phase_ == START || phase_ == REGISTER) << phase_; 124 DCHECK(!internal_.registration.get()); 125 internal_.registration = registration; 126} 127 128ServiceWorkerRegistration* ServiceWorkerRegisterJob::registration() { 129 DCHECK(phase_ >= REGISTER || job_type_ == UPDATE_JOB) << phase_; 130 return internal_.registration.get(); 131} 132 133void ServiceWorkerRegisterJob::set_new_version( 134 ServiceWorkerVersion* version) { 135 DCHECK(phase_ == UPDATE) << phase_; 136 DCHECK(!internal_.new_version.get()); 137 internal_.new_version = version; 138} 139 140ServiceWorkerVersion* ServiceWorkerRegisterJob::new_version() { 141 DCHECK(phase_ >= UPDATE) << phase_; 142 return internal_.new_version.get(); 143} 144 145void ServiceWorkerRegisterJob::set_uninstalling_registration( 146 const scoped_refptr<ServiceWorkerRegistration>& registration) { 147 DCHECK_EQ(phase_, WAIT_FOR_UNINSTALL); 148 internal_.uninstalling_registration = registration; 149} 150 151ServiceWorkerRegistration* 152ServiceWorkerRegisterJob::uninstalling_registration() { 153 DCHECK_EQ(phase_, WAIT_FOR_UNINSTALL); 154 return internal_.uninstalling_registration.get(); 155} 156 157void ServiceWorkerRegisterJob::SetPhase(Phase phase) { 158 switch (phase) { 159 case INITIAL: 160 NOTREACHED(); 161 break; 162 case START: 163 DCHECK(phase_ == INITIAL) << phase_; 164 break; 165 case WAIT_FOR_UNINSTALL: 166 DCHECK(phase_ == START) << phase_; 167 break; 168 case REGISTER: 169 DCHECK(phase_ == START || phase_ == WAIT_FOR_UNINSTALL) << phase_; 170 break; 171 case UPDATE: 172 DCHECK(phase_ == START || phase_ == REGISTER) << phase_; 173 break; 174 case INSTALL: 175 DCHECK(phase_ == UPDATE) << phase_; 176 break; 177 case STORE: 178 DCHECK(phase_ == INSTALL) << phase_; 179 break; 180 case COMPLETE: 181 DCHECK(phase_ != INITIAL && phase_ != COMPLETE) << phase_; 182 break; 183 case ABORT: 184 break; 185 } 186 phase_ = phase; 187} 188 189// This function corresponds to the steps in [[Register]] following 190// "Let registration be the result of running the [[GetRegistration]] algorithm. 191// Throughout this file, comments in quotes are excerpts from the spec. 192void ServiceWorkerRegisterJob::ContinueWithRegistration( 193 ServiceWorkerStatusCode status, 194 const scoped_refptr<ServiceWorkerRegistration>& existing_registration) { 195 DCHECK_EQ(REGISTRATION_JOB, job_type_); 196 if (status != SERVICE_WORKER_ERROR_NOT_FOUND && status != SERVICE_WORKER_OK) { 197 Complete(status); 198 return; 199 } 200 201 if (!existing_registration.get() || existing_registration->is_uninstalled()) { 202 RegisterAndContinue(SERVICE_WORKER_OK); 203 return; 204 } 205 206 DCHECK(existing_registration->GetNewestVersion()); 207 // "If scriptURL is equal to registration.[[ScriptURL]], then:" 208 if (existing_registration->GetNewestVersion()->script_url() == script_url_) { 209 // "Set registration.[[Uninstalling]] to false." 210 existing_registration->AbortPendingClear(base::Bind( 211 &ServiceWorkerRegisterJob::ContinueWithRegistrationForSameScriptUrl, 212 weak_factory_.GetWeakPtr(), 213 existing_registration)); 214 return; 215 } 216 217 if (existing_registration->is_uninstalling()) { 218 // "Wait until the Record {[[key]], [[value]]} entry of its 219 // [[ScopeToRegistrationMap]] where registation.scope matches entry.[[key]] 220 // is deleted." 221 WaitForUninstall(existing_registration); 222 return; 223 } 224 225 // "Set registration.[[Uninstalling]] to false." 226 DCHECK(!existing_registration->is_uninstalling()); 227 228 // "Return the result of running the [[Update]] algorithm, or its equivalent, 229 // passing registration as the argument." 230 set_registration(existing_registration); 231 UpdateAndContinue(); 232} 233 234void ServiceWorkerRegisterJob::ContinueWithUpdate( 235 ServiceWorkerStatusCode status, 236 const scoped_refptr<ServiceWorkerRegistration>& existing_registration) { 237 DCHECK_EQ(UPDATE_JOB, job_type_); 238 if (status != SERVICE_WORKER_OK) { 239 Complete(status); 240 return; 241 } 242 243 if (existing_registration.get() != registration()) { 244 Complete(SERVICE_WORKER_ERROR_NOT_FOUND); 245 return; 246 } 247 248 // A previous job may have unregistered or installed a new version to this 249 // registration. 250 if (registration()->is_uninstalling() || 251 registration()->GetNewestVersion()->script_url() != script_url_) { 252 Complete(SERVICE_WORKER_ERROR_NOT_FOUND); 253 return; 254 } 255 256 // TODO(michaeln): If the last update check was less than 24 hours 257 // ago, depending on the freshness of the cached worker script we 258 // may be able to complete the update job right here. 259 260 UpdateAndContinue(); 261} 262 263// Creates a new ServiceWorkerRegistration. 264void ServiceWorkerRegisterJob::RegisterAndContinue( 265 ServiceWorkerStatusCode status) { 266 SetPhase(REGISTER); 267 if (status != SERVICE_WORKER_OK) { 268 // Abort this registration job. 269 Complete(status); 270 return; 271 } 272 273 set_registration(new ServiceWorkerRegistration( 274 pattern_, context_->storage()->NewRegistrationId(), context_)); 275 AssociateProviderHostsToRegistration(registration()); 276 UpdateAndContinue(); 277} 278 279void ServiceWorkerRegisterJob::WaitForUninstall( 280 const scoped_refptr<ServiceWorkerRegistration>& existing_registration) { 281 SetPhase(WAIT_FOR_UNINSTALL); 282 set_uninstalling_registration(existing_registration); 283 uninstalling_registration()->AddListener(this); 284} 285 286void ServiceWorkerRegisterJob::ContinueWithRegistrationForSameScriptUrl( 287 const scoped_refptr<ServiceWorkerRegistration>& existing_registration, 288 ServiceWorkerStatusCode status) { 289 if (status != SERVICE_WORKER_OK) { 290 Complete(status); 291 return; 292 } 293 set_registration(existing_registration); 294 295 // TODO(falken): Follow the spec: resolve the promise 296 // with the newest version. 297 298 if (!existing_registration->active_version()) { 299 UpdateAndContinue(); 300 return; 301 } 302 303 ResolvePromise(status, 304 existing_registration.get(), 305 existing_registration->active_version()); 306 Complete(SERVICE_WORKER_OK); 307} 308 309// This function corresponds to the spec's [[Update]] algorithm. 310void ServiceWorkerRegisterJob::UpdateAndContinue() { 311 SetPhase(UPDATE); 312 context_->storage()->NotifyInstallingRegistration(registration()); 313 314 // TODO(falken): "If serviceWorkerRegistration.installingWorker is not null.." 315 // then terminate the installing worker. It doesn't make sense to implement 316 // yet since we always activate the worker if install completed, so there can 317 // be no installing worker at this point. 318 319 // "Let serviceWorker be a newly-created ServiceWorker object..." and start 320 // the worker. 321 set_new_version(new ServiceWorkerVersion(registration(), 322 script_url_, 323 context_->storage()->NewVersionId(), 324 context_)); 325 326 bool pause_after_download = job_type_ == UPDATE_JOB; 327 if (pause_after_download) 328 new_version()->embedded_worker()->AddListener(this); 329 new_version()->StartWorker( 330 pause_after_download, 331 base::Bind(&ServiceWorkerRegisterJob::OnStartWorkerFinished, 332 weak_factory_.GetWeakPtr())); 333} 334 335void ServiceWorkerRegisterJob::OnStartWorkerFinished( 336 ServiceWorkerStatusCode status) { 337 if (status == SERVICE_WORKER_OK) { 338 InstallAndContinue(); 339 return; 340 } 341 342 // "If serviceWorker fails to start up..." then reject the promise with an 343 // error and abort. When there is a main script network error, the status will 344 // be updated to a more specific one. 345 const net::URLRequestStatus& main_script_status = 346 new_version()->script_cache_map()->main_script_status(); 347 if (main_script_status.status() != net::URLRequestStatus::SUCCESS) { 348 switch (main_script_status.error()) { 349 case net::ERR_INSECURE_RESPONSE: 350 case net::ERR_UNSAFE_REDIRECT: 351 status = SERVICE_WORKER_ERROR_SECURITY; 352 break; 353 case net::ERR_ABORTED: 354 status = SERVICE_WORKER_ERROR_ABORT; 355 break; 356 case net::ERR_FAILED: 357 status = SERVICE_WORKER_ERROR_NETWORK; 358 break; 359 default: 360 NOTREACHED(); 361 } 362 } 363 Complete(status); 364} 365 366// This function corresponds to the spec's [[Install]] algorithm. 367void ServiceWorkerRegisterJob::InstallAndContinue() { 368 SetPhase(INSTALL); 369 370 // "2. Set registration.installingWorker to worker." 371 registration()->SetInstallingVersion(new_version()); 372 373 // "3. Resolve promise with registration." 374 ResolvePromise(SERVICE_WORKER_OK, registration(), new_version()); 375 376 // "4. Run the [[UpdateState]] algorithm passing registration.installingWorker 377 // and "installing" as the arguments." 378 new_version()->SetStatus(ServiceWorkerVersion::INSTALLING); 379 380 // "5. Fire a simple event named updatefound..." 381 registration()->NotifyUpdateFound(); 382 383 // "6. Fire an event named install..." 384 new_version()->DispatchInstallEvent( 385 -1, 386 base::Bind(&ServiceWorkerRegisterJob::OnInstallFinished, 387 weak_factory_.GetWeakPtr())); 388} 389 390void ServiceWorkerRegisterJob::OnInstallFinished( 391 ServiceWorkerStatusCode status) { 392 // TODO(kinuko,falken): For some error cases (e.g. ServiceWorker is 393 // unexpectedly terminated) we may want to retry sending the event again. 394 if (status != SERVICE_WORKER_OK) { 395 // "8. If installFailed is true, then:..." 396 Complete(status); 397 return; 398 } 399 400 SetPhase(STORE); 401 registration()->set_last_update_check(base::Time::Now()); 402 context_->storage()->StoreRegistration( 403 registration(), 404 new_version(), 405 base::Bind(&ServiceWorkerRegisterJob::OnStoreRegistrationComplete, 406 weak_factory_.GetWeakPtr())); 407} 408 409void ServiceWorkerRegisterJob::OnStoreRegistrationComplete( 410 ServiceWorkerStatusCode status) { 411 if (status != SERVICE_WORKER_OK) { 412 Complete(status); 413 return; 414 } 415 416 // "9. If registration.waitingWorker is not null, then:..." 417 if (registration()->waiting_version()) { 418 // "1. Run the [[UpdateState]] algorithm passing registration.waitingWorker 419 // and "redundant" as the arguments." 420 registration()->waiting_version()->SetStatus( 421 ServiceWorkerVersion::REDUNDANT); 422 } 423 424 // "10. Set registration.waitingWorker to registration.installingWorker." 425 // "11. Set registration.installingWorker to null." 426 registration()->SetWaitingVersion(new_version()); 427 428 // "12. Run the [[UpdateState]] algorithm passing registration.waitingWorker 429 // and "installed" as the arguments." 430 new_version()->SetStatus(ServiceWorkerVersion::INSTALLED); 431 432 // TODO(michaeln): "13. If activateImmediate is true, then..." 433 434 // "14. Wait until no document is using registration as their 435 // Service Worker registration." 436 registration()->ActivateWaitingVersionWhenReady(); 437 438 Complete(SERVICE_WORKER_OK); 439} 440 441void ServiceWorkerRegisterJob::Complete(ServiceWorkerStatusCode status) { 442 CompleteInternal(status); 443 context_->job_coordinator()->FinishJob(pattern_, this); 444} 445 446void ServiceWorkerRegisterJob::CompleteInternal( 447 ServiceWorkerStatusCode status) { 448 SetPhase(COMPLETE); 449 if (status != SERVICE_WORKER_OK) { 450 if (registration()) { 451 if (new_version()) { 452 registration()->UnsetVersion(new_version()); 453 new_version()->Doom(); 454 } 455 if (!registration()->waiting_version() && 456 !registration()->active_version()) { 457 registration()->NotifyRegistrationFailed(); 458 context_->storage()->DeleteRegistration( 459 registration()->id(), 460 registration()->pattern().GetOrigin(), 461 base::Bind(&ServiceWorkerUtils::NoOpStatusCallback)); 462 } 463 } 464 if (!is_promise_resolved_) 465 ResolvePromise(status, NULL, NULL); 466 } 467 DCHECK(callbacks_.empty()); 468 if (registration()) { 469 context_->storage()->NotifyDoneInstallingRegistration( 470 registration(), new_version(), status); 471 } 472 if (new_version()) 473 new_version()->embedded_worker()->RemoveListener(this); 474} 475 476void ServiceWorkerRegisterJob::ResolvePromise( 477 ServiceWorkerStatusCode status, 478 ServiceWorkerRegistration* registration, 479 ServiceWorkerVersion* version) { 480 DCHECK(!is_promise_resolved_); 481 is_promise_resolved_ = true; 482 promise_resolved_status_ = status; 483 promise_resolved_registration_ = registration; 484 promise_resolved_version_ = version; 485 for (std::vector<RegistrationCallback>::iterator it = callbacks_.begin(); 486 it != callbacks_.end(); 487 ++it) { 488 it->Run(status, registration, version); 489 } 490 callbacks_.clear(); 491} 492 493void ServiceWorkerRegisterJob::OnPausedAfterDownload() { 494 // This happens prior to OnStartWorkerFinished time. 495 scoped_refptr<ServiceWorkerVersion> most_recent_version = 496 registration()->waiting_version() ? 497 registration()->waiting_version() : 498 registration()->active_version(); 499 DCHECK(most_recent_version.get()); 500 int64 most_recent_script_id = 501 most_recent_version->script_cache_map()->Lookup(script_url_); 502 int64 new_script_id = 503 new_version()->script_cache_map()->Lookup(script_url_); 504 505 // TODO(michaeln): It would be better to compare as the new resource 506 // is being downloaded and to avoid writing it to disk until we know 507 // its needed. 508 context_->storage()->CompareScriptResources( 509 most_recent_script_id, new_script_id, 510 base::Bind(&ServiceWorkerRegisterJob::OnCompareScriptResourcesComplete, 511 weak_factory_.GetWeakPtr(), 512 most_recent_version)); 513} 514 515bool ServiceWorkerRegisterJob::OnMessageReceived(const IPC::Message& message) { 516 return false; 517} 518 519void ServiceWorkerRegisterJob::OnRegistrationFinishedUninstalling( 520 ServiceWorkerRegistration* existing_registration) { 521 DCHECK_EQ(phase_, WAIT_FOR_UNINSTALL); 522 DCHECK_EQ(existing_registration, uninstalling_registration()); 523 existing_registration->RemoveListener(this); 524 set_uninstalling_registration(NULL); 525 RegisterAndContinue(SERVICE_WORKER_OK); 526} 527 528void ServiceWorkerRegisterJob::OnCompareScriptResourcesComplete( 529 ServiceWorkerVersion* most_recent_version, 530 ServiceWorkerStatusCode status, 531 bool are_equal) { 532 if (are_equal) { 533 // Only bump the last check time when we've bypassed the browser cache. 534 base::TimeDelta time_since_last_check = 535 base::Time::Now() - registration()->last_update_check(); 536 if (time_since_last_check > base::TimeDelta::FromHours(24)) { 537 registration()->set_last_update_check(base::Time::Now()); 538 context_->storage()->UpdateLastUpdateCheckTime(registration()); 539 } 540 541 ResolvePromise(SERVICE_WORKER_OK, registration(), most_recent_version); 542 Complete(SERVICE_WORKER_ERROR_EXISTS); 543 return; 544 } 545 546 // Proceed with really starting the worker. 547 new_version()->embedded_worker()->ResumeAfterDownload(); 548 new_version()->embedded_worker()->RemoveListener(this); 549} 550 551void ServiceWorkerRegisterJob::AssociateProviderHostsToRegistration( 552 ServiceWorkerRegistration* registration) { 553 DCHECK(registration); 554 for (scoped_ptr<ServiceWorkerContextCore::ProviderHostIterator> it = 555 context_->GetProviderHostIterator(); 556 !it->IsAtEnd(); it->Advance()) { 557 ServiceWorkerProviderHost* host = it->GetProviderHost(); 558 if (ServiceWorkerUtils::ScopeMatches(registration->pattern(), 559 host->document_url())) { 560 if (host->CanAssociateRegistration(registration)) 561 host->AssociateRegistration(registration); 562 } 563 } 564} 565 566} // namespace content 567