summaryrefslogtreecommitdiffstats
path: root/chromium/content/browser/service_worker/service_worker_register_job.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/content/browser/service_worker/service_worker_register_job.cc')
-rw-r--r--chromium/content/browser/service_worker/service_worker_register_job.cc538
1 files changed, 445 insertions, 93 deletions
diff --git a/chromium/content/browser/service_worker/service_worker_register_job.cc b/chromium/content/browser/service_worker/service_worker_register_job.cc
index 90333595891..098b6b31df8 100644
--- a/chromium/content/browser/service_worker/service_worker_register_job.cc
+++ b/chromium/content/browser/service_worker/service_worker_register_job.cc
@@ -4,114 +4,466 @@
#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/public/browser/browser_thread.h"
-#include "url/gurl.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);
+}
+
+}
+
+typedef ServiceWorkerRegisterJobBase::RegistrationJobType RegistrationJobType;
+
ServiceWorkerRegisterJob::ServiceWorkerRegisterJob(
- const base::WeakPtr<ServiceWorkerStorage>& storage,
- const RegistrationCompleteCallback& callback)
- : storage_(storage), callback_(callback), weak_factory_(this) {}
-
-ServiceWorkerRegisterJob::~ServiceWorkerRegisterJob() {}
-
-void ServiceWorkerRegisterJob::StartRegister(const GURL& pattern,
- const GURL& script_url) {
- // Set up a chain of callbacks, in reverse order. Each of these
- // callbacks may be called asynchronously by the previous callback.
- ServiceWorkerStorage::RegistrationCallback finish_registration(base::Bind(
- &ServiceWorkerRegisterJob::RegisterComplete, weak_factory_.GetWeakPtr()));
-
- ServiceWorkerStorage::UnregistrationCallback register_new(
- base::Bind(&ServiceWorkerRegisterJob::RegisterPatternAndContinue,
- weak_factory_.GetWeakPtr(),
- pattern,
- script_url,
- finish_registration));
-
- ServiceWorkerStorage::FindRegistrationCallback unregister_old(
- base::Bind(&ServiceWorkerRegisterJob::UnregisterPatternAndContinue,
- weak_factory_.GetWeakPtr(),
- pattern,
- script_url,
- register_new));
-
- storage_->FindRegistrationForPattern(pattern, unregister_old);
-}
-
-void ServiceWorkerRegisterJob::StartUnregister(const GURL& pattern) {
- // Set up a chain of callbacks, in reverse order. Each of these
- // callbacks may be called asynchronously by the previous callback.
- ServiceWorkerStorage::UnregistrationCallback finish_unregistration(
- base::Bind(&ServiceWorkerRegisterJob::UnregisterComplete,
+ base::WeakPtr<ServiceWorkerContextCore> context,
+ const GURL& pattern,
+ const GURL& script_url)
+ : context_(context),
+ pattern_(pattern),
+ script_url_(script_url),
+ phase_(INITIAL),
+ is_promise_resolved_(false),
+ promise_resolved_status_(SERVICE_WORKER_OK),
+ weak_factory_(this) {}
+
+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 || !pending_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);
+ context_->storage()->FindRegistrationForPattern(
+ pattern_,
+ base::Bind(
+ &ServiceWorkerRegisterJob::HandleExistingRegistrationAndContinue,
+ weak_factory_.GetWeakPtr()));
+}
+
+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 REGISTRATION;
+}
+
+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) << phase_;
+ return internal_.registration;
+}
+
+void ServiceWorkerRegisterJob::set_pending_version(
+ ServiceWorkerVersion* version) {
+ DCHECK(phase_ == UPDATE) << phase_;
+ DCHECK(!internal_.pending_version);
+ internal_.pending_version = version;
+}
+
+ServiceWorkerVersion* ServiceWorkerRegisterJob::pending_version() {
+ DCHECK(phase_ >= UPDATE) << phase_;
+ return internal_.pending_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 ACTIVATE:
+ DCHECK(phase_ == STORE) << 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 serviceWorkerRegistration be _GetRegistration(scope)"
+// |existing_registration| corresponds to serviceWorkerRegistration.
+// Throughout this file, comments in quotes are excerpts from the spec.
+void ServiceWorkerRegisterJob::HandleExistingRegistrationAndContinue(
+ ServiceWorkerStatusCode status,
+ const scoped_refptr<ServiceWorkerRegistration>& existing_registration) {
+ // On unexpected error, abort this registration job.
+ if (status != SERVICE_WORKER_ERROR_NOT_FOUND && status != SERVICE_WORKER_OK) {
+ Complete(status);
+ return;
+ }
+
+ // "If serviceWorkerRegistration is not null and script is equal to
+ // serviceWorkerRegistration.scriptUrl..." resolve with the existing
+ // registration and abort.
+ if (existing_registration.get() &&
+ existing_registration->script_url() == script_url_) {
+ set_registration(existing_registration);
+ // If there's no active version, go ahead to Update (this isn't in the spec
+ // but seems reasonable, and without SoftUpdate implemented we can never
+ // Update otherwise).
+ if (!existing_registration->active_version()) {
+ UpdateAndContinue(status);
+ return;
+ }
+ ResolvePromise(
+ status, existing_registration, existing_registration->active_version());
+ Complete(SERVICE_WORKER_OK);
+ return;
+ }
+
+ // "If serviceWorkerRegistration is null..." create a new registration.
+ if (!existing_registration.get()) {
+ RegisterAndContinue(SERVICE_WORKER_OK);
+ return;
+ }
+
+ // On script URL mismatch, "set serviceWorkerRegistration.scriptUrl to
+ // script." We accomplish this by deleting the existing registration and
+ // registering a new one.
+ // TODO(falken): Match the spec. We now throw away the active_version_ and
+ // waiting_version_ of the existing registration, which isn't in the spec.
+ // 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()));
+}
- ServiceWorkerStorage::FindRegistrationCallback unregister(
- base::Bind(&ServiceWorkerRegisterJob::UnregisterPatternAndContinue,
- weak_factory_.GetWeakPtr(),
- pattern,
- GURL(),
- finish_unregistration));
+// Creates a new ServiceWorkerRegistration.
+void ServiceWorkerRegisterJob::RegisterAndContinue(
+ ServiceWorkerStatusCode status) {
+ SetPhase(REGISTER);
+ if (status != SERVICE_WORKER_OK) {
+ // Abort this registration job.
+ Complete(status);
+ return;
+ }
- storage_->FindRegistrationForPattern(pattern, unregister);
+ set_registration(new ServiceWorkerRegistration(
+ pattern_, script_url_, context_->storage()->NewRegistrationId(),
+ context_));
+ context_->storage()->NotifyInstallingRegistration(registration());
+ UpdateAndContinue(SERVICE_WORKER_OK);
}
-void ServiceWorkerRegisterJob::RegisterPatternAndContinue(
- const GURL& pattern,
- const GURL& script_url,
- const ServiceWorkerStorage::RegistrationCallback& callback,
- ServiceWorkerRegistrationStatus previous_status) {
- if (previous_status != REGISTRATION_OK) {
- BrowserThread::PostTask(
- BrowserThread::IO,
- FROM_HERE,
- base::Bind(callback,
- previous_status,
- scoped_refptr<ServiceWorkerRegistration>()));
+// This function corresponds to the spec's _Update algorithm.
+void ServiceWorkerRegisterJob::UpdateAndContinue(
+ ServiceWorkerStatusCode status) {
+ SetPhase(UPDATE);
+ if (status != SERVICE_WORKER_OK) {
+ // Abort this registration job.
+ Complete(status);
return;
}
- // TODO: Eventually RegisterInternal will be replaced by an asynchronous
- // operation. Pass its resulting status through 'callback'.
- scoped_refptr<ServiceWorkerRegistration> registration =
- storage_->RegisterInternal(pattern, script_url);
- BrowserThread::PostTask(BrowserThread::IO,
- FROM_HERE,
- base::Bind(callback, REGISTRATION_OK, 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.
+ // TODO(nhiroki): Check 'installing_version()' instead when it's supported.
+ DCHECK(!registration()->waiting_version());
+
+ // "Let serviceWorker be a newly-created ServiceWorker object..." and start
+ // the worker.
+ set_pending_version(new ServiceWorkerVersion(
+ registration(), context_->storage()->NewVersionId(), context_));
+
+ // TODO(michaeln): Start the worker into a paused state where the
+ // script resource is downloaded but not yet evaluated.
+ pending_version()->StartWorkerWithCandidateProcesses(
+ pending_process_ids_,
+ base::Bind(&ServiceWorkerRegisterJob::OnStartWorkerFinished,
+ weak_factory_.GetWeakPtr()));
}
-void ServiceWorkerRegisterJob::UnregisterPatternAndContinue(
- const GURL& pattern,
- const GURL& new_script_url,
- const ServiceWorkerStorage::UnregistrationCallback& callback,
- bool found,
- ServiceWorkerRegistrationStatus previous_status,
- const scoped_refptr<ServiceWorkerRegistration>& previous_registration) {
-
- // The previous registration may not exist, which is ok.
- if (previous_status == REGISTRATION_OK && found &&
- (new_script_url.is_empty() ||
- previous_registration->script_url() != new_script_url)) {
- // TODO: Eventually UnregisterInternal will be replaced by an
- // asynchronous operation. Pass its resulting status though
- // 'callback'.
- storage_->UnregisterInternal(pattern);
- }
- BrowserThread::PostTask(
- BrowserThread::IO, FROM_HERE, base::Bind(callback, previous_status));
-}
-
-void ServiceWorkerRegisterJob::UnregisterComplete(
- ServiceWorkerRegistrationStatus status) {
- callback_.Run(this, status, NULL);
-}
-
-void ServiceWorkerRegisterJob::RegisterComplete(
- ServiceWorkerRegistrationStatus status,
- const scoped_refptr<ServiceWorkerRegistration>& registration) {
- callback_.Run(this, status, registration);
+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;
+ }
+
+ // TODO(michaeln): Compare the old and new script.
+ // If different unpause the worker and continue with
+ // the job. If the same ResolvePromise with the current
+ // version and complete the job, throwing away the new version
+ // since there's nothing new.
+
+ // "Resolve promise with serviceWorker."
+ // Although the spec doesn't set waitingWorker until after resolving the
+ // promise, our system's resolving works by passing ServiceWorkerRegistration
+ // to the callbacks, so waitingWorker must be set first.
+ DCHECK(!registration()->waiting_version());
+ registration()->set_waiting_version(pending_version());
+ ResolvePromise(status, registration(), pending_version());
+
+ AssociateWaitingVersionToDocuments(context_, pending_version());
+
+ InstallAndContinue();
+}
+
+// This function corresponds to the spec's _Install algorithm.
+void ServiceWorkerRegisterJob::InstallAndContinue() {
+ SetPhase(INSTALL);
+ // "Set serviceWorkerRegistration.installingWorker._state to installing."
+ // "Fire install event on the associated ServiceWorkerGlobalScope object."
+ pending_version()->DispatchInstallEvent(
+ -1,
+ base::Bind(&ServiceWorkerRegisterJob::OnInstallFinished,
+ weak_factory_.GetWeakPtr()));
+}
+
+void ServiceWorkerRegisterJob::OnInstallFinished(
+ ServiceWorkerStatusCode status) {
+ // "If any handler called waitUntil()..." and the resulting promise
+ // is rejected, abort.
+ // 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) {
+ Complete(status);
+ return;
+ }
+
+ SetPhase(STORE);
+ context_->storage()->StoreRegistration(
+ registration(),
+ pending_version(),
+ base::Bind(&ServiceWorkerRegisterJob::OnStoreRegistrationComplete,
+ weak_factory_.GetWeakPtr()));
+}
+
+void ServiceWorkerRegisterJob::OnStoreRegistrationComplete(
+ ServiceWorkerStatusCode status) {
+ if (status != SERVICE_WORKER_OK) {
+ Complete(status);
+ return;
+ }
+
+ ActivateAndContinue();
+}
+
+// This function corresponds to the spec's _Activate algorithm.
+void ServiceWorkerRegisterJob::ActivateAndContinue() {
+ SetPhase(ACTIVATE);
+
+ // "If existingWorker is not null, then: wait for exitingWorker to finish
+ // handling any in-progress requests."
+ // See if we already have an active_version for the scope and it has
+ // controllee documents (if so activating the new version should wait
+ // until we have no documents controlled by the version).
+ if (registration()->active_version() &&
+ registration()->active_version()->HasControllee()) {
+ // TODO(kinuko,falken): Currently we immediately return if the existing
+ // registration already has an active version, so we shouldn't come
+ // this way.
+ NOTREACHED();
+ // TODO(falken): Register an continuation task to wait for NoControllees
+ // notification so that we can resume activation later (see comments
+ // in ServiceWorkerVersion::RemoveControllee).
+ Complete(SERVICE_WORKER_OK);
+ return;
+ }
+
+ // "Set serviceWorkerRegistration.waitingWorker to null."
+ // "Set serviceWorkerRegistration.activeWorker to activatingWorker."
+ DisassociateWaitingVersionFromDocuments(
+ context_, pending_version()->version_id());
+ registration()->set_waiting_version(NULL);
+ DCHECK(!registration()->active_version());
+ registration()->set_active_version(pending_version());
+
+ // "Set serviceWorkerRegistration.activeWorker._state to activating."
+ // "Fire activate event on the associated ServiceWorkerGlobalScope object."
+ // "Set serviceWorkerRegistration.activeWorker._state to active."
+ pending_version()->DispatchActivateEvent(
+ base::Bind(&ServiceWorkerRegisterJob::OnActivateFinished,
+ weak_factory_.GetWeakPtr()));
+}
+
+void ServiceWorkerRegisterJob::OnActivateFinished(
+ ServiceWorkerStatusCode status) {
+ // "If any handler called waitUntil()..." and the resulting promise
+ // is rejected, abort.
+ // 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) {
+ registration()->set_active_version(NULL);
+ Complete(status);
+ return;
+ }
+ context_->storage()->UpdateToActiveState(
+ registration(),
+ base::Bind(&ServiceWorkerUtils::NoOpStatusCallback));
+ 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() && registration()->waiting_version()) {
+ DisassociateWaitingVersionFromDocuments(
+ context_, registration()->waiting_version()->version_id());
+ registration()->set_waiting_version(NULL);
+ }
+ if (registration() && !registration()->active_version()) {
+ 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(), pending_version(), status);
+ }
+}
+
+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();
+}
+
+// static
+void ServiceWorkerRegisterJob::AssociateWaitingVersionToDocuments(
+ base::WeakPtr<ServiceWorkerContextCore> context,
+ ServiceWorkerVersion* version) {
+ DCHECK(context);
+ DCHECK(version);
+
+ for (scoped_ptr<ServiceWorkerContextCore::ProviderHostIterator> it =
+ context->GetProviderHostIterator();
+ !it->IsAtEnd();
+ it->Advance()) {
+ ServiceWorkerProviderHost* host = it->GetProviderHost();
+ if (!host->IsContextAlive())
+ continue;
+ if (ServiceWorkerUtils::ScopeMatches(version->scope(),
+ host->document_url())) {
+ // The spec's _Update algorithm says, "upgrades active version to a new
+ // version for the same URL scope.", so skip if the scope (registration)
+ // of |version| is different from that of the current active/waiting
+ // version.
+ if (!host->ValidateVersionForAssociation(version))
+ continue;
+
+ // TODO(nhiroki): Keep |host->waiting_version()| to be replaced and set
+ // status of them to 'redandunt' after breaking the loop.
+
+ host->SetWaitingVersion(version);
+ // TODO(nhiroki): Set |host|'s installing version to null.
+ }
+ }
+}
+
+// static
+void ServiceWorkerRegisterJob::DisassociateWaitingVersionFromDocuments(
+ base::WeakPtr<ServiceWorkerContextCore> context,
+ int64 version_id) {
+ DCHECK(context);
+ for (scoped_ptr<ServiceWorkerContextCore::ProviderHostIterator> it =
+ context->GetProviderHostIterator();
+ !it->IsAtEnd();
+ it->Advance()) {
+ ServiceWorkerProviderHost* host = it->GetProviderHost();
+ if (!host->IsContextAlive())
+ continue;
+ if (host->waiting_version() &&
+ host->waiting_version()->version_id() == version_id) {
+ host->SetWaitingVersion(NULL);
+ }
+ }
}
} // namespace content