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 #include "net/base/net_errors.h"
     16 
     17 namespace content {
     18 
     19 namespace {
     20 
     21 void RunSoon(const base::Closure& closure) {
     22   base::MessageLoop::current()->PostTask(FROM_HERE, closure);
     23 }
     24 
     25 }  // namespace
     26 
     27 typedef ServiceWorkerRegisterJobBase::RegistrationJobType RegistrationJobType;
     28 
     29 ServiceWorkerRegisterJob::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 
     42 ServiceWorkerRegisterJob::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 
     56 ServiceWorkerRegisterJob::~ServiceWorkerRegisterJob() {
     57   DCHECK(!context_ ||
     58          phase_ == INITIAL || phase_ == COMPLETE || phase_ == ABORT)
     59       << "Jobs should only be interrupted during shutdown.";
     60 }
     61 
     62 void 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 
     76 void 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 
     97 void 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 
    104 bool 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 
    113 RegistrationJobType ServiceWorkerRegisterJob::GetType() {
    114   return job_type_;
    115 }
    116 
    117 ServiceWorkerRegisterJob::Internal::Internal() {}
    118 
    119 ServiceWorkerRegisterJob::Internal::~Internal() {}
    120 
    121 void 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 
    128 ServiceWorkerRegistration* ServiceWorkerRegisterJob::registration() {
    129   DCHECK(phase_ >= REGISTER || job_type_ == UPDATE_JOB) << phase_;
    130   return internal_.registration.get();
    131 }
    132 
    133 void 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 
    140 ServiceWorkerVersion* ServiceWorkerRegisterJob::new_version() {
    141   DCHECK(phase_ >= UPDATE) << phase_;
    142   return internal_.new_version.get();
    143 }
    144 
    145 void ServiceWorkerRegisterJob::set_uninstalling_registration(
    146     const scoped_refptr<ServiceWorkerRegistration>& registration) {
    147   DCHECK_EQ(phase_, WAIT_FOR_UNINSTALL);
    148   internal_.uninstalling_registration = registration;
    149 }
    150 
    151 ServiceWorkerRegistration*
    152 ServiceWorkerRegisterJob::uninstalling_registration() {
    153   DCHECK_EQ(phase_, WAIT_FOR_UNINSTALL);
    154   return internal_.uninstalling_registration.get();
    155 }
    156 
    157 void 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.
    192 void 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 
    234 void 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.
    264 void 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 
    279 void 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 
    286 void 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.
    310 void 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 
    335 void 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.
    367 void 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 
    390 void 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 
    409 void 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 
    441 void ServiceWorkerRegisterJob::Complete(ServiceWorkerStatusCode status) {
    442   CompleteInternal(status);
    443   context_->job_coordinator()->FinishJob(pattern_, this);
    444 }
    445 
    446 void 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 
    476 void 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 
    493 void 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 
    515 bool ServiceWorkerRegisterJob::OnMessageReceived(const IPC::Message& message) {
    516   return false;
    517 }
    518 
    519 void 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 
    528 void 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 
    551 void 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