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 should_uninstall_on_failure_(false),
42 promise_resolved_status_(SERVICE_WORKER_OK
),
46 ServiceWorkerRegisterJob::ServiceWorkerRegisterJob(
47 base::WeakPtr
<ServiceWorkerContextCore
> context
,
48 ServiceWorkerRegistration
* registration
)
50 job_type_(UPDATE_JOB
),
51 pattern_(registration
->pattern()),
52 script_url_(registration
->GetNewestVersion()->script_url()),
54 doom_installing_worker_(false),
55 is_promise_resolved_(false),
56 should_uninstall_on_failure_(false),
57 promise_resolved_status_(SERVICE_WORKER_OK
),
59 internal_
.registration
= registration
;
62 ServiceWorkerRegisterJob::~ServiceWorkerRegisterJob() {
64 phase_
== INITIAL
|| phase_
== COMPLETE
|| phase_
== ABORT
)
65 << "Jobs should only be interrupted during shutdown.";
68 void ServiceWorkerRegisterJob::AddCallback(
69 const RegistrationCallback
& callback
,
70 ServiceWorkerProviderHost
* provider_host
) {
71 if (!is_promise_resolved_
) {
72 callbacks_
.push_back(callback
);
74 provider_host
->AddScopedProcessReferenceToPattern(pattern_
);
77 RunSoon(base::Bind(callback
, promise_resolved_status_
,
78 promise_resolved_status_message_
,
79 promise_resolved_registration_
));
82 void ServiceWorkerRegisterJob::Start() {
84 ServiceWorkerStorage::FindRegistrationCallback next_step
;
85 if (job_type_
== REGISTRATION_JOB
) {
86 next_step
= base::Bind(
87 &ServiceWorkerRegisterJob::ContinueWithRegistration
,
88 weak_factory_
.GetWeakPtr());
90 next_step
= base::Bind(
91 &ServiceWorkerRegisterJob::ContinueWithUpdate
,
92 weak_factory_
.GetWeakPtr());
95 scoped_refptr
<ServiceWorkerRegistration
> registration
=
96 context_
->storage()->GetUninstallingRegistration(pattern_
);
97 if (registration
.get())
98 RunSoon(base::Bind(next_step
, SERVICE_WORKER_OK
, registration
));
100 context_
->storage()->FindRegistrationForPattern(pattern_
, next_step
);
103 void ServiceWorkerRegisterJob::Abort() {
105 CompleteInternal(SERVICE_WORKER_ERROR_ABORT
, std::string());
106 // Don't have to call FinishJob() because the caller takes care of removing
107 // the jobs from the queue.
110 bool ServiceWorkerRegisterJob::Equals(ServiceWorkerRegisterJobBase
* job
) const {
111 if (job
->GetType() != GetType())
113 ServiceWorkerRegisterJob
* register_job
=
114 static_cast<ServiceWorkerRegisterJob
*>(job
);
115 return register_job
->pattern_
== pattern_
&&
116 register_job
->script_url_
== script_url_
;
119 RegistrationJobType
ServiceWorkerRegisterJob::GetType() const {
123 void ServiceWorkerRegisterJob::DoomInstallingWorker() {
124 doom_installing_worker_
= true;
125 if (phase_
== INSTALL
)
126 Complete(SERVICE_WORKER_ERROR_INSTALL_WORKER_FAILED
, std::string());
129 ServiceWorkerRegisterJob::Internal::Internal() {}
131 ServiceWorkerRegisterJob::Internal::~Internal() {}
133 void ServiceWorkerRegisterJob::set_registration(
134 const scoped_refptr
<ServiceWorkerRegistration
>& registration
) {
135 DCHECK(phase_
== START
|| phase_
== REGISTER
) << phase_
;
136 DCHECK(!internal_
.registration
.get());
137 internal_
.registration
= registration
;
140 ServiceWorkerRegistration
* ServiceWorkerRegisterJob::registration() {
141 DCHECK(phase_
>= REGISTER
|| job_type_
== UPDATE_JOB
) << phase_
;
142 return internal_
.registration
.get();
145 void ServiceWorkerRegisterJob::set_new_version(
146 ServiceWorkerVersion
* version
) {
147 DCHECK(phase_
== UPDATE
) << phase_
;
148 DCHECK(!internal_
.new_version
.get());
149 internal_
.new_version
= version
;
152 ServiceWorkerVersion
* ServiceWorkerRegisterJob::new_version() {
153 DCHECK(phase_
>= UPDATE
) << phase_
;
154 return internal_
.new_version
.get();
157 void ServiceWorkerRegisterJob::SetPhase(Phase phase
) {
163 DCHECK(phase_
== INITIAL
) << phase_
;
166 DCHECK(phase_
== START
) << phase_
;
169 DCHECK(phase_
== START
|| phase_
== REGISTER
) << phase_
;
172 DCHECK(phase_
== UPDATE
) << phase_
;
175 DCHECK(phase_
== INSTALL
) << phase_
;
178 DCHECK(phase_
!= INITIAL
&& phase_
!= COMPLETE
) << phase_
;
186 // This function corresponds to the steps in [[Register]] following
187 // "Let registration be the result of running the [[GetRegistration]] algorithm.
188 // Throughout this file, comments in quotes are excerpts from the spec.
189 void ServiceWorkerRegisterJob::ContinueWithRegistration(
190 ServiceWorkerStatusCode status
,
191 const scoped_refptr
<ServiceWorkerRegistration
>& existing_registration
) {
192 DCHECK_EQ(REGISTRATION_JOB
, job_type_
);
193 if (status
!= SERVICE_WORKER_ERROR_NOT_FOUND
&& status
!= SERVICE_WORKER_OK
) {
198 if (!existing_registration
.get() || existing_registration
->is_uninstalled()) {
199 RegisterAndContinue();
203 DCHECK(existing_registration
->GetNewestVersion());
204 // "If scriptURL is equal to registration.[[ScriptURL]], then:"
205 if (existing_registration
->GetNewestVersion()->script_url() == script_url_
) {
206 // "Set registration.[[Uninstalling]] to false."
207 existing_registration
->AbortPendingClear(base::Bind(
208 &ServiceWorkerRegisterJob::ContinueWithRegistrationForSameScriptUrl
,
209 weak_factory_
.GetWeakPtr(),
210 existing_registration
));
214 if (existing_registration
->is_uninstalling()) {
215 existing_registration
->AbortPendingClear(base::Bind(
216 &ServiceWorkerRegisterJob::ContinueWithUninstallingRegistration
,
217 weak_factory_
.GetWeakPtr(),
218 existing_registration
));
222 // "Return the result of running the [[Update]] algorithm, or its equivalent,
223 // passing registration as the argument."
224 set_registration(existing_registration
);
228 void ServiceWorkerRegisterJob::ContinueWithUpdate(
229 ServiceWorkerStatusCode status
,
230 const scoped_refptr
<ServiceWorkerRegistration
>& existing_registration
) {
231 DCHECK_EQ(UPDATE_JOB
, job_type_
);
232 if (status
!= SERVICE_WORKER_OK
) {
237 if (existing_registration
.get() != registration()) {
238 Complete(SERVICE_WORKER_ERROR_NOT_FOUND
);
242 // A previous job may have unregistered or installed a new version to this
244 if (registration()->is_uninstalling() ||
245 registration()->GetNewestVersion()->script_url() != script_url_
) {
246 Complete(SERVICE_WORKER_ERROR_NOT_FOUND
);
250 // TODO(michaeln): If the last update check was less than 24 hours
251 // ago, depending on the freshness of the cached worker script we
252 // may be able to complete the update job right here.
257 // Creates a new ServiceWorkerRegistration.
258 void ServiceWorkerRegisterJob::RegisterAndContinue() {
261 set_registration(new ServiceWorkerRegistration(
262 pattern_
, context_
->storage()->NewRegistrationId(), context_
));
263 AssociateProviderHostsToRegistration(registration());
267 void ServiceWorkerRegisterJob::ContinueWithUninstallingRegistration(
268 const scoped_refptr
<ServiceWorkerRegistration
>& existing_registration
,
269 ServiceWorkerStatusCode status
) {
270 if (status
!= SERVICE_WORKER_OK
) {
274 should_uninstall_on_failure_
= true;
275 set_registration(existing_registration
);
279 void ServiceWorkerRegisterJob::ContinueWithRegistrationForSameScriptUrl(
280 const scoped_refptr
<ServiceWorkerRegistration
>& existing_registration
,
281 ServiceWorkerStatusCode status
) {
282 if (status
!= SERVICE_WORKER_OK
) {
286 set_registration(existing_registration
);
288 // "If newestWorker is not null, and scriptURL is equal to
289 // newestWorker.scriptURL, then:
290 // Return a promise resolved with registration."
291 // We resolve only if there's an active version. If there's not,
292 // then there is either no version or only a waiting version from
293 // the last browser session; it makes sense to proceed with registration in
295 DCHECK(!existing_registration
->installing_version());
296 if (existing_registration
->active_version()) {
297 ResolvePromise(status
, std::string(), existing_registration
.get());
298 Complete(SERVICE_WORKER_OK
);
302 // "Return the result of running the [[Update]] algorithm, or its equivalent,
303 // passing registration as the argument."
307 // This function corresponds to the spec's [[Update]] algorithm.
308 void ServiceWorkerRegisterJob::UpdateAndContinue() {
310 context_
->storage()->NotifyInstallingRegistration(registration());
312 // "Let worker be a new ServiceWorker object..." and start
314 set_new_version(new ServiceWorkerVersion(registration(),
316 context_
->storage()->NewVersionId(),
319 bool pause_after_download
= job_type_
== UPDATE_JOB
;
320 if (pause_after_download
)
321 new_version()->embedded_worker()->AddListener(this);
322 new_version()->StartWorker(
323 pause_after_download
,
324 base::Bind(&ServiceWorkerRegisterJob::OnStartWorkerFinished
,
325 weak_factory_
.GetWeakPtr()));
328 void ServiceWorkerRegisterJob::OnStartWorkerFinished(
329 ServiceWorkerStatusCode status
) {
330 if (status
== SERVICE_WORKER_OK
) {
331 InstallAndContinue();
335 // "If serviceWorker fails to start up..." then reject the promise with an
336 // error and abort. When there is a main script network error, the status will
337 // be updated to a more specific one.
338 const net::URLRequestStatus
& main_script_status
=
339 new_version()->script_cache_map()->main_script_status();
341 if (main_script_status
.status() != net::URLRequestStatus::SUCCESS
) {
342 switch (main_script_status
.error()) {
343 case net::ERR_INSECURE_RESPONSE
:
344 case net::ERR_UNSAFE_REDIRECT
:
345 status
= SERVICE_WORKER_ERROR_SECURITY
;
347 case net::ERR_ABORTED
:
348 status
= SERVICE_WORKER_ERROR_ABORT
;
351 status
= SERVICE_WORKER_ERROR_NETWORK
;
353 message
= new_version()->script_cache_map()->main_script_status_message();
355 message
= kFetchScriptError
;
357 Complete(status
, message
);
360 // This function corresponds to the spec's [[Install]] algorithm.
361 void ServiceWorkerRegisterJob::InstallAndContinue() {
364 // "Set registration.installingWorker to worker."
365 DCHECK(!registration()->installing_version());
366 registration()->SetInstallingVersion(new_version());
368 // "Run the Update State algorithm passing registration's installing worker
369 // and installing as the arguments."
370 new_version()->SetStatus(ServiceWorkerVersion::INSTALLING
);
372 // "Resolve registrationPromise with registration."
373 ResolvePromise(SERVICE_WORKER_OK
, std::string(), registration());
375 // "Fire a simple event named updatefound..."
376 registration()->NotifyUpdateFound();
378 // "Fire an event named install..."
379 new_version()->DispatchInstallEvent(
381 base::Bind(&ServiceWorkerRegisterJob::OnInstallFinished
,
382 weak_factory_
.GetWeakPtr()));
384 // A subsequent registration job may terminate our installing worker. It can
385 // only do so after we've started the worker and dispatched the install
386 // event, as those are atomic substeps in the [[Install]] algorithm.
387 if (doom_installing_worker_
)
388 Complete(SERVICE_WORKER_ERROR_INSTALL_WORKER_FAILED
);
391 void ServiceWorkerRegisterJob::OnInstallFinished(
392 ServiceWorkerStatusCode status
) {
393 // TODO(kinuko,falken): For some error cases (e.g. ServiceWorker is
394 // unexpectedly terminated) we may want to retry sending the event again.
395 if (status
!= SERVICE_WORKER_OK
) {
396 // "8. If installFailed is true, then:..."
402 registration()->set_last_update_check(base::Time::Now());
403 context_
->storage()->StoreRegistration(
406 base::Bind(&ServiceWorkerRegisterJob::OnStoreRegistrationComplete
,
407 weak_factory_
.GetWeakPtr()));
410 void ServiceWorkerRegisterJob::OnStoreRegistrationComplete(
411 ServiceWorkerStatusCode status
) {
412 if (status
!= SERVICE_WORKER_OK
) {
417 // "9. If registration.waitingWorker is not null, then:..."
418 if (registration()->waiting_version()) {
419 // "1. Run the [[UpdateState]] algorithm passing registration.waitingWorker
420 // and "redundant" as the arguments."
421 registration()->waiting_version()->SetStatus(
422 ServiceWorkerVersion::REDUNDANT
);
425 // "10. Set registration.waitingWorker to registration.installingWorker."
426 // "11. Set registration.installingWorker to null."
427 registration()->SetWaitingVersion(new_version());
429 // "12. Run the [[UpdateState]] algorithm passing registration.waitingWorker
430 // and "installed" as the arguments."
431 new_version()->SetStatus(ServiceWorkerVersion::INSTALLED
);
433 // "If registration's waiting worker's skip waiting flag is set:" then
434 // activate the worker immediately otherwise "wait until no service worker
435 // client is using registration as their service worker registration."
436 registration()->ActivateWaitingVersionWhenReady();
438 Complete(SERVICE_WORKER_OK
);
441 void ServiceWorkerRegisterJob::Complete(ServiceWorkerStatusCode status
) {
442 Complete(status
, std::string());
445 void ServiceWorkerRegisterJob::Complete(ServiceWorkerStatusCode status
,
446 const std::string
& status_message
) {
447 CompleteInternal(status
, status_message
);
448 context_
->job_coordinator()->FinishJob(pattern_
, this);
451 void ServiceWorkerRegisterJob::CompleteInternal(
452 ServiceWorkerStatusCode status
,
453 const std::string
& status_message
) {
455 if (status
!= SERVICE_WORKER_OK
) {
456 if (registration()) {
457 if (should_uninstall_on_failure_
)
458 registration()->ClearWhenReady();
460 registration()->UnsetVersion(new_version());
461 new_version()->Doom();
463 if (!registration()->waiting_version() &&
464 !registration()->active_version()) {
465 registration()->NotifyRegistrationFailed();
466 context_
->storage()->DeleteRegistration(
467 registration()->id(),
468 registration()->pattern().GetOrigin(),
469 base::Bind(&ServiceWorkerUtils::NoOpStatusCallback
));
472 if (!is_promise_resolved_
)
473 ResolvePromise(status
, status_message
, NULL
);
475 DCHECK(callbacks_
.empty());
476 if (registration()) {
477 context_
->storage()->NotifyDoneInstallingRegistration(
478 registration(), new_version(), status
);
481 new_version()->embedded_worker()->RemoveListener(this);
484 void ServiceWorkerRegisterJob::ResolvePromise(
485 ServiceWorkerStatusCode status
,
486 const std::string
& status_message
,
487 ServiceWorkerRegistration
* registration
) {
488 DCHECK(!is_promise_resolved_
);
490 is_promise_resolved_
= true;
491 promise_resolved_status_
= status
;
492 promise_resolved_status_message_
= status_message
,
493 promise_resolved_registration_
= registration
;
494 for (std::vector
<RegistrationCallback
>::iterator it
= callbacks_
.begin();
495 it
!= callbacks_
.end();
497 it
->Run(status
, status_message
, registration
);
502 void ServiceWorkerRegisterJob::OnPausedAfterDownload() {
503 // This happens prior to OnStartWorkerFinished time.
504 scoped_refptr
<ServiceWorkerVersion
> most_recent_version
=
505 registration()->waiting_version() ?
506 registration()->waiting_version() :
507 registration()->active_version();
508 DCHECK(most_recent_version
.get());
509 int64 most_recent_script_id
=
510 most_recent_version
->script_cache_map()->LookupResourceId(script_url_
);
511 int64 new_script_id
=
512 new_version()->script_cache_map()->LookupResourceId(script_url_
);
514 // TODO(michaeln): It would be better to compare as the new resource
515 // is being downloaded and to avoid writing it to disk until we know
517 context_
->storage()->CompareScriptResources(
518 most_recent_script_id
,
520 base::Bind(&ServiceWorkerRegisterJob::OnCompareScriptResourcesComplete
,
521 weak_factory_
.GetWeakPtr()));
524 bool ServiceWorkerRegisterJob::OnMessageReceived(const IPC::Message
& message
) {
528 void ServiceWorkerRegisterJob::OnCompareScriptResourcesComplete(
529 ServiceWorkerStatusCode status
,
532 // Only bump the last check time when we've bypassed the browser cache.
533 base::TimeDelta time_since_last_check
=
534 base::Time::Now() - registration()->last_update_check();
535 if (time_since_last_check
> base::TimeDelta::FromHours(24)) {
536 registration()->set_last_update_check(base::Time::Now());
537 context_
->storage()->UpdateLastUpdateCheckTime(registration());
540 ResolvePromise(SERVICE_WORKER_OK
, std::string(), registration());
541 Complete(SERVICE_WORKER_ERROR_EXISTS
);
545 // Proceed with really starting the worker.
546 new_version()->embedded_worker()->ResumeAfterDownload();
547 new_version()->embedded_worker()->RemoveListener(this);
550 void ServiceWorkerRegisterJob::AssociateProviderHostsToRegistration(
551 ServiceWorkerRegistration
* registration
) {
552 DCHECK(registration
);
553 for (scoped_ptr
<ServiceWorkerContextCore::ProviderHostIterator
> it
=
554 context_
->GetProviderHostIterator();
555 !it
->IsAtEnd(); it
->Advance()) {
556 ServiceWorkerProviderHost
* host
= it
->GetProviderHost();
557 if (ServiceWorkerUtils::ScopeMatches(registration
->pattern(),
558 host
->document_url())) {
559 if (host
->CanAssociateRegistration(registration
))
560 host
->AssociateRegistration(registration
);
565 } // namespace content