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 16 namespace content { 17 18 namespace { 19 20 void RunSoon(const base::Closure& closure) { 21 base::MessageLoop::current()->PostTask(FROM_HERE, closure); 22 } 23 24 } 25 26 typedef ServiceWorkerRegisterJobBase::RegistrationJobType RegistrationJobType; 27 28 ServiceWorkerRegisterJob::ServiceWorkerRegisterJob( 29 base::WeakPtr<ServiceWorkerContextCore> context, 30 const GURL& pattern, 31 const GURL& script_url) 32 : context_(context), 33 pattern_(pattern), 34 script_url_(script_url), 35 phase_(INITIAL), 36 is_promise_resolved_(false), 37 promise_resolved_status_(SERVICE_WORKER_OK), 38 weak_factory_(this) {} 39 40 ServiceWorkerRegisterJob::~ServiceWorkerRegisterJob() { 41 DCHECK(!context_ || 42 phase_ == INITIAL || phase_ == COMPLETE || phase_ == ABORT) 43 << "Jobs should only be interrupted during shutdown."; 44 } 45 46 void ServiceWorkerRegisterJob::AddCallback(const RegistrationCallback& callback, 47 int process_id) { 48 if (!is_promise_resolved_) { 49 callbacks_.push_back(callback); 50 if (process_id != -1 && (phase_ < UPDATE || !pending_version())) 51 pending_process_ids_.push_back(process_id); 52 return; 53 } 54 RunSoon(base::Bind( 55 callback, promise_resolved_status_, 56 promise_resolved_registration_, promise_resolved_version_)); 57 } 58 59 void ServiceWorkerRegisterJob::Start() { 60 SetPhase(START); 61 context_->storage()->FindRegistrationForPattern( 62 pattern_, 63 base::Bind( 64 &ServiceWorkerRegisterJob::HandleExistingRegistrationAndContinue, 65 weak_factory_.GetWeakPtr())); 66 } 67 68 void ServiceWorkerRegisterJob::Abort() { 69 SetPhase(ABORT); 70 CompleteInternal(SERVICE_WORKER_ERROR_ABORT); 71 // Don't have to call FinishJob() because the caller takes care of removing 72 // the jobs from the queue. 73 } 74 75 bool ServiceWorkerRegisterJob::Equals(ServiceWorkerRegisterJobBase* job) { 76 if (job->GetType() != GetType()) 77 return false; 78 ServiceWorkerRegisterJob* register_job = 79 static_cast<ServiceWorkerRegisterJob*>(job); 80 return register_job->pattern_ == pattern_ && 81 register_job->script_url_ == script_url_; 82 } 83 84 RegistrationJobType ServiceWorkerRegisterJob::GetType() { 85 return REGISTRATION; 86 } 87 88 ServiceWorkerRegisterJob::Internal::Internal() {} 89 90 ServiceWorkerRegisterJob::Internal::~Internal() {} 91 92 void ServiceWorkerRegisterJob::set_registration( 93 ServiceWorkerRegistration* registration) { 94 DCHECK(phase_ == START || phase_ == REGISTER) << phase_; 95 DCHECK(!internal_.registration); 96 internal_.registration = registration; 97 } 98 99 ServiceWorkerRegistration* ServiceWorkerRegisterJob::registration() { 100 DCHECK(phase_ >= REGISTER) << phase_; 101 return internal_.registration; 102 } 103 104 void ServiceWorkerRegisterJob::set_pending_version( 105 ServiceWorkerVersion* version) { 106 DCHECK(phase_ == UPDATE) << phase_; 107 DCHECK(!internal_.pending_version); 108 internal_.pending_version = version; 109 } 110 111 ServiceWorkerVersion* ServiceWorkerRegisterJob::pending_version() { 112 DCHECK(phase_ >= UPDATE) << phase_; 113 return internal_.pending_version; 114 } 115 116 void ServiceWorkerRegisterJob::SetPhase(Phase phase) { 117 switch (phase) { 118 case INITIAL: 119 NOTREACHED(); 120 break; 121 case START: 122 DCHECK(phase_ == INITIAL) << phase_; 123 break; 124 case REGISTER: 125 DCHECK(phase_ == START) << phase_; 126 break; 127 case UPDATE: 128 DCHECK(phase_ == START || phase_ == REGISTER) << phase_; 129 break; 130 case INSTALL: 131 DCHECK(phase_ == UPDATE) << phase_; 132 break; 133 case STORE: 134 DCHECK(phase_ == INSTALL) << phase_; 135 break; 136 case ACTIVATE: 137 DCHECK(phase_ == STORE) << phase_; 138 break; 139 case COMPLETE: 140 DCHECK(phase_ != INITIAL && phase_ != COMPLETE) << phase_; 141 break; 142 case ABORT: 143 break; 144 } 145 phase_ = phase; 146 } 147 148 // This function corresponds to the steps in Register following 149 // "Let serviceWorkerRegistration be _GetRegistration(scope)" 150 // |existing_registration| corresponds to serviceWorkerRegistration. 151 // Throughout this file, comments in quotes are excerpts from the spec. 152 void ServiceWorkerRegisterJob::HandleExistingRegistrationAndContinue( 153 ServiceWorkerStatusCode status, 154 const scoped_refptr<ServiceWorkerRegistration>& existing_registration) { 155 // On unexpected error, abort this registration job. 156 if (status != SERVICE_WORKER_ERROR_NOT_FOUND && status != SERVICE_WORKER_OK) { 157 Complete(status); 158 return; 159 } 160 161 // "If serviceWorkerRegistration is not null and script is equal to 162 // serviceWorkerRegistration.scriptUrl..." resolve with the existing 163 // registration and abort. 164 if (existing_registration.get() && 165 existing_registration->script_url() == script_url_) { 166 set_registration(existing_registration); 167 // If there's no active version, go ahead to Update (this isn't in the spec 168 // but seems reasonable, and without SoftUpdate implemented we can never 169 // Update otherwise). 170 if (!existing_registration->active_version()) { 171 UpdateAndContinue(status); 172 return; 173 } 174 ResolvePromise( 175 status, existing_registration, existing_registration->active_version()); 176 Complete(SERVICE_WORKER_OK); 177 return; 178 } 179 180 // "If serviceWorkerRegistration is null..." create a new registration. 181 if (!existing_registration.get()) { 182 RegisterAndContinue(SERVICE_WORKER_OK); 183 return; 184 } 185 186 // On script URL mismatch, "set serviceWorkerRegistration.scriptUrl to 187 // script." We accomplish this by deleting the existing registration and 188 // registering a new one. 189 // TODO(falken): Match the spec. We now throw away the active_version_ and 190 // waiting_version_ of the existing registration, which isn't in the spec. 191 // TODO(michaeln): Deactivate the live existing_registration object and 192 // eventually call storage->DeleteVersionResources() 193 // when it no longer has any controllees. 194 context_->storage()->DeleteRegistration( 195 existing_registration->id(), 196 existing_registration->script_url().GetOrigin(), 197 base::Bind(&ServiceWorkerRegisterJob::RegisterAndContinue, 198 weak_factory_.GetWeakPtr())); 199 } 200 201 // Creates a new ServiceWorkerRegistration. 202 void ServiceWorkerRegisterJob::RegisterAndContinue( 203 ServiceWorkerStatusCode status) { 204 SetPhase(REGISTER); 205 if (status != SERVICE_WORKER_OK) { 206 // Abort this registration job. 207 Complete(status); 208 return; 209 } 210 211 set_registration(new ServiceWorkerRegistration( 212 pattern_, script_url_, context_->storage()->NewRegistrationId(), 213 context_)); 214 context_->storage()->NotifyInstallingRegistration(registration()); 215 UpdateAndContinue(SERVICE_WORKER_OK); 216 } 217 218 // This function corresponds to the spec's _Update algorithm. 219 void ServiceWorkerRegisterJob::UpdateAndContinue( 220 ServiceWorkerStatusCode status) { 221 SetPhase(UPDATE); 222 if (status != SERVICE_WORKER_OK) { 223 // Abort this registration job. 224 Complete(status); 225 return; 226 } 227 228 // TODO(falken): "If serviceWorkerRegistration.installingWorker is not null.." 229 // then terminate the installing worker. It doesn't make sense to implement 230 // yet since we always activate the worker if install completed, so there can 231 // be no installing worker at this point. 232 // TODO(nhiroki): Check 'installing_version()' instead when it's supported. 233 DCHECK(!registration()->waiting_version()); 234 235 // "Let serviceWorker be a newly-created ServiceWorker object..." and start 236 // the worker. 237 set_pending_version(new ServiceWorkerVersion( 238 registration(), context_->storage()->NewVersionId(), context_)); 239 240 // TODO(michaeln): Start the worker into a paused state where the 241 // script resource is downloaded but not yet evaluated. 242 pending_version()->StartWorkerWithCandidateProcesses( 243 pending_process_ids_, 244 base::Bind(&ServiceWorkerRegisterJob::OnStartWorkerFinished, 245 weak_factory_.GetWeakPtr())); 246 } 247 248 void ServiceWorkerRegisterJob::OnStartWorkerFinished( 249 ServiceWorkerStatusCode status) { 250 // "If serviceWorker fails to start up..." then reject the promise with an 251 // error and abort. 252 if (status != SERVICE_WORKER_OK) { 253 Complete(status); 254 return; 255 } 256 257 // TODO(michaeln): Compare the old and new script. 258 // If different unpause the worker and continue with 259 // the job. If the same ResolvePromise with the current 260 // version and complete the job, throwing away the new version 261 // since there's nothing new. 262 263 // "Resolve promise with serviceWorker." 264 // Although the spec doesn't set waitingWorker until after resolving the 265 // promise, our system's resolving works by passing ServiceWorkerRegistration 266 // to the callbacks, so waitingWorker must be set first. 267 DCHECK(!registration()->waiting_version()); 268 registration()->set_waiting_version(pending_version()); 269 ResolvePromise(status, registration(), pending_version()); 270 271 AssociateWaitingVersionToDocuments(context_, pending_version()); 272 273 InstallAndContinue(); 274 } 275 276 // This function corresponds to the spec's _Install algorithm. 277 void ServiceWorkerRegisterJob::InstallAndContinue() { 278 SetPhase(INSTALL); 279 // "Set serviceWorkerRegistration.installingWorker._state to installing." 280 // "Fire install event on the associated ServiceWorkerGlobalScope object." 281 pending_version()->DispatchInstallEvent( 282 -1, 283 base::Bind(&ServiceWorkerRegisterJob::OnInstallFinished, 284 weak_factory_.GetWeakPtr())); 285 } 286 287 void ServiceWorkerRegisterJob::OnInstallFinished( 288 ServiceWorkerStatusCode status) { 289 // "If any handler called waitUntil()..." and the resulting promise 290 // is rejected, abort. 291 // TODO(kinuko,falken): For some error cases (e.g. ServiceWorker is 292 // unexpectedly terminated) we may want to retry sending the event again. 293 if (status != SERVICE_WORKER_OK) { 294 Complete(status); 295 return; 296 } 297 298 SetPhase(STORE); 299 context_->storage()->StoreRegistration( 300 registration(), 301 pending_version(), 302 base::Bind(&ServiceWorkerRegisterJob::OnStoreRegistrationComplete, 303 weak_factory_.GetWeakPtr())); 304 } 305 306 void ServiceWorkerRegisterJob::OnStoreRegistrationComplete( 307 ServiceWorkerStatusCode status) { 308 if (status != SERVICE_WORKER_OK) { 309 Complete(status); 310 return; 311 } 312 313 ActivateAndContinue(); 314 } 315 316 // This function corresponds to the spec's _Activate algorithm. 317 void ServiceWorkerRegisterJob::ActivateAndContinue() { 318 SetPhase(ACTIVATE); 319 320 // "If existingWorker is not null, then: wait for exitingWorker to finish 321 // handling any in-progress requests." 322 // See if we already have an active_version for the scope and it has 323 // controllee documents (if so activating the new version should wait 324 // until we have no documents controlled by the version). 325 if (registration()->active_version() && 326 registration()->active_version()->HasControllee()) { 327 // TODO(kinuko,falken): Currently we immediately return if the existing 328 // registration already has an active version, so we shouldn't come 329 // this way. 330 NOTREACHED(); 331 // TODO(falken): Register an continuation task to wait for NoControllees 332 // notification so that we can resume activation later (see comments 333 // in ServiceWorkerVersion::RemoveControllee). 334 Complete(SERVICE_WORKER_OK); 335 return; 336 } 337 338 // "Set serviceWorkerRegistration.waitingWorker to null." 339 // "Set serviceWorkerRegistration.activeWorker to activatingWorker." 340 DisassociateWaitingVersionFromDocuments( 341 context_, pending_version()->version_id()); 342 registration()->set_waiting_version(NULL); 343 DCHECK(!registration()->active_version()); 344 registration()->set_active_version(pending_version()); 345 346 // "Set serviceWorkerRegistration.activeWorker._state to activating." 347 // "Fire activate event on the associated ServiceWorkerGlobalScope object." 348 // "Set serviceWorkerRegistration.activeWorker._state to active." 349 pending_version()->DispatchActivateEvent( 350 base::Bind(&ServiceWorkerRegisterJob::OnActivateFinished, 351 weak_factory_.GetWeakPtr())); 352 } 353 354 void ServiceWorkerRegisterJob::OnActivateFinished( 355 ServiceWorkerStatusCode status) { 356 // "If any handler called waitUntil()..." and the resulting promise 357 // is rejected, abort. 358 // TODO(kinuko,falken): For some error cases (e.g. ServiceWorker is 359 // unexpectedly terminated) we may want to retry sending the event again. 360 if (status != SERVICE_WORKER_OK) { 361 registration()->set_active_version(NULL); 362 Complete(status); 363 return; 364 } 365 context_->storage()->UpdateToActiveState( 366 registration(), 367 base::Bind(&ServiceWorkerUtils::NoOpStatusCallback)); 368 Complete(SERVICE_WORKER_OK); 369 } 370 371 void ServiceWorkerRegisterJob::Complete(ServiceWorkerStatusCode status) { 372 CompleteInternal(status); 373 context_->job_coordinator()->FinishJob(pattern_, this); 374 } 375 376 void ServiceWorkerRegisterJob::CompleteInternal( 377 ServiceWorkerStatusCode status) { 378 SetPhase(COMPLETE); 379 if (status != SERVICE_WORKER_OK) { 380 if (registration() && registration()->waiting_version()) { 381 DisassociateWaitingVersionFromDocuments( 382 context_, registration()->waiting_version()->version_id()); 383 registration()->set_waiting_version(NULL); 384 } 385 if (registration() && !registration()->active_version()) { 386 context_->storage()->DeleteRegistration( 387 registration()->id(), 388 registration()->script_url().GetOrigin(), 389 base::Bind(&ServiceWorkerUtils::NoOpStatusCallback)); 390 } 391 if (!is_promise_resolved_) 392 ResolvePromise(status, NULL, NULL); 393 } 394 DCHECK(callbacks_.empty()); 395 if (registration()) { 396 context_->storage()->NotifyDoneInstallingRegistration( 397 registration(), pending_version(), status); 398 } 399 } 400 401 void ServiceWorkerRegisterJob::ResolvePromise( 402 ServiceWorkerStatusCode status, 403 ServiceWorkerRegistration* registration, 404 ServiceWorkerVersion* version) { 405 DCHECK(!is_promise_resolved_); 406 is_promise_resolved_ = true; 407 promise_resolved_status_ = status; 408 promise_resolved_registration_ = registration; 409 promise_resolved_version_ = version; 410 for (std::vector<RegistrationCallback>::iterator it = callbacks_.begin(); 411 it != callbacks_.end(); 412 ++it) { 413 it->Run(status, registration, version); 414 } 415 callbacks_.clear(); 416 } 417 418 // static 419 void ServiceWorkerRegisterJob::AssociateWaitingVersionToDocuments( 420 base::WeakPtr<ServiceWorkerContextCore> context, 421 ServiceWorkerVersion* version) { 422 DCHECK(context); 423 DCHECK(version); 424 425 for (scoped_ptr<ServiceWorkerContextCore::ProviderHostIterator> it = 426 context->GetProviderHostIterator(); 427 !it->IsAtEnd(); 428 it->Advance()) { 429 ServiceWorkerProviderHost* host = it->GetProviderHost(); 430 if (!host->IsContextAlive()) 431 continue; 432 if (ServiceWorkerUtils::ScopeMatches(version->scope(), 433 host->document_url())) { 434 // The spec's _Update algorithm says, "upgrades active version to a new 435 // version for the same URL scope.", so skip if the scope (registration) 436 // of |version| is different from that of the current active/waiting 437 // version. 438 if (!host->ValidateVersionForAssociation(version)) 439 continue; 440 441 // TODO(nhiroki): Keep |host->waiting_version()| to be replaced and set 442 // status of them to 'redandunt' after breaking the loop. 443 444 host->SetWaitingVersion(version); 445 // TODO(nhiroki): Set |host|'s installing version to null. 446 } 447 } 448 } 449 450 // static 451 void ServiceWorkerRegisterJob::DisassociateWaitingVersionFromDocuments( 452 base::WeakPtr<ServiceWorkerContextCore> context, 453 int64 version_id) { 454 DCHECK(context); 455 for (scoped_ptr<ServiceWorkerContextCore::ProviderHostIterator> it = 456 context->GetProviderHostIterator(); 457 !it->IsAtEnd(); 458 it->Advance()) { 459 ServiceWorkerProviderHost* host = it->GetProviderHost(); 460 if (!host->IsContextAlive()) 461 continue; 462 if (host->waiting_version() && 463 host->waiting_version()->version_id() == version_id) { 464 host->SetWaitingVersion(NULL); 465 } 466 } 467 } 468 469 } // namespace content 470