| // Copyright 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/browser/service_worker/service_worker_register_job.h" |
| |
| #include <vector> |
| |
| #include "base/message_loop/message_loop.h" |
| #include "content/browser/service_worker/service_worker_context_core.h" |
| #include "content/browser/service_worker/service_worker_job_coordinator.h" |
| #include "content/browser/service_worker/service_worker_registration.h" |
| #include "content/browser/service_worker/service_worker_storage.h" |
| #include "content/browser/service_worker/service_worker_utils.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| void RunSoon(const base::Closure& closure) { |
| base::MessageLoop::current()->PostTask(FROM_HERE, closure); |
| } |
| |
| } // namespace |
| |
| typedef ServiceWorkerRegisterJobBase::RegistrationJobType RegistrationJobType; |
| |
| ServiceWorkerRegisterJob::ServiceWorkerRegisterJob( |
| base::WeakPtr<ServiceWorkerContextCore> context, |
| const GURL& pattern, |
| const GURL& script_url) |
| : context_(context), |
| job_type_(REGISTRATION_JOB), |
| pattern_(pattern), |
| script_url_(script_url), |
| phase_(INITIAL), |
| is_promise_resolved_(false), |
| promise_resolved_status_(SERVICE_WORKER_OK), |
| weak_factory_(this) {} |
| |
| ServiceWorkerRegisterJob::ServiceWorkerRegisterJob( |
| base::WeakPtr<ServiceWorkerContextCore> context, |
| ServiceWorkerRegistration* registration) |
| : context_(context), |
| job_type_(UPDATE_JOB), |
| pattern_(registration->pattern()), |
| script_url_(registration->script_url()), |
| phase_(INITIAL), |
| is_promise_resolved_(false), |
| promise_resolved_status_(SERVICE_WORKER_OK), |
| weak_factory_(this) { |
| internal_.registration = registration; |
| } |
| |
| ServiceWorkerRegisterJob::~ServiceWorkerRegisterJob() { |
| DCHECK(!context_ || |
| phase_ == INITIAL || phase_ == COMPLETE || phase_ == ABORT) |
| << "Jobs should only be interrupted during shutdown."; |
| } |
| |
| void ServiceWorkerRegisterJob::AddCallback(const RegistrationCallback& callback, |
| int process_id) { |
| if (!is_promise_resolved_) { |
| callbacks_.push_back(callback); |
| if (process_id != -1 && (phase_ < UPDATE || !new_version())) |
| pending_process_ids_.push_back(process_id); |
| return; |
| } |
| RunSoon(base::Bind( |
| callback, promise_resolved_status_, |
| promise_resolved_registration_, promise_resolved_version_)); |
| } |
| |
| void ServiceWorkerRegisterJob::Start() { |
| SetPhase(START); |
| ServiceWorkerStorage::FindRegistrationCallback next_step; |
| if (job_type_ == REGISTRATION_JOB) { |
| next_step = base::Bind( |
| &ServiceWorkerRegisterJob::ContinueWithRegistration, |
| weak_factory_.GetWeakPtr()); |
| } else { |
| next_step = base::Bind( |
| &ServiceWorkerRegisterJob::ContinueWithUpdate, |
| weak_factory_.GetWeakPtr()); |
| } |
| |
| scoped_refptr<ServiceWorkerRegistration> registration = |
| context_->storage()->GetUninstallingRegistration(pattern_); |
| if (registration) |
| RunSoon(base::Bind(next_step, SERVICE_WORKER_OK, registration)); |
| else |
| context_->storage()->FindRegistrationForPattern(pattern_, next_step); |
| } |
| |
| void ServiceWorkerRegisterJob::Abort() { |
| SetPhase(ABORT); |
| CompleteInternal(SERVICE_WORKER_ERROR_ABORT); |
| // Don't have to call FinishJob() because the caller takes care of removing |
| // the jobs from the queue. |
| } |
| |
| bool ServiceWorkerRegisterJob::Equals(ServiceWorkerRegisterJobBase* job) { |
| if (job->GetType() != GetType()) |
| return false; |
| ServiceWorkerRegisterJob* register_job = |
| static_cast<ServiceWorkerRegisterJob*>(job); |
| return register_job->pattern_ == pattern_ && |
| register_job->script_url_ == script_url_; |
| } |
| |
| RegistrationJobType ServiceWorkerRegisterJob::GetType() { |
| return job_type_; |
| } |
| |
| ServiceWorkerRegisterJob::Internal::Internal() {} |
| |
| ServiceWorkerRegisterJob::Internal::~Internal() {} |
| |
| void ServiceWorkerRegisterJob::set_registration( |
| ServiceWorkerRegistration* registration) { |
| DCHECK(phase_ == START || phase_ == REGISTER) << phase_; |
| DCHECK(!internal_.registration); |
| internal_.registration = registration; |
| } |
| |
| ServiceWorkerRegistration* ServiceWorkerRegisterJob::registration() { |
| DCHECK(phase_ >= REGISTER || job_type_ == UPDATE_JOB) << phase_; |
| return internal_.registration; |
| } |
| |
| void ServiceWorkerRegisterJob::set_new_version( |
| ServiceWorkerVersion* version) { |
| DCHECK(phase_ == UPDATE) << phase_; |
| DCHECK(!internal_.new_version); |
| internal_.new_version = version; |
| } |
| |
| ServiceWorkerVersion* ServiceWorkerRegisterJob::new_version() { |
| DCHECK(phase_ >= UPDATE) << phase_; |
| return internal_.new_version; |
| } |
| |
| void ServiceWorkerRegisterJob::SetPhase(Phase phase) { |
| switch (phase) { |
| case INITIAL: |
| NOTREACHED(); |
| break; |
| case START: |
| DCHECK(phase_ == INITIAL) << phase_; |
| break; |
| case REGISTER: |
| DCHECK(phase_ == START) << phase_; |
| break; |
| case UPDATE: |
| DCHECK(phase_ == START || phase_ == REGISTER) << phase_; |
| break; |
| case INSTALL: |
| DCHECK(phase_ == UPDATE) << phase_; |
| break; |
| case STORE: |
| DCHECK(phase_ == INSTALL) << phase_; |
| break; |
| case COMPLETE: |
| DCHECK(phase_ != INITIAL && phase_ != COMPLETE) << phase_; |
| break; |
| case ABORT: |
| break; |
| } |
| phase_ = phase; |
| } |
| |
| // This function corresponds to the steps in [[Register]] following |
| // "Let registration be the result of running the [[GetRegistration]] algorithm. |
| // Throughout this file, comments in quotes are excerpts from the spec. |
| void ServiceWorkerRegisterJob::ContinueWithRegistration( |
| ServiceWorkerStatusCode status, |
| const scoped_refptr<ServiceWorkerRegistration>& existing_registration) { |
| DCHECK_EQ(REGISTRATION_JOB, job_type_); |
| if (status != SERVICE_WORKER_ERROR_NOT_FOUND && status != SERVICE_WORKER_OK) { |
| Complete(status); |
| return; |
| } |
| |
| if (!existing_registration) { |
| RegisterAndContinue(SERVICE_WORKER_OK); |
| return; |
| } |
| |
| // "Set registration.[[Uninstalling]] to false." |
| existing_registration->AbortPendingClear(); |
| |
| // "If scriptURL is equal to registration.[[ScriptURL]], then:" |
| if (existing_registration->script_url() == script_url_) { |
| // Spec says to resolve with registration.[[GetNewestWorker]]. We come close |
| // by resolving with the active version. |
| set_registration(existing_registration); |
| |
| if (!existing_registration->active_version()) { |
| UpdateAndContinue(); |
| return; |
| } |
| |
| ResolvePromise( |
| status, existing_registration, existing_registration->active_version()); |
| Complete(SERVICE_WORKER_OK); |
| return; |
| } |
| |
| // "Set registration.[[ScriptURL]] to scriptURL." We accomplish this by |
| // deleting the existing registration and registering a new one. |
| // TODO(michaeln): Deactivate the live existing_registration object and |
| // eventually call storage->DeleteVersionResources() when it no longer has any |
| // controllees. |
| context_->storage()->DeleteRegistration( |
| existing_registration->id(), |
| existing_registration->script_url().GetOrigin(), |
| base::Bind(&ServiceWorkerRegisterJob::RegisterAndContinue, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void ServiceWorkerRegisterJob::ContinueWithUpdate( |
| ServiceWorkerStatusCode status, |
| const scoped_refptr<ServiceWorkerRegistration>& existing_registration) { |
| DCHECK_EQ(UPDATE_JOB, job_type_); |
| if (status != SERVICE_WORKER_OK) { |
| Complete(status); |
| return; |
| } |
| |
| if (existing_registration != registration()) { |
| Complete(SERVICE_WORKER_ERROR_NOT_FOUND); |
| return; |
| } |
| |
| // TODO(michaeln): If the last update check was less than 24 hours |
| // ago, depending on the freshness of the cached worker script we |
| // may be able to complete the update job right here. |
| |
| UpdateAndContinue(); |
| } |
| |
| // Creates a new ServiceWorkerRegistration. |
| void ServiceWorkerRegisterJob::RegisterAndContinue( |
| ServiceWorkerStatusCode status) { |
| SetPhase(REGISTER); |
| if (status != SERVICE_WORKER_OK) { |
| // Abort this registration job. |
| Complete(status); |
| return; |
| } |
| |
| set_registration(new ServiceWorkerRegistration( |
| pattern_, script_url_, context_->storage()->NewRegistrationId(), |
| context_)); |
| AssociateProviderHostsToRegistration(registration()); |
| UpdateAndContinue(); |
| } |
| |
| // This function corresponds to the spec's [[Update]] algorithm. |
| void ServiceWorkerRegisterJob::UpdateAndContinue() { |
| SetPhase(UPDATE); |
| context_->storage()->NotifyInstallingRegistration(registration()); |
| |
| // TODO(falken): "If serviceWorkerRegistration.installingWorker is not null.." |
| // then terminate the installing worker. It doesn't make sense to implement |
| // yet since we always activate the worker if install completed, so there can |
| // be no installing worker at this point. |
| |
| // "Let serviceWorker be a newly-created ServiceWorker object..." and start |
| // the worker. |
| set_new_version(new ServiceWorkerVersion( |
| registration(), context_->storage()->NewVersionId(), context_)); |
| |
| bool pause_after_download = job_type_ == UPDATE_JOB; |
| if (pause_after_download) |
| new_version()->embedded_worker()->AddListener(this); |
| new_version()->StartWorkerWithCandidateProcesses( |
| pending_process_ids_, |
| pause_after_download, |
| base::Bind(&ServiceWorkerRegisterJob::OnStartWorkerFinished, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void ServiceWorkerRegisterJob::OnStartWorkerFinished( |
| ServiceWorkerStatusCode status) { |
| // "If serviceWorker fails to start up..." then reject the promise with an |
| // error and abort. |
| if (status != SERVICE_WORKER_OK) { |
| Complete(status); |
| return; |
| } |
| |
| // "Resolve promise with serviceWorker." |
| DCHECK(!registration()->installing_version()); |
| ResolvePromise(status, registration(), new_version()); |
| InstallAndContinue(); |
| } |
| |
| // This function corresponds to the spec's _Install algorithm. |
| void ServiceWorkerRegisterJob::InstallAndContinue() { |
| SetPhase(INSTALL); |
| |
| // "3. Set registration.installingWorker to worker." |
| registration()->SetInstallingVersion(new_version()); |
| |
| // "4. Run the [[UpdateState]] algorithm passing registration.installingWorker |
| // and "installing" as the arguments." |
| new_version()->SetStatus(ServiceWorkerVersion::INSTALLING); |
| |
| // TODO(nhiroki,michaeln): "5. Fire a simple event named updatefound..." |
| |
| // "6. Fire an event named install..." |
| new_version()->DispatchInstallEvent( |
| -1, |
| base::Bind(&ServiceWorkerRegisterJob::OnInstallFinished, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void ServiceWorkerRegisterJob::OnInstallFinished( |
| ServiceWorkerStatusCode status) { |
| // TODO(kinuko,falken): For some error cases (e.g. ServiceWorker is |
| // unexpectedly terminated) we may want to retry sending the event again. |
| if (status != SERVICE_WORKER_OK) { |
| // "8. If installFailed is true, then:..." |
| Complete(status); |
| return; |
| } |
| |
| SetPhase(STORE); |
| registration()->set_last_update_check(base::Time::Now()); |
| context_->storage()->StoreRegistration( |
| registration(), |
| new_version(), |
| base::Bind(&ServiceWorkerRegisterJob::OnStoreRegistrationComplete, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void ServiceWorkerRegisterJob::OnStoreRegistrationComplete( |
| ServiceWorkerStatusCode status) { |
| if (status != SERVICE_WORKER_OK) { |
| Complete(status); |
| return; |
| } |
| |
| // "9. If registration.waitingWorker is not null, then:..." |
| if (registration()->waiting_version()) { |
| // "1. Run the [[UpdateState]] algorithm passing registration.waitingWorker |
| // and "redundant" as the arguments." |
| registration()->waiting_version()->SetStatus( |
| ServiceWorkerVersion::REDUNDANT); |
| } |
| |
| // "10. Set registration.waitingWorker to registration.installingWorker." |
| // "11. Set registration.installingWorker to null." |
| registration()->SetWaitingVersion(new_version()); |
| |
| // "12. Run the [[UpdateState]] algorithm passing registration.waitingWorker |
| // and "installed" as the arguments." |
| new_version()->SetStatus(ServiceWorkerVersion::INSTALLED); |
| |
| // TODO(michaeln): "13. If activateImmediate is true, then..." |
| |
| // "14. Wait until no document is using registration as their |
| // Service Worker registration." |
| registration()->ActivateWaitingVersionWhenReady(); |
| |
| Complete(SERVICE_WORKER_OK); |
| } |
| |
| void ServiceWorkerRegisterJob::Complete(ServiceWorkerStatusCode status) { |
| CompleteInternal(status); |
| context_->job_coordinator()->FinishJob(pattern_, this); |
| } |
| |
| void ServiceWorkerRegisterJob::CompleteInternal( |
| ServiceWorkerStatusCode status) { |
| SetPhase(COMPLETE); |
| if (status != SERVICE_WORKER_OK) { |
| if (registration()) { |
| if (new_version()) { |
| registration()->UnsetVersion(new_version()); |
| new_version()->Doom(); |
| } |
| if (!registration()->waiting_version() && |
| !registration()->active_version()) { |
| registration()->NotifyRegistrationFailed(); |
| context_->storage()->DeleteRegistration( |
| registration()->id(), |
| registration()->script_url().GetOrigin(), |
| base::Bind(&ServiceWorkerUtils::NoOpStatusCallback)); |
| } |
| } |
| if (!is_promise_resolved_) |
| ResolvePromise(status, NULL, NULL); |
| } |
| DCHECK(callbacks_.empty()); |
| if (registration()) { |
| context_->storage()->NotifyDoneInstallingRegistration( |
| registration(), new_version(), status); |
| } |
| if (new_version()) |
| new_version()->embedded_worker()->RemoveListener(this); |
| } |
| |
| void ServiceWorkerRegisterJob::ResolvePromise( |
| ServiceWorkerStatusCode status, |
| ServiceWorkerRegistration* registration, |
| ServiceWorkerVersion* version) { |
| DCHECK(!is_promise_resolved_); |
| is_promise_resolved_ = true; |
| promise_resolved_status_ = status; |
| promise_resolved_registration_ = registration; |
| promise_resolved_version_ = version; |
| for (std::vector<RegistrationCallback>::iterator it = callbacks_.begin(); |
| it != callbacks_.end(); |
| ++it) { |
| it->Run(status, registration, version); |
| } |
| callbacks_.clear(); |
| } |
| |
| void ServiceWorkerRegisterJob::OnPausedAfterDownload() { |
| // This happens prior to OnStartWorkerFinished time. |
| scoped_refptr<ServiceWorkerVersion> most_recent_version = |
| registration()->waiting_version() ? |
| registration()->waiting_version() : |
| registration()->active_version(); |
| DCHECK(most_recent_version); |
| int64 most_recent_script_id = |
| most_recent_version->script_cache_map()->Lookup(script_url_); |
| int64 new_script_id = |
| new_version()->script_cache_map()->Lookup(script_url_); |
| |
| // TODO(michaeln): It would be better to compare as the new resource |
| // is being downloaded and to avoid writing it to disk until we know |
| // its needed. |
| context_->storage()->CompareScriptResources( |
| most_recent_script_id, new_script_id, |
| base::Bind(&ServiceWorkerRegisterJob::OnCompareScriptResourcesComplete, |
| weak_factory_.GetWeakPtr(), |
| most_recent_version)); |
| } |
| |
| bool ServiceWorkerRegisterJob::OnMessageReceived(const IPC::Message& message) { |
| return false; |
| } |
| |
| void ServiceWorkerRegisterJob::OnCompareScriptResourcesComplete( |
| ServiceWorkerVersion* most_recent_version, |
| ServiceWorkerStatusCode status, |
| bool are_equal) { |
| if (are_equal) { |
| // Only bump the last check time when we've bypassed the browser cache. |
| base::TimeDelta time_since_last_check = |
| base::Time::Now() - registration()->last_update_check(); |
| if (time_since_last_check > base::TimeDelta::FromHours(24)) { |
| registration()->set_last_update_check(base::Time::Now()); |
| context_->storage()->UpdateLastUpdateCheckTime(registration()); |
| } |
| |
| ResolvePromise(SERVICE_WORKER_OK, registration(), most_recent_version); |
| Complete(SERVICE_WORKER_ERROR_EXISTS); |
| return; |
| } |
| |
| // Proceed with really starting the worker. |
| new_version()->embedded_worker()->ResumeAfterDownload(); |
| new_version()->embedded_worker()->RemoveListener(this); |
| } |
| |
| void ServiceWorkerRegisterJob::AssociateProviderHostsToRegistration( |
| ServiceWorkerRegistration* registration) { |
| DCHECK(registration); |
| for (scoped_ptr<ServiceWorkerContextCore::ProviderHostIterator> it = |
| context_->GetProviderHostIterator(); |
| !it->IsAtEnd(); it->Advance()) { |
| ServiceWorkerProviderHost* host = it->GetProviderHost(); |
| if (ServiceWorkerUtils::ScopeMatches(registration->pattern(), |
| host->document_url())) { |
| if (host->CanAssociateRegistration(registration)) |
| host->AssociateRegistration(registration); |
| } |
| } |
| } |
| |
| } // namespace content |