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_
, promise_resolved_registration_
));
75 void ServiceWorkerRegisterJob::Start() {
77 ServiceWorkerStorage::FindRegistrationCallback next_step
;
78 if (job_type_
== REGISTRATION_JOB
) {
79 next_step
= base::Bind(
80 &ServiceWorkerRegisterJob::ContinueWithRegistration
,
81 weak_factory_
.GetWeakPtr());
83 next_step
= base::Bind(
84 &ServiceWorkerRegisterJob::ContinueWithUpdate
,
85 weak_factory_
.GetWeakPtr());
88 scoped_refptr
<ServiceWorkerRegistration
> registration
=
89 context_
->storage()->GetUninstallingRegistration(pattern_
);
90 if (registration
.get())
91 RunSoon(base::Bind(next_step
, SERVICE_WORKER_OK
, registration
));
93 context_
->storage()->FindRegistrationForPattern(pattern_
, next_step
);
96 void ServiceWorkerRegisterJob::Abort() {
98 CompleteInternal(SERVICE_WORKER_ERROR_ABORT
);
99 // Don't have to call FinishJob() because the caller takes care of removing
100 // the jobs from the queue.
103 bool ServiceWorkerRegisterJob::Equals(ServiceWorkerRegisterJobBase
* job
) {
104 if (job
->GetType() != GetType())
106 ServiceWorkerRegisterJob
* register_job
=
107 static_cast<ServiceWorkerRegisterJob
*>(job
);
108 return register_job
->pattern_
== pattern_
&&
109 register_job
->script_url_
== script_url_
;
112 RegistrationJobType
ServiceWorkerRegisterJob::GetType() {
116 ServiceWorkerRegisterJob::Internal::Internal() {}
118 ServiceWorkerRegisterJob::Internal::~Internal() {}
120 void ServiceWorkerRegisterJob::set_registration(
121 const scoped_refptr
<ServiceWorkerRegistration
>& registration
) {
122 DCHECK(phase_
== START
|| phase_
== REGISTER
) << phase_
;
123 DCHECK(!internal_
.registration
.get());
124 internal_
.registration
= registration
;
127 ServiceWorkerRegistration
* ServiceWorkerRegisterJob::registration() {
128 DCHECK(phase_
>= REGISTER
|| job_type_
== UPDATE_JOB
) << phase_
;
129 return internal_
.registration
.get();
132 void ServiceWorkerRegisterJob::set_new_version(
133 ServiceWorkerVersion
* version
) {
134 DCHECK(phase_
== UPDATE
) << phase_
;
135 DCHECK(!internal_
.new_version
.get());
136 internal_
.new_version
= version
;
139 ServiceWorkerVersion
* ServiceWorkerRegisterJob::new_version() {
140 DCHECK(phase_
>= UPDATE
) << phase_
;
141 return internal_
.new_version
.get();
144 void ServiceWorkerRegisterJob::set_uninstalling_registration(
145 const scoped_refptr
<ServiceWorkerRegistration
>& registration
) {
146 DCHECK_EQ(phase_
, WAIT_FOR_UNINSTALL
);
147 internal_
.uninstalling_registration
= registration
;
150 ServiceWorkerRegistration
*
151 ServiceWorkerRegisterJob::uninstalling_registration() {
152 DCHECK_EQ(phase_
, WAIT_FOR_UNINSTALL
);
153 return internal_
.uninstalling_registration
.get();
156 void ServiceWorkerRegisterJob::SetPhase(Phase phase
) {
162 DCHECK(phase_
== INITIAL
) << phase_
;
164 case WAIT_FOR_UNINSTALL
:
165 DCHECK(phase_
== START
) << phase_
;
168 DCHECK(phase_
== START
|| phase_
== WAIT_FOR_UNINSTALL
) << phase_
;
171 DCHECK(phase_
== START
|| phase_
== REGISTER
) << phase_
;
174 DCHECK(phase_
== UPDATE
) << phase_
;
177 DCHECK(phase_
== INSTALL
) << phase_
;
180 DCHECK(phase_
!= INITIAL
&& phase_
!= COMPLETE
) << phase_
;
188 // This function corresponds to the steps in [[Register]] following
189 // "Let registration be the result of running the [[GetRegistration]] algorithm.
190 // Throughout this file, comments in quotes are excerpts from the spec.
191 void ServiceWorkerRegisterJob::ContinueWithRegistration(
192 ServiceWorkerStatusCode status
,
193 const scoped_refptr
<ServiceWorkerRegistration
>& existing_registration
) {
194 DCHECK_EQ(REGISTRATION_JOB
, job_type_
);
195 if (status
!= SERVICE_WORKER_ERROR_NOT_FOUND
&& status
!= SERVICE_WORKER_OK
) {
200 if (!existing_registration
.get() || existing_registration
->is_uninstalled()) {
201 RegisterAndContinue(SERVICE_WORKER_OK
);
205 DCHECK(existing_registration
->GetNewestVersion());
206 // "If scriptURL is equal to registration.[[ScriptURL]], then:"
207 if (existing_registration
->GetNewestVersion()->script_url() == script_url_
) {
208 // "Set registration.[[Uninstalling]] to false."
209 existing_registration
->AbortPendingClear(base::Bind(
210 &ServiceWorkerRegisterJob::ContinueWithRegistrationForSameScriptUrl
,
211 weak_factory_
.GetWeakPtr(),
212 existing_registration
));
216 if (existing_registration
->is_uninstalling()) {
217 // "Wait until the Record {[[key]], [[value]]} entry of its
218 // [[ScopeToRegistrationMap]] where registation.scope matches entry.[[key]]
220 WaitForUninstall(existing_registration
);
224 // "Set registration.[[Uninstalling]] to false."
225 DCHECK(!existing_registration
->is_uninstalling());
227 // "Return the result of running the [[Update]] algorithm, or its equivalent,
228 // passing registration as the argument."
229 set_registration(existing_registration
);
233 void ServiceWorkerRegisterJob::ContinueWithUpdate(
234 ServiceWorkerStatusCode status
,
235 const scoped_refptr
<ServiceWorkerRegistration
>& existing_registration
) {
236 DCHECK_EQ(UPDATE_JOB
, job_type_
);
237 if (status
!= SERVICE_WORKER_OK
) {
242 if (existing_registration
.get() != registration()) {
243 Complete(SERVICE_WORKER_ERROR_NOT_FOUND
);
247 // A previous job may have unregistered or installed a new version to this
249 if (registration()->is_uninstalling() ||
250 registration()->GetNewestVersion()->script_url() != script_url_
) {
251 Complete(SERVICE_WORKER_ERROR_NOT_FOUND
);
255 // TODO(michaeln): If the last update check was less than 24 hours
256 // ago, depending on the freshness of the cached worker script we
257 // may be able to complete the update job right here.
262 // Creates a new ServiceWorkerRegistration.
263 void ServiceWorkerRegisterJob::RegisterAndContinue(
264 ServiceWorkerStatusCode status
) {
266 if (status
!= SERVICE_WORKER_OK
) {
267 // Abort this registration job.
272 set_registration(new ServiceWorkerRegistration(
273 pattern_
, context_
->storage()->NewRegistrationId(), context_
));
274 AssociateProviderHostsToRegistration(registration());
278 void ServiceWorkerRegisterJob::WaitForUninstall(
279 const scoped_refptr
<ServiceWorkerRegistration
>& existing_registration
) {
280 SetPhase(WAIT_FOR_UNINSTALL
);
281 set_uninstalling_registration(existing_registration
);
282 uninstalling_registration()->AddListener(this);
285 void ServiceWorkerRegisterJob::ContinueWithRegistrationForSameScriptUrl(
286 const scoped_refptr
<ServiceWorkerRegistration
>& existing_registration
,
287 ServiceWorkerStatusCode status
) {
288 if (status
!= SERVICE_WORKER_OK
) {
292 set_registration(existing_registration
);
294 // "If newestWorker is not null, and scriptURL is equal to
295 // newestWorker.scriptURL, then:
296 // Return a promise resolved with registration."
297 // We resolve only if there's an active version. If there's not,
298 // then there is either no version or only a waiting version from
299 // the last browser session; it makes sense to proceed with registration in
301 DCHECK(!existing_registration
->installing_version());
302 if (existing_registration
->active_version()) {
303 ResolvePromise(status
, existing_registration
.get());
304 Complete(SERVICE_WORKER_OK
);
308 // "Return the result of running the [[Update]] algorithm, or its equivalent,
309 // passing registration as the argument."
313 // This function corresponds to the spec's [[Update]] algorithm.
314 void ServiceWorkerRegisterJob::UpdateAndContinue() {
316 context_
->storage()->NotifyInstallingRegistration(registration());
318 // "Let worker be a new ServiceWorker object..." and start
320 set_new_version(new ServiceWorkerVersion(registration(),
322 context_
->storage()->NewVersionId(),
325 bool pause_after_download
= job_type_
== UPDATE_JOB
;
326 if (pause_after_download
)
327 new_version()->embedded_worker()->AddListener(this);
328 new_version()->StartWorker(
329 pause_after_download
,
330 base::Bind(&ServiceWorkerRegisterJob::OnStartWorkerFinished
,
331 weak_factory_
.GetWeakPtr()));
334 void ServiceWorkerRegisterJob::OnStartWorkerFinished(
335 ServiceWorkerStatusCode status
) {
336 if (status
== SERVICE_WORKER_OK
) {
337 InstallAndContinue();
341 // "If serviceWorker fails to start up..." then reject the promise with an
342 // error and abort. When there is a main script network error, the status will
343 // be updated to a more specific one.
344 const net::URLRequestStatus
& main_script_status
=
345 new_version()->script_cache_map()->main_script_status();
346 if (main_script_status
.status() != net::URLRequestStatus::SUCCESS
) {
347 switch (main_script_status
.error()) {
348 case net::ERR_INSECURE_RESPONSE
:
349 case net::ERR_UNSAFE_REDIRECT
:
350 status
= SERVICE_WORKER_ERROR_SECURITY
;
352 case net::ERR_ABORTED
:
353 status
= SERVICE_WORKER_ERROR_ABORT
;
356 status
= SERVICE_WORKER_ERROR_NETWORK
;
362 // This function corresponds to the spec's [[Install]] algorithm.
363 void ServiceWorkerRegisterJob::InstallAndContinue() {
366 // "Set registration.installingWorker to worker."
367 DCHECK(!registration()->installing_version());
368 registration()->SetInstallingVersion(new_version());
370 // "Run the Update State algorithm passing registration's installing worker
371 // and installing as the arguments."
372 new_version()->SetStatus(ServiceWorkerVersion::INSTALLING
);
374 // "Resolve registrationPromise with registration."
375 ResolvePromise(SERVICE_WORKER_OK
, registration());
377 // "Fire a simple event named updatefound..."
378 registration()->NotifyUpdateFound();
380 // "Fire an event named install..."
381 new_version()->DispatchInstallEvent(
383 base::Bind(&ServiceWorkerRegisterJob::OnInstallFinished
,
384 weak_factory_
.GetWeakPtr()));
387 void ServiceWorkerRegisterJob::OnInstallFinished(
388 ServiceWorkerStatusCode status
) {
389 // TODO(kinuko,falken): For some error cases (e.g. ServiceWorker is
390 // unexpectedly terminated) we may want to retry sending the event again.
391 if (status
!= SERVICE_WORKER_OK
) {
392 // "8. If installFailed is true, then:..."
398 registration()->set_last_update_check(base::Time::Now());
399 context_
->storage()->StoreRegistration(
402 base::Bind(&ServiceWorkerRegisterJob::OnStoreRegistrationComplete
,
403 weak_factory_
.GetWeakPtr()));
406 void ServiceWorkerRegisterJob::OnStoreRegistrationComplete(
407 ServiceWorkerStatusCode status
) {
408 if (status
!= SERVICE_WORKER_OK
) {
413 // "9. If registration.waitingWorker is not null, then:..."
414 if (registration()->waiting_version()) {
415 // "1. Run the [[UpdateState]] algorithm passing registration.waitingWorker
416 // and "redundant" as the arguments."
417 registration()->waiting_version()->SetStatus(
418 ServiceWorkerVersion::REDUNDANT
);
421 // "10. Set registration.waitingWorker to registration.installingWorker."
422 // "11. Set registration.installingWorker to null."
423 registration()->SetWaitingVersion(new_version());
425 // "12. Run the [[UpdateState]] algorithm passing registration.waitingWorker
426 // and "installed" as the arguments."
427 new_version()->SetStatus(ServiceWorkerVersion::INSTALLED
);
429 // TODO(michaeln): "13. If activateImmediate is true, then..."
431 // "14. Wait until no document is using registration as their
432 // Service Worker registration."
433 registration()->ActivateWaitingVersionWhenReady();
435 Complete(SERVICE_WORKER_OK
);
438 void ServiceWorkerRegisterJob::Complete(ServiceWorkerStatusCode status
) {
439 CompleteInternal(status
);
440 context_
->job_coordinator()->FinishJob(pattern_
, this);
443 void ServiceWorkerRegisterJob::CompleteInternal(
444 ServiceWorkerStatusCode status
) {
446 if (status
!= SERVICE_WORKER_OK
) {
447 if (registration()) {
449 registration()->UnsetVersion(new_version());
450 new_version()->Doom();
452 if (!registration()->waiting_version() &&
453 !registration()->active_version()) {
454 registration()->NotifyRegistrationFailed();
455 context_
->storage()->DeleteRegistration(
456 registration()->id(),
457 registration()->pattern().GetOrigin(),
458 base::Bind(&ServiceWorkerUtils::NoOpStatusCallback
));
461 if (!is_promise_resolved_
)
462 ResolvePromise(status
, NULL
);
464 DCHECK(callbacks_
.empty());
465 if (registration()) {
466 context_
->storage()->NotifyDoneInstallingRegistration(
467 registration(), new_version(), status
);
470 new_version()->embedded_worker()->RemoveListener(this);
473 void ServiceWorkerRegisterJob::ResolvePromise(
474 ServiceWorkerStatusCode status
,
475 ServiceWorkerRegistration
* registration
) {
476 DCHECK(!is_promise_resolved_
);
477 is_promise_resolved_
= true;
478 promise_resolved_status_
= status
;
479 promise_resolved_registration_
= registration
;
480 for (std::vector
<RegistrationCallback
>::iterator it
= callbacks_
.begin();
481 it
!= callbacks_
.end();
483 it
->Run(status
, registration
);
488 void ServiceWorkerRegisterJob::OnPausedAfterDownload() {
489 // This happens prior to OnStartWorkerFinished time.
490 scoped_refptr
<ServiceWorkerVersion
> most_recent_version
=
491 registration()->waiting_version() ?
492 registration()->waiting_version() :
493 registration()->active_version();
494 DCHECK(most_recent_version
.get());
495 int64 most_recent_script_id
=
496 most_recent_version
->script_cache_map()->LookupResourceId(script_url_
);
497 int64 new_script_id
=
498 new_version()->script_cache_map()->LookupResourceId(script_url_
);
500 // TODO(michaeln): It would be better to compare as the new resource
501 // is being downloaded and to avoid writing it to disk until we know
503 context_
->storage()->CompareScriptResources(
504 most_recent_script_id
,
506 base::Bind(&ServiceWorkerRegisterJob::OnCompareScriptResourcesComplete
,
507 weak_factory_
.GetWeakPtr()));
510 bool ServiceWorkerRegisterJob::OnMessageReceived(const IPC::Message
& message
) {
514 void ServiceWorkerRegisterJob::OnRegistrationFinishedUninstalling(
515 ServiceWorkerRegistration
* existing_registration
) {
516 DCHECK_EQ(phase_
, WAIT_FOR_UNINSTALL
);
517 DCHECK_EQ(existing_registration
, uninstalling_registration());
518 existing_registration
->RemoveListener(this);
519 set_uninstalling_registration(NULL
);
520 RegisterAndContinue(SERVICE_WORKER_OK
);
523 void ServiceWorkerRegisterJob::OnCompareScriptResourcesComplete(
524 ServiceWorkerStatusCode status
,
527 // Only bump the last check time when we've bypassed the browser cache.
528 base::TimeDelta time_since_last_check
=
529 base::Time::Now() - registration()->last_update_check();
530 if (time_since_last_check
> base::TimeDelta::FromHours(24)) {
531 registration()->set_last_update_check(base::Time::Now());
532 context_
->storage()->UpdateLastUpdateCheckTime(registration());
535 ResolvePromise(SERVICE_WORKER_OK
, registration());
536 Complete(SERVICE_WORKER_ERROR_EXISTS
);
540 // Proceed with really starting the worker.
541 new_version()->embedded_worker()->ResumeAfterDownload();
542 new_version()->embedded_worker()->RemoveListener(this);
545 void ServiceWorkerRegisterJob::AssociateProviderHostsToRegistration(
546 ServiceWorkerRegistration
* registration
) {
547 DCHECK(registration
);
548 for (scoped_ptr
<ServiceWorkerContextCore::ProviderHostIterator
> it
=
549 context_
->GetProviderHostIterator();
550 !it
->IsAtEnd(); it
->Advance()) {
551 ServiceWorkerProviderHost
* host
= it
->GetProviderHost();
552 if (ServiceWorkerUtils::ScopeMatches(registration
->pattern(),
553 host
->document_url())) {
554 if (host
->CanAssociateRegistration(registration
))
555 host
->AssociateRegistration(registration
);
560 } // namespace content