Home | History | Annotate | Download | only in service_worker
      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