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 "content/common/service_worker/service_worker_types.h"
16 #include "net/base/net_errors.h"
22 void RunSoon(const base::Closure
& closure
) {
23 base::MessageLoop::current()->PostTask(FROM_HERE
, closure
);
28 typedef ServiceWorkerRegisterJobBase::RegistrationJobType RegistrationJobType
;
30 ServiceWorkerRegisterJob::ServiceWorkerRegisterJob(
31 base::WeakPtr
<ServiceWorkerContextCore
> context
,
33 const GURL
& script_url
)
35 job_type_(REGISTRATION_JOB
),
37 script_url_(script_url
),
39 doom_installing_worker_(false),
40 is_promise_resolved_(false),
41 promise_resolved_status_(SERVICE_WORKER_OK
),
45 ServiceWorkerRegisterJob::ServiceWorkerRegisterJob(
46 base::WeakPtr
<ServiceWorkerContextCore
> context
,
47 ServiceWorkerRegistration
* registration
)
49 job_type_(UPDATE_JOB
),
50 pattern_(registration
->pattern()),
51 script_url_(registration
->GetNewestVersion()->script_url()),
53 doom_installing_worker_(false),
54 is_promise_resolved_(false),
55 promise_resolved_status_(SERVICE_WORKER_OK
),
57 internal_
.registration
= registration
;
60 ServiceWorkerRegisterJob::~ServiceWorkerRegisterJob() {
62 phase_
== INITIAL
|| phase_
== COMPLETE
|| phase_
== ABORT
)
63 << "Jobs should only be interrupted during shutdown.";
66 void ServiceWorkerRegisterJob::AddCallback(
67 const RegistrationCallback
& callback
,
68 ServiceWorkerProviderHost
* provider_host
) {
69 if (!is_promise_resolved_
) {
70 callbacks_
.push_back(callback
);
72 provider_host
->AddScopedProcessReferenceToPattern(pattern_
);
75 RunSoon(base::Bind(callback
, promise_resolved_status_
,
76 promise_resolved_status_message_
,
77 promise_resolved_registration_
));
80 void ServiceWorkerRegisterJob::Start() {
82 ServiceWorkerStorage::FindRegistrationCallback next_step
;
83 if (job_type_
== REGISTRATION_JOB
) {
84 next_step
= base::Bind(
85 &ServiceWorkerRegisterJob::ContinueWithRegistration
,
86 weak_factory_
.GetWeakPtr());
88 next_step
= base::Bind(
89 &ServiceWorkerRegisterJob::ContinueWithUpdate
,
90 weak_factory_
.GetWeakPtr());
93 scoped_refptr
<ServiceWorkerRegistration
> registration
=
94 context_
->storage()->GetUninstallingRegistration(pattern_
);
95 if (registration
.get())
96 RunSoon(base::Bind(next_step
, SERVICE_WORKER_OK
, registration
));
98 context_
->storage()->FindRegistrationForPattern(pattern_
, next_step
);
101 void ServiceWorkerRegisterJob::Abort() {
103 CompleteInternal(SERVICE_WORKER_ERROR_ABORT
, std::string());
104 // Don't have to call FinishJob() because the caller takes care of removing
105 // the jobs from the queue.
108 bool ServiceWorkerRegisterJob::Equals(ServiceWorkerRegisterJobBase
* job
) const {
109 if (job
->GetType() != GetType())
111 ServiceWorkerRegisterJob
* register_job
=
112 static_cast<ServiceWorkerRegisterJob
*>(job
);
113 return register_job
->pattern_
== pattern_
&&
114 register_job
->script_url_
== script_url_
;
117 RegistrationJobType
ServiceWorkerRegisterJob::GetType() const {
121 void ServiceWorkerRegisterJob::DoomInstallingWorker() {
122 doom_installing_worker_
= true;
123 if (phase_
== INSTALL
)
124 Complete(SERVICE_WORKER_ERROR_INSTALL_WORKER_FAILED
, std::string());
127 ServiceWorkerRegisterJob::Internal::Internal() {}
129 ServiceWorkerRegisterJob::Internal::~Internal() {}
131 void ServiceWorkerRegisterJob::set_registration(
132 const scoped_refptr
<ServiceWorkerRegistration
>& registration
) {
133 DCHECK(phase_
== START
|| phase_
== REGISTER
) << phase_
;
134 DCHECK(!internal_
.registration
.get());
135 internal_
.registration
= registration
;
138 ServiceWorkerRegistration
* ServiceWorkerRegisterJob::registration() {
139 DCHECK(phase_
>= REGISTER
|| job_type_
== UPDATE_JOB
) << phase_
;
140 return internal_
.registration
.get();
143 void ServiceWorkerRegisterJob::set_new_version(
144 ServiceWorkerVersion
* version
) {
145 DCHECK(phase_
== UPDATE
) << phase_
;
146 DCHECK(!internal_
.new_version
.get());
147 internal_
.new_version
= version
;
150 ServiceWorkerVersion
* ServiceWorkerRegisterJob::new_version() {
151 DCHECK(phase_
>= UPDATE
) << phase_
;
152 return internal_
.new_version
.get();
155 void ServiceWorkerRegisterJob::set_uninstalling_registration(
156 const scoped_refptr
<ServiceWorkerRegistration
>& registration
) {
157 DCHECK_EQ(phase_
, WAIT_FOR_UNINSTALL
);
158 internal_
.uninstalling_registration
= registration
;
161 ServiceWorkerRegistration
*
162 ServiceWorkerRegisterJob::uninstalling_registration() {
163 DCHECK_EQ(phase_
, WAIT_FOR_UNINSTALL
);
164 return internal_
.uninstalling_registration
.get();
167 void ServiceWorkerRegisterJob::SetPhase(Phase phase
) {
173 DCHECK(phase_
== INITIAL
) << phase_
;
175 case WAIT_FOR_UNINSTALL
:
176 DCHECK(phase_
== START
) << phase_
;
179 DCHECK(phase_
== START
|| phase_
== WAIT_FOR_UNINSTALL
) << phase_
;
182 DCHECK(phase_
== START
|| phase_
== REGISTER
) << phase_
;
185 DCHECK(phase_
== UPDATE
) << phase_
;
188 DCHECK(phase_
== INSTALL
) << phase_
;
191 DCHECK(phase_
!= INITIAL
&& phase_
!= COMPLETE
) << phase_
;
199 // This function corresponds to the steps in [[Register]] following
200 // "Let registration be the result of running the [[GetRegistration]] algorithm.
201 // Throughout this file, comments in quotes are excerpts from the spec.
202 void ServiceWorkerRegisterJob::ContinueWithRegistration(
203 ServiceWorkerStatusCode status
,
204 const scoped_refptr
<ServiceWorkerRegistration
>& existing_registration
) {
205 DCHECK_EQ(REGISTRATION_JOB
, job_type_
);
206 if (status
!= SERVICE_WORKER_ERROR_NOT_FOUND
&& status
!= SERVICE_WORKER_OK
) {
211 if (!existing_registration
.get() || existing_registration
->is_uninstalled()) {
212 RegisterAndContinue(SERVICE_WORKER_OK
);
216 DCHECK(existing_registration
->GetNewestVersion());
217 // "If scriptURL is equal to registration.[[ScriptURL]], then:"
218 if (existing_registration
->GetNewestVersion()->script_url() == script_url_
) {
219 // "Set registration.[[Uninstalling]] to false."
220 existing_registration
->AbortPendingClear(base::Bind(
221 &ServiceWorkerRegisterJob::ContinueWithRegistrationForSameScriptUrl
,
222 weak_factory_
.GetWeakPtr(),
223 existing_registration
));
227 if (existing_registration
->is_uninstalling()) {
228 // "Wait until the Record {[[key]], [[value]]} entry of its
229 // [[ScopeToRegistrationMap]] where registation.scope matches entry.[[key]]
231 WaitForUninstall(existing_registration
);
235 // "Set registration.[[Uninstalling]] to false."
236 DCHECK(!existing_registration
->is_uninstalling());
238 // "Return the result of running the [[Update]] algorithm, or its equivalent,
239 // passing registration as the argument."
240 set_registration(existing_registration
);
244 void ServiceWorkerRegisterJob::ContinueWithUpdate(
245 ServiceWorkerStatusCode status
,
246 const scoped_refptr
<ServiceWorkerRegistration
>& existing_registration
) {
247 DCHECK_EQ(UPDATE_JOB
, job_type_
);
248 if (status
!= SERVICE_WORKER_OK
) {
253 if (existing_registration
.get() != registration()) {
254 Complete(SERVICE_WORKER_ERROR_NOT_FOUND
);
258 // A previous job may have unregistered or installed a new version to this
260 if (registration()->is_uninstalling() ||
261 registration()->GetNewestVersion()->script_url() != script_url_
) {
262 Complete(SERVICE_WORKER_ERROR_NOT_FOUND
);
266 // TODO(michaeln): If the last update check was less than 24 hours
267 // ago, depending on the freshness of the cached worker script we
268 // may be able to complete the update job right here.
273 // Creates a new ServiceWorkerRegistration.
274 void ServiceWorkerRegisterJob::RegisterAndContinue(
275 ServiceWorkerStatusCode status
) {
277 if (status
!= SERVICE_WORKER_OK
) {
282 set_registration(new ServiceWorkerRegistration(
283 pattern_
, context_
->storage()->NewRegistrationId(), context_
));
284 AssociateProviderHostsToRegistration(registration());
288 void ServiceWorkerRegisterJob::WaitForUninstall(
289 const scoped_refptr
<ServiceWorkerRegistration
>& existing_registration
) {
290 SetPhase(WAIT_FOR_UNINSTALL
);
291 set_uninstalling_registration(existing_registration
);
292 uninstalling_registration()->AddListener(this);
295 void ServiceWorkerRegisterJob::ContinueWithRegistrationForSameScriptUrl(
296 const scoped_refptr
<ServiceWorkerRegistration
>& existing_registration
,
297 ServiceWorkerStatusCode status
) {
298 if (status
!= SERVICE_WORKER_OK
) {
302 set_registration(existing_registration
);
304 // "If newestWorker is not null, and scriptURL is equal to
305 // newestWorker.scriptURL, then:
306 // Return a promise resolved with registration."
307 // We resolve only if there's an active version. If there's not,
308 // then there is either no version or only a waiting version from
309 // the last browser session; it makes sense to proceed with registration in
311 DCHECK(!existing_registration
->installing_version());
312 if (existing_registration
->active_version()) {
313 ResolvePromise(status
, std::string(), existing_registration
.get());
314 Complete(SERVICE_WORKER_OK
);
318 // "Return the result of running the [[Update]] algorithm, or its equivalent,
319 // passing registration as the argument."
323 // This function corresponds to the spec's [[Update]] algorithm.
324 void ServiceWorkerRegisterJob::UpdateAndContinue() {
326 context_
->storage()->NotifyInstallingRegistration(registration());
328 // "Let worker be a new ServiceWorker object..." and start
330 set_new_version(new ServiceWorkerVersion(registration(),
332 context_
->storage()->NewVersionId(),
335 bool pause_after_download
= job_type_
== UPDATE_JOB
;
336 if (pause_after_download
)
337 new_version()->embedded_worker()->AddListener(this);
338 new_version()->StartWorker(
339 pause_after_download
,
340 base::Bind(&ServiceWorkerRegisterJob::OnStartWorkerFinished
,
341 weak_factory_
.GetWeakPtr()));
344 void ServiceWorkerRegisterJob::OnStartWorkerFinished(
345 ServiceWorkerStatusCode status
) {
346 if (status
== SERVICE_WORKER_OK
) {
347 InstallAndContinue();
351 // "If serviceWorker fails to start up..." then reject the promise with an
352 // error and abort. When there is a main script network error, the status will
353 // be updated to a more specific one.
354 const net::URLRequestStatus
& main_script_status
=
355 new_version()->script_cache_map()->main_script_status();
357 if (main_script_status
.status() != net::URLRequestStatus::SUCCESS
) {
358 switch (main_script_status
.error()) {
359 case net::ERR_INSECURE_RESPONSE
:
360 case net::ERR_UNSAFE_REDIRECT
:
361 status
= SERVICE_WORKER_ERROR_SECURITY
;
363 case net::ERR_ABORTED
:
364 status
= SERVICE_WORKER_ERROR_ABORT
;
367 status
= SERVICE_WORKER_ERROR_NETWORK
;
369 message
= new_version()->script_cache_map()->main_script_status_message();
371 message
= kFetchScriptError
;
373 Complete(status
, message
);
376 // This function corresponds to the spec's [[Install]] algorithm.
377 void ServiceWorkerRegisterJob::InstallAndContinue() {
380 // "Set registration.installingWorker to worker."
381 DCHECK(!registration()->installing_version());
382 registration()->SetInstallingVersion(new_version());
384 // "Run the Update State algorithm passing registration's installing worker
385 // and installing as the arguments."
386 new_version()->SetStatus(ServiceWorkerVersion::INSTALLING
);
388 // "Resolve registrationPromise with registration."
389 ResolvePromise(SERVICE_WORKER_OK
, std::string(), registration());
391 // "Fire a simple event named updatefound..."
392 registration()->NotifyUpdateFound();
394 // "Fire an event named install..."
395 new_version()->DispatchInstallEvent(
397 base::Bind(&ServiceWorkerRegisterJob::OnInstallFinished
,
398 weak_factory_
.GetWeakPtr()));
400 // A subsequent registration job may terminate our installing worker. It can
401 // only do so after we've started the worker and dispatched the install
402 // event, as those are atomic substeps in the [[Install]] algorithm.
403 if (doom_installing_worker_
)
404 Complete(SERVICE_WORKER_ERROR_INSTALL_WORKER_FAILED
);
407 void ServiceWorkerRegisterJob::OnInstallFinished(
408 ServiceWorkerStatusCode status
) {
409 // TODO(kinuko,falken): For some error cases (e.g. ServiceWorker is
410 // unexpectedly terminated) we may want to retry sending the event again.
411 if (status
!= SERVICE_WORKER_OK
) {
412 // "8. If installFailed is true, then:..."
418 registration()->set_last_update_check(base::Time::Now());
419 context_
->storage()->StoreRegistration(
422 base::Bind(&ServiceWorkerRegisterJob::OnStoreRegistrationComplete
,
423 weak_factory_
.GetWeakPtr()));
426 void ServiceWorkerRegisterJob::OnStoreRegistrationComplete(
427 ServiceWorkerStatusCode status
) {
428 if (status
!= SERVICE_WORKER_OK
) {
433 // "9. If registration.waitingWorker is not null, then:..."
434 if (registration()->waiting_version()) {
435 // "1. Run the [[UpdateState]] algorithm passing registration.waitingWorker
436 // and "redundant" as the arguments."
437 registration()->waiting_version()->SetStatus(
438 ServiceWorkerVersion::REDUNDANT
);
441 // "10. Set registration.waitingWorker to registration.installingWorker."
442 // "11. Set registration.installingWorker to null."
443 registration()->SetWaitingVersion(new_version());
445 // "12. Run the [[UpdateState]] algorithm passing registration.waitingWorker
446 // and "installed" as the arguments."
447 new_version()->SetStatus(ServiceWorkerVersion::INSTALLED
);
449 // "If registration's waiting worker's skip waiting flag is set:" then
450 // activate the worker immediately otherwise "wait until no service worker
451 // client is using registration as their service worker registration."
452 registration()->ActivateWaitingVersionWhenReady();
454 Complete(SERVICE_WORKER_OK
);
457 void ServiceWorkerRegisterJob::Complete(ServiceWorkerStatusCode status
) {
458 Complete(status
, std::string());
461 void ServiceWorkerRegisterJob::Complete(ServiceWorkerStatusCode status
,
462 const std::string
& status_message
) {
463 CompleteInternal(status
, status_message
);
464 context_
->job_coordinator()->FinishJob(pattern_
, this);
467 void ServiceWorkerRegisterJob::CompleteInternal(
468 ServiceWorkerStatusCode status
,
469 const std::string
& status_message
) {
471 if (status
!= SERVICE_WORKER_OK
) {
472 if (registration()) {
474 registration()->UnsetVersion(new_version());
475 new_version()->Doom();
477 if (!registration()->waiting_version() &&
478 !registration()->active_version()) {
479 registration()->NotifyRegistrationFailed();
480 context_
->storage()->DeleteRegistration(
481 registration()->id(),
482 registration()->pattern().GetOrigin(),
483 base::Bind(&ServiceWorkerUtils::NoOpStatusCallback
));
486 if (!is_promise_resolved_
)
487 ResolvePromise(status
, status_message
, NULL
);
489 DCHECK(callbacks_
.empty());
490 if (registration()) {
491 context_
->storage()->NotifyDoneInstallingRegistration(
492 registration(), new_version(), status
);
495 new_version()->embedded_worker()->RemoveListener(this);
498 void ServiceWorkerRegisterJob::ResolvePromise(
499 ServiceWorkerStatusCode status
,
500 const std::string
& status_message
,
501 ServiceWorkerRegistration
* registration
) {
502 DCHECK(!is_promise_resolved_
);
504 is_promise_resolved_
= true;
505 promise_resolved_status_
= status
;
506 promise_resolved_status_message_
= status_message
,
507 promise_resolved_registration_
= registration
;
508 for (std::vector
<RegistrationCallback
>::iterator it
= callbacks_
.begin();
509 it
!= callbacks_
.end();
511 it
->Run(status
, status_message
, registration
);
516 void ServiceWorkerRegisterJob::OnPausedAfterDownload() {
517 // This happens prior to OnStartWorkerFinished time.
518 scoped_refptr
<ServiceWorkerVersion
> most_recent_version
=
519 registration()->waiting_version() ?
520 registration()->waiting_version() :
521 registration()->active_version();
522 DCHECK(most_recent_version
.get());
523 int64 most_recent_script_id
=
524 most_recent_version
->script_cache_map()->LookupResourceId(script_url_
);
525 int64 new_script_id
=
526 new_version()->script_cache_map()->LookupResourceId(script_url_
);
528 // TODO(michaeln): It would be better to compare as the new resource
529 // is being downloaded and to avoid writing it to disk until we know
531 context_
->storage()->CompareScriptResources(
532 most_recent_script_id
,
534 base::Bind(&ServiceWorkerRegisterJob::OnCompareScriptResourcesComplete
,
535 weak_factory_
.GetWeakPtr()));
538 bool ServiceWorkerRegisterJob::OnMessageReceived(const IPC::Message
& message
) {
542 void ServiceWorkerRegisterJob::OnRegistrationFinishedUninstalling(
543 ServiceWorkerRegistration
* existing_registration
) {
544 DCHECK_EQ(phase_
, WAIT_FOR_UNINSTALL
);
545 DCHECK_EQ(existing_registration
, uninstalling_registration());
546 existing_registration
->RemoveListener(this);
547 set_uninstalling_registration(NULL
);
548 RegisterAndContinue(SERVICE_WORKER_OK
);
551 void ServiceWorkerRegisterJob::OnCompareScriptResourcesComplete(
552 ServiceWorkerStatusCode status
,
555 // Only bump the last check time when we've bypassed the browser cache.
556 base::TimeDelta time_since_last_check
=
557 base::Time::Now() - registration()->last_update_check();
558 if (time_since_last_check
> base::TimeDelta::FromHours(24)) {
559 registration()->set_last_update_check(base::Time::Now());
560 context_
->storage()->UpdateLastUpdateCheckTime(registration());
563 ResolvePromise(SERVICE_WORKER_OK
, std::string(), registration());
564 Complete(SERVICE_WORKER_ERROR_EXISTS
);
568 // Proceed with really starting the worker.
569 new_version()->embedded_worker()->ResumeAfterDownload();
570 new_version()->embedded_worker()->RemoveListener(this);
573 void ServiceWorkerRegisterJob::AssociateProviderHostsToRegistration(
574 ServiceWorkerRegistration
* registration
) {
575 DCHECK(registration
);
576 for (scoped_ptr
<ServiceWorkerContextCore::ProviderHostIterator
> it
=
577 context_
->GetProviderHostIterator();
578 !it
->IsAtEnd(); it
->Advance()) {
579 ServiceWorkerProviderHost
* host
= it
->GetProviderHost();
580 if (ServiceWorkerUtils::ScopeMatches(registration
->pattern(),
581 host
->document_url())) {
582 if (host
->CanAssociateRegistration(registration
))
583 host
->AssociateRegistration(registration
);
588 } // namespace content