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"
15 #include "net/base/net_errors.h"
21 void RunSoon(const base::Closure& closure) {
22 base::MessageLoop::current()->PostTask(FROM_HERE, closure);
27 typedef ServiceWorkerRegisterJobBase::RegistrationJobType RegistrationJobType;
29 ServiceWorkerRegisterJob::ServiceWorkerRegisterJob(
30 base::WeakPtr<ServiceWorkerContextCore> context,
32 const GURL& script_url)
34 job_type_(REGISTRATION_JOB),
36 script_url_(script_url),
38 is_promise_resolved_(false),
39 promise_resolved_status_(SERVICE_WORKER_OK),
40 weak_factory_(this) {}
42 ServiceWorkerRegisterJob::ServiceWorkerRegisterJob(
43 base::WeakPtr<ServiceWorkerContextCore> context,
44 ServiceWorkerRegistration* registration)
46 job_type_(UPDATE_JOB),
47 pattern_(registration->pattern()),
48 script_url_(registration->GetNewestVersion()->script_url()),
50 is_promise_resolved_(false),
51 promise_resolved_status_(SERVICE_WORKER_OK),
53 internal_.registration = registration;
56 ServiceWorkerRegisterJob::~ServiceWorkerRegisterJob() {
58 phase_ == INITIAL || phase_ == COMPLETE || phase_ == ABORT)
59 << "Jobs should only be interrupted during shutdown.";
62 void ServiceWorkerRegisterJob::AddCallback(
63 const RegistrationCallback& callback,
64 ServiceWorkerProviderHost* provider_host) {
65 if (!is_promise_resolved_) {
66 callbacks_.push_back(callback);
68 provider_host->AddScopedProcessReferenceToPattern(pattern_);
72 callback, promise_resolved_status_,
73 promise_resolved_registration_, promise_resolved_version_));
76 void ServiceWorkerRegisterJob::Start() {
78 ServiceWorkerStorage::FindRegistrationCallback next_step;
79 if (job_type_ == REGISTRATION_JOB) {
80 next_step = base::Bind(
81 &ServiceWorkerRegisterJob::ContinueWithRegistration,
82 weak_factory_.GetWeakPtr());
84 next_step = base::Bind(
85 &ServiceWorkerRegisterJob::ContinueWithUpdate,
86 weak_factory_.GetWeakPtr());
89 scoped_refptr<ServiceWorkerRegistration> registration =
90 context_->storage()->GetUninstallingRegistration(pattern_);
91 if (registration.get())
92 RunSoon(base::Bind(next_step, SERVICE_WORKER_OK, registration));
94 context_->storage()->FindRegistrationForPattern(pattern_, next_step);
97 void ServiceWorkerRegisterJob::Abort() {
99 CompleteInternal(SERVICE_WORKER_ERROR_ABORT);
100 // Don't have to call FinishJob() because the caller takes care of removing
101 // the jobs from the queue.
104 bool ServiceWorkerRegisterJob::Equals(ServiceWorkerRegisterJobBase* job) {
105 if (job->GetType() != GetType())
107 ServiceWorkerRegisterJob* register_job =
108 static_cast<ServiceWorkerRegisterJob*>(job);
109 return register_job->pattern_ == pattern_ &&
110 register_job->script_url_ == script_url_;
113 RegistrationJobType ServiceWorkerRegisterJob::GetType() {
117 ServiceWorkerRegisterJob::Internal::Internal() {}
119 ServiceWorkerRegisterJob::Internal::~Internal() {}
121 void ServiceWorkerRegisterJob::set_registration(
122 const scoped_refptr<ServiceWorkerRegistration>& registration) {
123 DCHECK(phase_ == START || phase_ == REGISTER) << phase_;
124 DCHECK(!internal_.registration.get());
125 internal_.registration = registration;
128 ServiceWorkerRegistration* ServiceWorkerRegisterJob::registration() {
129 DCHECK(phase_ >= REGISTER || job_type_ == UPDATE_JOB) << phase_;
130 return internal_.registration.get();
133 void ServiceWorkerRegisterJob::set_new_version(
134 ServiceWorkerVersion* version) {
135 DCHECK(phase_ == UPDATE) << phase_;
136 DCHECK(!internal_.new_version.get());
137 internal_.new_version = version;
140 ServiceWorkerVersion* ServiceWorkerRegisterJob::new_version() {
141 DCHECK(phase_ >= UPDATE) << phase_;
142 return internal_.new_version.get();
145 void ServiceWorkerRegisterJob::set_uninstalling_registration(
146 const scoped_refptr<ServiceWorkerRegistration>& registration) {
147 DCHECK_EQ(phase_, WAIT_FOR_UNINSTALL);
148 internal_.uninstalling_registration = registration;
151 ServiceWorkerRegistration*
152 ServiceWorkerRegisterJob::uninstalling_registration() {
153 DCHECK_EQ(phase_, WAIT_FOR_UNINSTALL);
154 return internal_.uninstalling_registration.get();
157 void ServiceWorkerRegisterJob::SetPhase(Phase phase) {
163 DCHECK(phase_ == INITIAL) << phase_;
165 case WAIT_FOR_UNINSTALL:
166 DCHECK(phase_ == START) << phase_;
169 DCHECK(phase_ == START || phase_ == WAIT_FOR_UNINSTALL) << phase_;
172 DCHECK(phase_ == START || phase_ == REGISTER) << phase_;
175 DCHECK(phase_ == UPDATE) << phase_;
178 DCHECK(phase_ == INSTALL) << phase_;
181 DCHECK(phase_ != INITIAL && phase_ != COMPLETE) << phase_;
189 // This function corresponds to the steps in [[Register]] following
190 // "Let registration be the result of running the [[GetRegistration]] algorithm.
191 // Throughout this file, comments in quotes are excerpts from the spec.
192 void ServiceWorkerRegisterJob::ContinueWithRegistration(
193 ServiceWorkerStatusCode status,
194 const scoped_refptr<ServiceWorkerRegistration>& existing_registration) {
195 DCHECK_EQ(REGISTRATION_JOB, job_type_);
196 if (status != SERVICE_WORKER_ERROR_NOT_FOUND && status != SERVICE_WORKER_OK) {
201 if (!existing_registration.get() || existing_registration->is_uninstalled()) {
202 RegisterAndContinue(SERVICE_WORKER_OK);
206 DCHECK(existing_registration->GetNewestVersion());
207 // "If scriptURL is equal to registration.[[ScriptURL]], then:"
208 if (existing_registration->GetNewestVersion()->script_url() == script_url_) {
209 // "Set registration.[[Uninstalling]] to false."
210 existing_registration->AbortPendingClear(base::Bind(
211 &ServiceWorkerRegisterJob::ContinueWithRegistrationForSameScriptUrl,
212 weak_factory_.GetWeakPtr(),
213 existing_registration));
217 if (existing_registration->is_uninstalling()) {
218 // "Wait until the Record {[[key]], [[value]]} entry of its
219 // [[ScopeToRegistrationMap]] where registation.scope matches entry.[[key]]
221 WaitForUninstall(existing_registration);
225 // "Set registration.[[Uninstalling]] to false."
226 DCHECK(!existing_registration->is_uninstalling());
228 // "Return the result of running the [[Update]] algorithm, or its equivalent,
229 // passing registration as the argument."
230 set_registration(existing_registration);
234 void ServiceWorkerRegisterJob::ContinueWithUpdate(
235 ServiceWorkerStatusCode status,
236 const scoped_refptr<ServiceWorkerRegistration>& existing_registration) {
237 DCHECK_EQ(UPDATE_JOB, job_type_);
238 if (status != SERVICE_WORKER_OK) {
243 if (existing_registration.get() != registration()) {
244 Complete(SERVICE_WORKER_ERROR_NOT_FOUND);
248 // A previous job may have unregistered or installed a new version to this
250 if (registration()->is_uninstalling() ||
251 registration()->GetNewestVersion()->script_url() != script_url_) {
252 Complete(SERVICE_WORKER_ERROR_NOT_FOUND);
256 // TODO(michaeln): If the last update check was less than 24 hours
257 // ago, depending on the freshness of the cached worker script we
258 // may be able to complete the update job right here.
263 // Creates a new ServiceWorkerRegistration.
264 void ServiceWorkerRegisterJob::RegisterAndContinue(
265 ServiceWorkerStatusCode status) {
267 if (status != SERVICE_WORKER_OK) {
268 // Abort this registration job.
273 set_registration(new ServiceWorkerRegistration(
274 pattern_, context_->storage()->NewRegistrationId(), context_));
275 AssociateProviderHostsToRegistration(registration());
279 void ServiceWorkerRegisterJob::WaitForUninstall(
280 const scoped_refptr<ServiceWorkerRegistration>& existing_registration) {
281 SetPhase(WAIT_FOR_UNINSTALL);
282 set_uninstalling_registration(existing_registration);
283 uninstalling_registration()->AddListener(this);
286 void ServiceWorkerRegisterJob::ContinueWithRegistrationForSameScriptUrl(
287 const scoped_refptr<ServiceWorkerRegistration>& existing_registration,
288 ServiceWorkerStatusCode status) {
289 if (status != SERVICE_WORKER_OK) {
293 set_registration(existing_registration);
295 // TODO(falken): Follow the spec: resolve the promise
296 // with the newest version.
298 if (!existing_registration->active_version()) {
303 ResolvePromise(status,
304 existing_registration.get(),
305 existing_registration->active_version());
306 Complete(SERVICE_WORKER_OK);
309 // This function corresponds to the spec's [[Update]] algorithm.
310 void ServiceWorkerRegisterJob::UpdateAndContinue() {
312 context_->storage()->NotifyInstallingRegistration(registration());
314 // TODO(falken): "If serviceWorkerRegistration.installingWorker is not null.."
315 // then terminate the installing worker. It doesn't make sense to implement
316 // yet since we always activate the worker if install completed, so there can
317 // be no installing worker at this point.
319 // "Let serviceWorker be a newly-created ServiceWorker object..." and start
321 set_new_version(new ServiceWorkerVersion(registration(),
323 context_->storage()->NewVersionId(),
326 bool pause_after_download = job_type_ == UPDATE_JOB;
327 if (pause_after_download)
328 new_version()->embedded_worker()->AddListener(this);
329 new_version()->StartWorker(
330 pause_after_download,
331 base::Bind(&ServiceWorkerRegisterJob::OnStartWorkerFinished,
332 weak_factory_.GetWeakPtr()));
335 void ServiceWorkerRegisterJob::OnStartWorkerFinished(
336 ServiceWorkerStatusCode status) {
337 if (status == SERVICE_WORKER_OK) {
338 InstallAndContinue();
342 // "If serviceWorker fails to start up..." then reject the promise with an
343 // error and abort. When there is a main script network error, the status will
344 // be updated to a more specific one.
345 const net::URLRequestStatus& main_script_status =
346 new_version()->script_cache_map()->main_script_status();
347 if (main_script_status.status() != net::URLRequestStatus::SUCCESS) {
348 switch (main_script_status.error()) {
349 case net::ERR_INSECURE_RESPONSE:
350 case net::ERR_UNSAFE_REDIRECT:
351 status = SERVICE_WORKER_ERROR_SECURITY;
353 case net::ERR_ABORTED:
354 status = SERVICE_WORKER_ERROR_ABORT;
356 case net::ERR_FAILED:
357 status = SERVICE_WORKER_ERROR_NETWORK;
366 // This function corresponds to the spec's [[Install]] algorithm.
367 void ServiceWorkerRegisterJob::InstallAndContinue() {
370 // "2. Set registration.installingWorker to worker."
371 registration()->SetInstallingVersion(new_version());
373 // "3. Resolve promise with registration."
374 ResolvePromise(SERVICE_WORKER_OK, registration(), new_version());
376 // "4. Run the [[UpdateState]] algorithm passing registration.installingWorker
377 // and "installing" as the arguments."
378 new_version()->SetStatus(ServiceWorkerVersion::INSTALLING);
380 // "5. Fire a simple event named updatefound..."
381 registration()->NotifyUpdateFound();
383 // "6. Fire an event named install..."
384 new_version()->DispatchInstallEvent(
386 base::Bind(&ServiceWorkerRegisterJob::OnInstallFinished,
387 weak_factory_.GetWeakPtr()));
390 void ServiceWorkerRegisterJob::OnInstallFinished(
391 ServiceWorkerStatusCode status) {
392 // TODO(kinuko,falken): For some error cases (e.g. ServiceWorker is
393 // unexpectedly terminated) we may want to retry sending the event again.
394 if (status != SERVICE_WORKER_OK) {
395 // "8. If installFailed is true, then:..."
401 registration()->set_last_update_check(base::Time::Now());
402 context_->storage()->StoreRegistration(
405 base::Bind(&ServiceWorkerRegisterJob::OnStoreRegistrationComplete,
406 weak_factory_.GetWeakPtr()));
409 void ServiceWorkerRegisterJob::OnStoreRegistrationComplete(
410 ServiceWorkerStatusCode status) {
411 if (status != SERVICE_WORKER_OK) {
416 // "9. If registration.waitingWorker is not null, then:..."
417 if (registration()->waiting_version()) {
418 // "1. Run the [[UpdateState]] algorithm passing registration.waitingWorker
419 // and "redundant" as the arguments."
420 registration()->waiting_version()->SetStatus(
421 ServiceWorkerVersion::REDUNDANT);
424 // "10. Set registration.waitingWorker to registration.installingWorker."
425 // "11. Set registration.installingWorker to null."
426 registration()->SetWaitingVersion(new_version());
428 // "12. Run the [[UpdateState]] algorithm passing registration.waitingWorker
429 // and "installed" as the arguments."
430 new_version()->SetStatus(ServiceWorkerVersion::INSTALLED);
432 // TODO(michaeln): "13. If activateImmediate is true, then..."
434 // "14. Wait until no document is using registration as their
435 // Service Worker registration."
436 registration()->ActivateWaitingVersionWhenReady();
438 Complete(SERVICE_WORKER_OK);
441 void ServiceWorkerRegisterJob::Complete(ServiceWorkerStatusCode status) {
442 CompleteInternal(status);
443 context_->job_coordinator()->FinishJob(pattern_, this);
446 void ServiceWorkerRegisterJob::CompleteInternal(
447 ServiceWorkerStatusCode status) {
449 if (status != SERVICE_WORKER_OK) {
450 if (registration()) {
452 registration()->UnsetVersion(new_version());
453 new_version()->Doom();
455 if (!registration()->waiting_version() &&
456 !registration()->active_version()) {
457 registration()->NotifyRegistrationFailed();
458 context_->storage()->DeleteRegistration(
459 registration()->id(),
460 registration()->pattern().GetOrigin(),
461 base::Bind(&ServiceWorkerUtils::NoOpStatusCallback));
464 if (!is_promise_resolved_)
465 ResolvePromise(status, NULL, NULL);
467 DCHECK(callbacks_.empty());
468 if (registration()) {
469 context_->storage()->NotifyDoneInstallingRegistration(
470 registration(), new_version(), status);
473 new_version()->embedded_worker()->RemoveListener(this);
476 void ServiceWorkerRegisterJob::ResolvePromise(
477 ServiceWorkerStatusCode status,
478 ServiceWorkerRegistration* registration,
479 ServiceWorkerVersion* version) {
480 DCHECK(!is_promise_resolved_);
481 is_promise_resolved_ = true;
482 promise_resolved_status_ = status;
483 promise_resolved_registration_ = registration;
484 promise_resolved_version_ = version;
485 for (std::vector<RegistrationCallback>::iterator it = callbacks_.begin();
486 it != callbacks_.end();
488 it->Run(status, registration, version);
493 void ServiceWorkerRegisterJob::OnPausedAfterDownload() {
494 // This happens prior to OnStartWorkerFinished time.
495 scoped_refptr<ServiceWorkerVersion> most_recent_version =
496 registration()->waiting_version() ?
497 registration()->waiting_version() :
498 registration()->active_version();
499 DCHECK(most_recent_version.get());
500 int64 most_recent_script_id =
501 most_recent_version->script_cache_map()->Lookup(script_url_);
502 int64 new_script_id =
503 new_version()->script_cache_map()->Lookup(script_url_);
505 // TODO(michaeln): It would be better to compare as the new resource
506 // is being downloaded and to avoid writing it to disk until we know
508 context_->storage()->CompareScriptResources(
509 most_recent_script_id, new_script_id,
510 base::Bind(&ServiceWorkerRegisterJob::OnCompareScriptResourcesComplete,
511 weak_factory_.GetWeakPtr(),
512 most_recent_version));
515 bool ServiceWorkerRegisterJob::OnMessageReceived(const IPC::Message& message) {
519 void ServiceWorkerRegisterJob::OnRegistrationFinishedUninstalling(
520 ServiceWorkerRegistration* existing_registration) {
521 DCHECK_EQ(phase_, WAIT_FOR_UNINSTALL);
522 DCHECK_EQ(existing_registration, uninstalling_registration());
523 existing_registration->RemoveListener(this);
524 set_uninstalling_registration(NULL);
525 RegisterAndContinue(SERVICE_WORKER_OK);
528 void ServiceWorkerRegisterJob::OnCompareScriptResourcesComplete(
529 ServiceWorkerVersion* most_recent_version,
530 ServiceWorkerStatusCode status,
533 // Only bump the last check time when we've bypassed the browser cache.
534 base::TimeDelta time_since_last_check =
535 base::Time::Now() - registration()->last_update_check();
536 if (time_since_last_check > base::TimeDelta::FromHours(24)) {
537 registration()->set_last_update_check(base::Time::Now());
538 context_->storage()->UpdateLastUpdateCheckTime(registration());
541 ResolvePromise(SERVICE_WORKER_OK, registration(), most_recent_version);
542 Complete(SERVICE_WORKER_ERROR_EXISTS);
546 // Proceed with really starting the worker.
547 new_version()->embedded_worker()->ResumeAfterDownload();
548 new_version()->embedded_worker()->RemoveListener(this);
551 void ServiceWorkerRegisterJob::AssociateProviderHostsToRegistration(
552 ServiceWorkerRegistration* registration) {
553 DCHECK(registration);
554 for (scoped_ptr<ServiceWorkerContextCore::ProviderHostIterator> it =
555 context_->GetProviderHostIterator();
556 !it->IsAtEnd(); it->Advance()) {
557 ServiceWorkerProviderHost* host = it->GetProviderHost();
558 if (ServiceWorkerUtils::ScopeMatches(registration->pattern(),
559 host->document_url())) {
560 if (host->CanAssociateRegistration(registration))
561 host->AssociateRegistration(registration);
566 } // namespace content