Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / content / browser / service_worker / service_worker_register_job.cc
index 098b6b3..a045331 100644 (file)
@@ -21,7 +21,7 @@ void RunSoon(const base::Closure& closure) {
   base::MessageLoop::current()->PostTask(FROM_HERE, closure);
 }
 
-}
+}  // namespace
 
 typedef ServiceWorkerRegisterJobBase::RegistrationJobType RegistrationJobType;
 
@@ -30,6 +30,7 @@ ServiceWorkerRegisterJob::ServiceWorkerRegisterJob(
     const GURL& pattern,
     const GURL& script_url)
     : context_(context),
+      job_type_(REGISTRATION_JOB),
       pattern_(pattern),
       script_url_(script_url),
       phase_(INITIAL),
@@ -37,6 +38,20 @@ ServiceWorkerRegisterJob::ServiceWorkerRegisterJob(
       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)
@@ -47,7 +62,7 @@ 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()))
+    if (process_id != -1 && (phase_ < UPDATE || !new_version()))
       pending_process_ids_.push_back(process_id);
     return;
   }
@@ -58,11 +73,23 @@ void ServiceWorkerRegisterJob::AddCallback(const RegistrationCallback& callback,
 
 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() {
@@ -82,7 +109,7 @@ bool ServiceWorkerRegisterJob::Equals(ServiceWorkerRegisterJobBase* job) {
 }
 
 RegistrationJobType ServiceWorkerRegisterJob::GetType() {
-  return REGISTRATION;
+  return job_type_;
 }
 
 ServiceWorkerRegisterJob::Internal::Internal() {}
@@ -97,20 +124,20 @@ void ServiceWorkerRegisterJob::set_registration(
 }
 
 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) {
@@ -133,9 +160,6 @@ 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;
@@ -145,52 +169,48 @@ void ServiceWorkerRegisterJob::SetPhase(Phase phase) {
   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(),
@@ -198,6 +218,27 @@ void ServiceWorkerRegisterJob::HandleExistingRegistrationAndContinue(
                  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) {
@@ -211,36 +252,31 @@ void ServiceWorkerRegisterJob::RegisterAndContinue(
   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()));
 }
@@ -254,31 +290,27 @@ void ServiceWorkerRegisterJob::OnStartWorkerFinished(
     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()));
@@ -286,19 +318,19 @@ void ServiceWorkerRegisterJob::InstallAndContinue() {
 
 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()));
 }
@@ -310,61 +342,28 @@ void ServiceWorkerRegisterJob::OnStoreRegistrationComplete(
     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);
 }
 
@@ -377,16 +376,19 @@ 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 (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);
@@ -394,8 +396,10 @@ void ServiceWorkerRegisterJob::CompleteInternal(
   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(
@@ -415,53 +419,66 @@ 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);
     }
   }
 }