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.
5 #include "content/browser/service_worker/service_worker_register_job.h"
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"
20 void RunSoon(const base::Closure& closure) {
21 base::MessageLoop::current()->PostTask(FROM_HERE, closure);
26 typedef ServiceWorkerRegisterJobBase::RegistrationJobType RegistrationJobType;
28 ServiceWorkerRegisterJob::ServiceWorkerRegisterJob(
29 base::WeakPtr<ServiceWorkerContextCore> context,
31 const GURL& script_url)
33 job_type_(REGISTRATION_JOB),
35 script_url_(script_url),
37 is_promise_resolved_(false),
38 promise_resolved_status_(SERVICE_WORKER_OK),
39 weak_factory_(this) {}
41 ServiceWorkerRegisterJob::ServiceWorkerRegisterJob(
42 base::WeakPtr<ServiceWorkerContextCore> context,
43 ServiceWorkerRegistration* registration)
45 job_type_(UPDATE_JOB),
46 pattern_(registration->pattern()),
47 script_url_(registration->script_url()),
49 is_promise_resolved_(false),
50 promise_resolved_status_(SERVICE_WORKER_OK),
52 internal_.registration = registration;
55 ServiceWorkerRegisterJob::~ServiceWorkerRegisterJob() {
57 phase_ == INITIAL || phase_ == COMPLETE || phase_ == ABORT)
58 << "Jobs should only be interrupted during shutdown.";
61 void ServiceWorkerRegisterJob::AddCallback(const RegistrationCallback& callback,
63 if (!is_promise_resolved_) {
64 callbacks_.push_back(callback);
65 if (process_id != -1 && (phase_ < UPDATE || !new_version()))
66 pending_process_ids_.push_back(process_id);
70 callback, promise_resolved_status_,
71 promise_resolved_registration_, promise_resolved_version_));
74 void ServiceWorkerRegisterJob::Start() {
76 ServiceWorkerStorage::FindRegistrationCallback next_step;
77 if (job_type_ == REGISTRATION_JOB) {
78 next_step = base::Bind(
79 &ServiceWorkerRegisterJob::ContinueWithRegistration,
80 weak_factory_.GetWeakPtr());
82 next_step = base::Bind(
83 &ServiceWorkerRegisterJob::ContinueWithUpdate,
84 weak_factory_.GetWeakPtr());
87 scoped_refptr<ServiceWorkerRegistration> registration =
88 context_->storage()->GetUninstallingRegistration(pattern_);
90 RunSoon(base::Bind(next_step, SERVICE_WORKER_OK, registration));
92 context_->storage()->FindRegistrationForPattern(pattern_, next_step);
95 void ServiceWorkerRegisterJob::Abort() {
97 CompleteInternal(SERVICE_WORKER_ERROR_ABORT);
98 // Don't have to call FinishJob() because the caller takes care of removing
99 // the jobs from the queue.
102 bool ServiceWorkerRegisterJob::Equals(ServiceWorkerRegisterJobBase* job) {
103 if (job->GetType() != GetType())
105 ServiceWorkerRegisterJob* register_job =
106 static_cast<ServiceWorkerRegisterJob*>(job);
107 return register_job->pattern_ == pattern_ &&
108 register_job->script_url_ == script_url_;
111 RegistrationJobType ServiceWorkerRegisterJob::GetType() {
115 ServiceWorkerRegisterJob::Internal::Internal() {}
117 ServiceWorkerRegisterJob::Internal::~Internal() {}
119 void ServiceWorkerRegisterJob::set_registration(
120 ServiceWorkerRegistration* registration) {
121 DCHECK(phase_ == START || phase_ == REGISTER) << phase_;
122 DCHECK(!internal_.registration);
123 internal_.registration = registration;
126 ServiceWorkerRegistration* ServiceWorkerRegisterJob::registration() {
127 DCHECK(phase_ >= REGISTER || job_type_ == UPDATE_JOB) << phase_;
128 return internal_.registration;
131 void ServiceWorkerRegisterJob::set_new_version(
132 ServiceWorkerVersion* version) {
133 DCHECK(phase_ == UPDATE) << phase_;
134 DCHECK(!internal_.new_version);
135 internal_.new_version = version;
138 ServiceWorkerVersion* ServiceWorkerRegisterJob::new_version() {
139 DCHECK(phase_ >= UPDATE) << phase_;
140 return internal_.new_version;
143 void ServiceWorkerRegisterJob::SetPhase(Phase phase) {
149 DCHECK(phase_ == INITIAL) << phase_;
152 DCHECK(phase_ == START) << phase_;
155 DCHECK(phase_ == START || phase_ == REGISTER) << phase_;
158 DCHECK(phase_ == UPDATE) << phase_;
161 DCHECK(phase_ == INSTALL) << phase_;
164 DCHECK(phase_ != INITIAL && phase_ != COMPLETE) << phase_;
172 // This function corresponds to the steps in [[Register]] following
173 // "Let registration be the result of running the [[GetRegistration]] algorithm.
174 // Throughout this file, comments in quotes are excerpts from the spec.
175 void ServiceWorkerRegisterJob::ContinueWithRegistration(
176 ServiceWorkerStatusCode status,
177 const scoped_refptr<ServiceWorkerRegistration>& existing_registration) {
178 DCHECK_EQ(REGISTRATION_JOB, job_type_);
179 if (status != SERVICE_WORKER_ERROR_NOT_FOUND && status != SERVICE_WORKER_OK) {
184 if (!existing_registration) {
185 RegisterAndContinue(SERVICE_WORKER_OK);
189 // "Set registration.[[Uninstalling]] to false."
190 existing_registration->AbortPendingClear();
192 // "If scriptURL is equal to registration.[[ScriptURL]], then:"
193 if (existing_registration->script_url() == script_url_) {
194 // Spec says to resolve with registration.[[GetNewestWorker]]. We come close
195 // by resolving with the active version.
196 set_registration(existing_registration);
198 if (!existing_registration->active_version()) {
204 status, existing_registration, existing_registration->active_version());
205 Complete(SERVICE_WORKER_OK);
209 // "Set registration.[[ScriptURL]] to scriptURL." We accomplish this by
210 // deleting the existing registration and registering a new one.
211 // TODO(michaeln): Deactivate the live existing_registration object and
212 // eventually call storage->DeleteVersionResources() when it no longer has any
214 context_->storage()->DeleteRegistration(
215 existing_registration->id(),
216 existing_registration->script_url().GetOrigin(),
217 base::Bind(&ServiceWorkerRegisterJob::RegisterAndContinue,
218 weak_factory_.GetWeakPtr()));
221 void ServiceWorkerRegisterJob::ContinueWithUpdate(
222 ServiceWorkerStatusCode status,
223 const scoped_refptr<ServiceWorkerRegistration>& existing_registration) {
224 DCHECK_EQ(UPDATE_JOB, job_type_);
225 if (status != SERVICE_WORKER_OK) {
230 if (existing_registration != registration()) {
231 Complete(SERVICE_WORKER_ERROR_NOT_FOUND);
235 // TODO(michaeln): If the last update check was less than 24 hours
236 // ago, depending on the freshness of the cached worker script we
237 // may be able to complete the update job right here.
242 // Creates a new ServiceWorkerRegistration.
243 void ServiceWorkerRegisterJob::RegisterAndContinue(
244 ServiceWorkerStatusCode status) {
246 if (status != SERVICE_WORKER_OK) {
247 // Abort this registration job.
252 set_registration(new ServiceWorkerRegistration(
253 pattern_, script_url_, context_->storage()->NewRegistrationId(),
255 AssociateProviderHostsToRegistration(registration());
259 // This function corresponds to the spec's [[Update]] algorithm.
260 void ServiceWorkerRegisterJob::UpdateAndContinue() {
262 context_->storage()->NotifyInstallingRegistration(registration());
264 // TODO(falken): "If serviceWorkerRegistration.installingWorker is not null.."
265 // then terminate the installing worker. It doesn't make sense to implement
266 // yet since we always activate the worker if install completed, so there can
267 // be no installing worker at this point.
269 // "Let serviceWorker be a newly-created ServiceWorker object..." and start
271 set_new_version(new ServiceWorkerVersion(
272 registration(), context_->storage()->NewVersionId(), context_));
274 bool pause_after_download = job_type_ == UPDATE_JOB;
275 if (pause_after_download)
276 new_version()->embedded_worker()->AddListener(this);
277 new_version()->StartWorkerWithCandidateProcesses(
278 pending_process_ids_,
279 pause_after_download,
280 base::Bind(&ServiceWorkerRegisterJob::OnStartWorkerFinished,
281 weak_factory_.GetWeakPtr()));
284 void ServiceWorkerRegisterJob::OnStartWorkerFinished(
285 ServiceWorkerStatusCode status) {
286 // "If serviceWorker fails to start up..." then reject the promise with an
288 if (status != SERVICE_WORKER_OK) {
293 // "Resolve promise with serviceWorker."
294 DCHECK(!registration()->installing_version());
295 ResolvePromise(status, registration(), new_version());
296 InstallAndContinue();
299 // This function corresponds to the spec's _Install algorithm.
300 void ServiceWorkerRegisterJob::InstallAndContinue() {
303 // "3. Set registration.installingWorker to worker."
304 registration()->SetInstallingVersion(new_version());
306 // "4. Run the [[UpdateState]] algorithm passing registration.installingWorker
307 // and "installing" as the arguments."
308 new_version()->SetStatus(ServiceWorkerVersion::INSTALLING);
310 // TODO(nhiroki,michaeln): "5. Fire a simple event named updatefound..."
312 // "6. Fire an event named install..."
313 new_version()->DispatchInstallEvent(
315 base::Bind(&ServiceWorkerRegisterJob::OnInstallFinished,
316 weak_factory_.GetWeakPtr()));
319 void ServiceWorkerRegisterJob::OnInstallFinished(
320 ServiceWorkerStatusCode status) {
321 // TODO(kinuko,falken): For some error cases (e.g. ServiceWorker is
322 // unexpectedly terminated) we may want to retry sending the event again.
323 if (status != SERVICE_WORKER_OK) {
324 // "8. If installFailed is true, then:..."
330 registration()->set_last_update_check(base::Time::Now());
331 context_->storage()->StoreRegistration(
334 base::Bind(&ServiceWorkerRegisterJob::OnStoreRegistrationComplete,
335 weak_factory_.GetWeakPtr()));
338 void ServiceWorkerRegisterJob::OnStoreRegistrationComplete(
339 ServiceWorkerStatusCode status) {
340 if (status != SERVICE_WORKER_OK) {
345 // "9. If registration.waitingWorker is not null, then:..."
346 if (registration()->waiting_version()) {
347 // "1. Run the [[UpdateState]] algorithm passing registration.waitingWorker
348 // and "redundant" as the arguments."
349 registration()->waiting_version()->SetStatus(
350 ServiceWorkerVersion::REDUNDANT);
353 // "10. Set registration.waitingWorker to registration.installingWorker."
354 // "11. Set registration.installingWorker to null."
355 registration()->SetWaitingVersion(new_version());
357 // "12. Run the [[UpdateState]] algorithm passing registration.waitingWorker
358 // and "installed" as the arguments."
359 new_version()->SetStatus(ServiceWorkerVersion::INSTALLED);
361 // TODO(michaeln): "13. If activateImmediate is true, then..."
363 // "14. Wait until no document is using registration as their
364 // Service Worker registration."
365 registration()->ActivateWaitingVersionWhenReady();
367 Complete(SERVICE_WORKER_OK);
370 void ServiceWorkerRegisterJob::Complete(ServiceWorkerStatusCode status) {
371 CompleteInternal(status);
372 context_->job_coordinator()->FinishJob(pattern_, this);
375 void ServiceWorkerRegisterJob::CompleteInternal(
376 ServiceWorkerStatusCode status) {
378 if (status != SERVICE_WORKER_OK) {
379 if (registration()) {
381 registration()->UnsetVersion(new_version());
382 new_version()->Doom();
384 if (!registration()->waiting_version() &&
385 !registration()->active_version()) {
386 registration()->NotifyRegistrationFailed();
387 context_->storage()->DeleteRegistration(
388 registration()->id(),
389 registration()->script_url().GetOrigin(),
390 base::Bind(&ServiceWorkerUtils::NoOpStatusCallback));
393 if (!is_promise_resolved_)
394 ResolvePromise(status, NULL, NULL);
396 DCHECK(callbacks_.empty());
397 if (registration()) {
398 context_->storage()->NotifyDoneInstallingRegistration(
399 registration(), new_version(), status);
402 new_version()->embedded_worker()->RemoveListener(this);
405 void ServiceWorkerRegisterJob::ResolvePromise(
406 ServiceWorkerStatusCode status,
407 ServiceWorkerRegistration* registration,
408 ServiceWorkerVersion* version) {
409 DCHECK(!is_promise_resolved_);
410 is_promise_resolved_ = true;
411 promise_resolved_status_ = status;
412 promise_resolved_registration_ = registration;
413 promise_resolved_version_ = version;
414 for (std::vector<RegistrationCallback>::iterator it = callbacks_.begin();
415 it != callbacks_.end();
417 it->Run(status, registration, version);
422 void ServiceWorkerRegisterJob::OnPausedAfterDownload() {
423 // This happens prior to OnStartWorkerFinished time.
424 scoped_refptr<ServiceWorkerVersion> most_recent_version =
425 registration()->waiting_version() ?
426 registration()->waiting_version() :
427 registration()->active_version();
428 DCHECK(most_recent_version);
429 int64 most_recent_script_id =
430 most_recent_version->script_cache_map()->Lookup(script_url_);
431 int64 new_script_id =
432 new_version()->script_cache_map()->Lookup(script_url_);
434 // TODO(michaeln): It would be better to compare as the new resource
435 // is being downloaded and to avoid writing it to disk until we know
437 context_->storage()->CompareScriptResources(
438 most_recent_script_id, new_script_id,
439 base::Bind(&ServiceWorkerRegisterJob::OnCompareScriptResourcesComplete,
440 weak_factory_.GetWeakPtr(),
441 most_recent_version));
444 bool ServiceWorkerRegisterJob::OnMessageReceived(const IPC::Message& message) {
448 void ServiceWorkerRegisterJob::OnCompareScriptResourcesComplete(
449 ServiceWorkerVersion* most_recent_version,
450 ServiceWorkerStatusCode status,
453 // Only bump the last check time when we've bypassed the browser cache.
454 base::TimeDelta time_since_last_check =
455 base::Time::Now() - registration()->last_update_check();
456 if (time_since_last_check > base::TimeDelta::FromHours(24)) {
457 registration()->set_last_update_check(base::Time::Now());
458 context_->storage()->UpdateLastUpdateCheckTime(registration());
461 ResolvePromise(SERVICE_WORKER_OK, registration(), most_recent_version);
462 Complete(SERVICE_WORKER_ERROR_EXISTS);
466 // Proceed with really starting the worker.
467 new_version()->embedded_worker()->ResumeAfterDownload();
468 new_version()->embedded_worker()->RemoveListener(this);
471 void ServiceWorkerRegisterJob::AssociateProviderHostsToRegistration(
472 ServiceWorkerRegistration* registration) {
473 DCHECK(registration);
474 for (scoped_ptr<ServiceWorkerContextCore::ProviderHostIterator> it =
475 context_->GetProviderHostIterator();
476 !it->IsAtEnd(); it->Advance()) {
477 ServiceWorkerProviderHost* host = it->GetProviderHost();
478 if (ServiceWorkerUtils::ScopeMatches(registration->pattern(),
479 host->document_url())) {
480 if (host->CanAssociateRegistration(registration))
481 host->AssociateRegistration(registration);
486 } // namespace content