base::MessageLoop::current()->PostTask(FROM_HERE, closure);
}
-}
+} // namespace
typedef ServiceWorkerRegisterJobBase::RegistrationJobType RegistrationJobType;
const GURL& pattern,
const GURL& script_url)
: context_(context),
+ job_type_(REGISTRATION_JOB),
pattern_(pattern),
script_url_(script_url),
phase_(INITIAL),
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)
int process_id) {
if (!is_promise_resolved_) {
callbacks_.push_back(callback);
- if (process_id != -1 && (phase_ < UPDATE || !pending_version()))
+ if (process_id != -1 && (phase_ < UPDATE || !new_version()))
pending_process_ids_.push_back(process_id);
return;
}
void ServiceWorkerRegisterJob::Start() {
SetPhase(START);
- context_->storage()->FindRegistrationForPattern(
- pattern_,
- base::Bind(
- &ServiceWorkerRegisterJob::HandleExistingRegistrationAndContinue,
- weak_factory_.GetWeakPtr()));
+ 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() {
}
RegistrationJobType ServiceWorkerRegisterJob::GetType() {
- return REGISTRATION;
+ return job_type_;
}
ServiceWorkerRegisterJob::Internal::Internal() {}
}
ServiceWorkerRegistration* ServiceWorkerRegisterJob::registration() {
- DCHECK(phase_ >= REGISTER) << phase_;
+ DCHECK(phase_ >= REGISTER || job_type_ == UPDATE_JOB) << phase_;
return internal_.registration;
}
-void ServiceWorkerRegisterJob::set_pending_version(
+void ServiceWorkerRegisterJob::set_new_version(
ServiceWorkerVersion* version) {
DCHECK(phase_ == UPDATE) << phase_;
- DCHECK(!internal_.pending_version);
- internal_.pending_version = version;
+ DCHECK(!internal_.new_version);
+ internal_.new_version = version;
}
-ServiceWorkerVersion* ServiceWorkerRegisterJob::pending_version() {
+ServiceWorkerVersion* ServiceWorkerRegisterJob::new_version() {
DCHECK(phase_ >= UPDATE) << phase_;
- return internal_.pending_version;
+ return internal_.new_version;
}
void ServiceWorkerRegisterJob::SetPhase(Phase phase) {
case STORE:
DCHECK(phase_ == INSTALL) << phase_;
break;
- case ACTIVATE:
- DCHECK(phase_ == STORE) << phase_;
- break;
case COMPLETE:
DCHECK(phase_ != INITIAL && phase_ != COMPLETE) << phase_;
break;
phase_ = phase;
}
-// This function corresponds to the steps in Register following
-// "Let serviceWorkerRegistration be _GetRegistration(scope)"
-// |existing_registration| corresponds to serviceWorkerRegistration.
+// 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::HandleExistingRegistrationAndContinue(
+void ServiceWorkerRegisterJob::ContinueWithRegistration(
ServiceWorkerStatusCode status,
const scoped_refptr<ServiceWorkerRegistration>& existing_registration) {
- // On unexpected error, abort this registration job.
+ DCHECK_EQ(REGISTRATION_JOB, job_type_);
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_) {
+ 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 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);
+ UpdateAndContinue();
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.
+ // "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.
+ // eventually call storage->DeleteVersionResources() when it no longer has any
+ // controllees.
context_->storage()->DeleteRegistration(
existing_registration->id(),
existing_registration->script_url().GetOrigin(),
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) {
set_registration(new ServiceWorkerRegistration(
pattern_, script_url_, context_->storage()->NewRegistrationId(),
context_));
- context_->storage()->NotifyInstallingRegistration(registration());
- UpdateAndContinue(SERVICE_WORKER_OK);
+ AssociateProviderHostsToRegistration(registration());
+ UpdateAndContinue();
}
-// This function corresponds to the spec's _Update algorithm.
-void ServiceWorkerRegisterJob::UpdateAndContinue(
- ServiceWorkerStatusCode status) {
+// This function corresponds to the spec's [[Update]] algorithm.
+void ServiceWorkerRegisterJob::UpdateAndContinue() {
SetPhase(UPDATE);
- if (status != SERVICE_WORKER_OK) {
- // Abort this registration job.
- Complete(status);
- return;
- }
+ 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.
- // 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(
+ set_new_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(
+ 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()));
}
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());
-
+ DCHECK(!registration()->installing_version());
+ ResolvePromise(status, registration(), new_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(
+
+ // "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) {
- // "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) {
+ // "8. If installFailed is true, then:..."
Complete(status);
return;
}
SetPhase(STORE);
+ registration()->set_last_update_check(base::Time::Now());
context_->storage()->StoreRegistration(
registration(),
- pending_version(),
+ new_version(),
base::Bind(&ServiceWorkerRegisterJob::OnStoreRegistrationComplete,
weak_factory_.GetWeakPtr()));
}
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;
+ // "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);
}
- // "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()));
-}
+ // "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();
-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);
}
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 (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(), pending_version(), status);
+ registration(), new_version(), status);
}
+ if (new_version())
+ new_version()->embedded_worker()->RemoveListener(this);
}
void ServiceWorkerRegisterJob::ResolvePromise(
callbacks_.clear();
}
-// static
-void ServiceWorkerRegisterJob::AssociateWaitingVersionToDocuments(
- base::WeakPtr<ServiceWorkerContextCore> context,
- ServiceWorkerVersion* version) {
- DCHECK(context);
- DCHECK(version);
+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));
+}
- 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.
+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);
}
-// static
-void ServiceWorkerRegisterJob::DisassociateWaitingVersionFromDocuments(
- base::WeakPtr<ServiceWorkerContextCore> context,
- int64 version_id) {
- DCHECK(context);
+void ServiceWorkerRegisterJob::AssociateProviderHostsToRegistration(
+ ServiceWorkerRegistration* registration) {
+ DCHECK(registration);
for (scoped_ptr<ServiceWorkerContextCore::ProviderHostIterator> it =
- context->GetProviderHostIterator();
- !it->IsAtEnd();
- it->Advance()) {
+ 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);
+ if (ServiceWorkerUtils::ScopeMatches(registration->pattern(),
+ host->document_url())) {
+ if (host->CanAssociateRegistration(registration))
+ host->AssociateRegistration(registration);
}
}
}