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 AddRegistrationToMatchingProviderHosts(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
;
358 if (status
== SERVICE_WORKER_ERROR_TIMEOUT
)
359 message
= "Timed out while trying to start the Service Worker.";
361 Complete(status
, message
);
364 // This function corresponds to the spec's [[Install]] algorithm.
365 void ServiceWorkerRegisterJob::InstallAndContinue() {
368 // "Set registration.installingWorker to worker."
369 DCHECK(!registration()->installing_version());
370 registration()->SetInstallingVersion(new_version());
372 // "Run the Update State algorithm passing registration's installing worker
373 // and installing as the arguments."
374 new_version()->SetStatus(ServiceWorkerVersion::INSTALLING
);
376 // "Resolve registrationPromise with registration."
377 ResolvePromise(SERVICE_WORKER_OK
, std::string(), registration());
379 // "Fire a simple event named updatefound..."
380 registration()->NotifyUpdateFound();
382 // "Fire an event named install..."
383 new_version()->DispatchInstallEvent(
384 base::Bind(&ServiceWorkerRegisterJob::OnInstallFinished
,
385 weak_factory_
.GetWeakPtr()));
387 // A subsequent registration job may terminate our installing worker. It can
388 // only do so after we've started the worker and dispatched the install
389 // event, as those are atomic substeps in the [[Install]] algorithm.
390 if (doom_installing_worker_
)
391 Complete(SERVICE_WORKER_ERROR_INSTALL_WORKER_FAILED
);
394 void ServiceWorkerRegisterJob::OnInstallFinished(
395 ServiceWorkerStatusCode status
) {
396 if (status
!= SERVICE_WORKER_OK
) {
397 // "8. If installFailed is true, then:..."
403 registration()->set_last_update_check(base::Time::Now());
404 context_
->storage()->StoreRegistration(
407 base::Bind(&ServiceWorkerRegisterJob::OnStoreRegistrationComplete
,
408 weak_factory_
.GetWeakPtr()));
411 void ServiceWorkerRegisterJob::OnStoreRegistrationComplete(
412 ServiceWorkerStatusCode status
) {
413 if (status
!= SERVICE_WORKER_OK
) {
418 // "9. If registration.waitingWorker is not null, then:..."
419 if (registration()->waiting_version()) {
420 // "1. Run the [[UpdateState]] algorithm passing registration.waitingWorker
421 // and "redundant" as the arguments."
422 registration()->waiting_version()->SetStatus(
423 ServiceWorkerVersion::REDUNDANT
);
426 // "10. Set registration.waitingWorker to registration.installingWorker."
427 // "11. Set registration.installingWorker to null."
428 registration()->SetWaitingVersion(new_version());
430 // "12. Run the [[UpdateState]] algorithm passing registration.waitingWorker
431 // and "installed" as the arguments."
432 new_version()->SetStatus(ServiceWorkerVersion::INSTALLED
);
434 // "If registration's waiting worker's skip waiting flag is set:" then
435 // activate the worker immediately otherwise "wait until no service worker
436 // client is using registration as their service worker registration."
437 registration()->ActivateWaitingVersionWhenReady();
439 Complete(SERVICE_WORKER_OK
);
442 void ServiceWorkerRegisterJob::Complete(ServiceWorkerStatusCode status
) {
443 Complete(status
, std::string());
446 void ServiceWorkerRegisterJob::Complete(ServiceWorkerStatusCode status
,
447 const std::string
& status_message
) {
448 CompleteInternal(status
, status_message
);
449 context_
->job_coordinator()->FinishJob(pattern_
, this);
452 void ServiceWorkerRegisterJob::CompleteInternal(
453 ServiceWorkerStatusCode status
,
454 const std::string
& status_message
) {
456 if (status
!= SERVICE_WORKER_OK
) {
457 if (registration()) {
458 if (should_uninstall_on_failure_
)
459 registration()->ClearWhenReady();
461 if (status
!= SERVICE_WORKER_ERROR_EXISTS
)
462 new_version()->ReportError(status
, status_message
);
463 registration()->UnsetVersion(new_version());
464 new_version()->Doom();
466 if (!registration()->waiting_version() &&
467 !registration()->active_version()) {
468 registration()->NotifyRegistrationFailed();
469 context_
->storage()->DeleteRegistration(
470 registration()->id(),
471 registration()->pattern().GetOrigin(),
472 base::Bind(&ServiceWorkerUtils::NoOpStatusCallback
));
475 if (!is_promise_resolved_
)
476 ResolvePromise(status
, status_message
, NULL
);
478 DCHECK(callbacks_
.empty());
479 if (registration()) {
480 context_
->storage()->NotifyDoneInstallingRegistration(
481 registration(), new_version(), status
);
484 new_version()->embedded_worker()->RemoveListener(this);
487 void ServiceWorkerRegisterJob::ResolvePromise(
488 ServiceWorkerStatusCode status
,
489 const std::string
& status_message
,
490 ServiceWorkerRegistration
* registration
) {
491 DCHECK(!is_promise_resolved_
);
493 is_promise_resolved_
= true;
494 promise_resolved_status_
= status
;
495 promise_resolved_status_message_
= status_message
,
496 promise_resolved_registration_
= registration
;
497 for (std::vector
<RegistrationCallback
>::iterator it
= callbacks_
.begin();
498 it
!= callbacks_
.end();
500 it
->Run(status
, status_message
, registration
);
505 void ServiceWorkerRegisterJob::OnPausedAfterDownload() {
506 // This happens prior to OnStartWorkerFinished time.
507 scoped_refptr
<ServiceWorkerVersion
> most_recent_version
=
508 registration()->waiting_version() ?
509 registration()->waiting_version() :
510 registration()->active_version();
511 DCHECK(most_recent_version
.get());
512 int64 most_recent_script_id
=
513 most_recent_version
->script_cache_map()->LookupResourceId(script_url_
);
514 int64 new_script_id
=
515 new_version()->script_cache_map()->LookupResourceId(script_url_
);
517 // TODO(michaeln): It would be better to compare as the new resource
518 // is being downloaded and to avoid writing it to disk until we know
520 context_
->storage()->CompareScriptResources(
521 most_recent_script_id
,
523 base::Bind(&ServiceWorkerRegisterJob::OnCompareScriptResourcesComplete
,
524 weak_factory_
.GetWeakPtr()));
527 bool ServiceWorkerRegisterJob::OnMessageReceived(const IPC::Message
& message
) {
531 void ServiceWorkerRegisterJob::OnCompareScriptResourcesComplete(
532 ServiceWorkerStatusCode status
,
535 // Only bump the last check time when we've bypassed the browser cache.
536 base::TimeDelta time_since_last_check
=
537 base::Time::Now() - registration()->last_update_check();
538 if (time_since_last_check
> base::TimeDelta::FromHours(24)) {
539 registration()->set_last_update_check(base::Time::Now());
540 context_
->storage()->UpdateLastUpdateCheckTime(registration());
543 ResolvePromise(SERVICE_WORKER_OK
, std::string(), registration());
544 Complete(SERVICE_WORKER_ERROR_EXISTS
);
548 // Proceed with really starting the worker.
549 new_version()->embedded_worker()->ResumeAfterDownload();
550 new_version()->embedded_worker()->RemoveListener(this);
553 void ServiceWorkerRegisterJob::AddRegistrationToMatchingProviderHosts(
554 ServiceWorkerRegistration
* registration
) {
555 DCHECK(registration
);
556 for (scoped_ptr
<ServiceWorkerContextCore::ProviderHostIterator
> it
=
557 context_
->GetProviderHostIterator();
558 !it
->IsAtEnd(); it
->Advance()) {
559 ServiceWorkerProviderHost
* host
= it
->GetProviderHost();
560 if (host
->IsHostToRunningServiceWorker())
562 if (!ServiceWorkerUtils::ScopeMatches(registration
->pattern(),
563 host
->document_url()))
565 host
->AddMatchingRegistration(registration
);
569 } // namespace content