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"
20 void RunSoon(const base::Closure
& closure
) {
21 base::MessageLoop::current()->PostTask(FROM_HERE
, closure
);
26 typedef ServiceWorkerRegisterJobBase::RegistrationJobType RegistrationJobType
;
28 ServiceWorkerRegisterJob::ServiceWorkerRegisterJob(
29 base::WeakPtr
<ServiceWorkerContextCore
> context
,
31 const GURL
& script_url
)
33 job_type_(REGISTRATION_JOB
),
35 script_url_(script_url
),
37 is_promise_resolved_(false),
38 promise_resolved_status_(SERVICE_WORKER_OK
),
39 weak_factory_(this) {}
41 ServiceWorkerRegisterJob::ServiceWorkerRegisterJob(
42 base::WeakPtr
<ServiceWorkerContextCore
> context
,
43 ServiceWorkerRegistration
* registration
)
45 job_type_(UPDATE_JOB
),
46 pattern_(registration
->pattern()),
47 script_url_(registration
->script_url()),
49 is_promise_resolved_(false),
50 promise_resolved_status_(SERVICE_WORKER_OK
),
52 internal_
.registration
= registration
;
55 ServiceWorkerRegisterJob::~ServiceWorkerRegisterJob() {
57 phase_
== INITIAL
|| phase_
== COMPLETE
|| phase_
== ABORT
)
58 << "Jobs should only be interrupted during shutdown.";
61 void ServiceWorkerRegisterJob::AddCallback(const RegistrationCallback
& callback
,
63 if (!is_promise_resolved_
) {
64 callbacks_
.push_back(callback
);
65 if (process_id
!= -1 && (phase_
< UPDATE
|| !new_version()))
66 pending_process_ids_
.push_back(process_id
);
70 callback
, promise_resolved_status_
,
71 promise_resolved_registration_
, promise_resolved_version_
));
74 void ServiceWorkerRegisterJob::Start() {
76 ServiceWorkerStorage::FindRegistrationCallback next_step
;
77 if (job_type_
== REGISTRATION_JOB
) {
78 next_step
= base::Bind(
79 &ServiceWorkerRegisterJob::ContinueWithRegistration
,
80 weak_factory_
.GetWeakPtr());
82 next_step
= base::Bind(
83 &ServiceWorkerRegisterJob::ContinueWithUpdate
,
84 weak_factory_
.GetWeakPtr());
87 scoped_refptr
<ServiceWorkerRegistration
> registration
=
88 context_
->storage()->GetUninstallingRegistration(pattern_
);
90 RunSoon(base::Bind(next_step
, SERVICE_WORKER_OK
, registration
));
92 context_
->storage()->FindRegistrationForPattern(pattern_
, next_step
);
95 void ServiceWorkerRegisterJob::Abort() {
97 CompleteInternal(SERVICE_WORKER_ERROR_ABORT
);
98 // Don't have to call FinishJob() because the caller takes care of removing
99 // the jobs from the queue.
102 bool ServiceWorkerRegisterJob::Equals(ServiceWorkerRegisterJobBase
* job
) {
103 if (job
->GetType() != GetType())
105 ServiceWorkerRegisterJob
* register_job
=
106 static_cast<ServiceWorkerRegisterJob
*>(job
);
107 return register_job
->pattern_
== pattern_
&&
108 register_job
->script_url_
== script_url_
;
111 RegistrationJobType
ServiceWorkerRegisterJob::GetType() {
115 ServiceWorkerRegisterJob::Internal::Internal() {}
117 ServiceWorkerRegisterJob::Internal::~Internal() {}
119 void ServiceWorkerRegisterJob::set_registration(
120 ServiceWorkerRegistration
* registration
) {
121 DCHECK(phase_
== START
|| phase_
== REGISTER
) << phase_
;
122 DCHECK(!internal_
.registration
);
123 internal_
.registration
= registration
;
126 ServiceWorkerRegistration
* ServiceWorkerRegisterJob::registration() {
127 DCHECK(phase_
>= REGISTER
|| job_type_
== UPDATE_JOB
) << phase_
;
128 return internal_
.registration
;
131 void ServiceWorkerRegisterJob::set_new_version(
132 ServiceWorkerVersion
* version
) {
133 DCHECK(phase_
== UPDATE
) << phase_
;
134 DCHECK(!internal_
.new_version
);
135 internal_
.new_version
= version
;
138 ServiceWorkerVersion
* ServiceWorkerRegisterJob::new_version() {
139 DCHECK(phase_
>= UPDATE
) << phase_
;
140 return internal_
.new_version
;
143 void ServiceWorkerRegisterJob::SetPhase(Phase phase
) {
149 DCHECK(phase_
== INITIAL
) << phase_
;
152 DCHECK(phase_
== START
) << phase_
;
155 DCHECK(phase_
== START
|| phase_
== REGISTER
) << phase_
;
158 DCHECK(phase_
== UPDATE
) << phase_
;
161 DCHECK(phase_
== INSTALL
) << phase_
;
164 DCHECK(phase_
!= INITIAL
&& phase_
!= COMPLETE
) << phase_
;
172 // This function corresponds to the steps in [[Register]] following
173 // "Let registration be the result of running the [[GetRegistration]] algorithm.
174 // Throughout this file, comments in quotes are excerpts from the spec.
175 void ServiceWorkerRegisterJob::ContinueWithRegistration(
176 ServiceWorkerStatusCode status
,
177 const scoped_refptr
<ServiceWorkerRegistration
>& existing_registration
) {
178 DCHECK_EQ(REGISTRATION_JOB
, job_type_
);
179 if (status
!= SERVICE_WORKER_ERROR_NOT_FOUND
&& status
!= SERVICE_WORKER_OK
) {
184 if (!existing_registration
) {
185 RegisterAndContinue(SERVICE_WORKER_OK
);
189 // "Set registration.[[Uninstalling]] to false."
190 existing_registration
->AbortPendingClear();
192 // "If scriptURL is equal to registration.[[ScriptURL]], then:"
193 if (existing_registration
->script_url() == script_url_
) {
194 // Spec says to resolve with registration.[[GetNewestWorker]]. We come close
195 // by resolving with the active version.
196 set_registration(existing_registration
);
198 if (!existing_registration
->active_version()) {
204 status
, existing_registration
, existing_registration
->active_version());
205 Complete(SERVICE_WORKER_OK
);
209 // "Set registration.[[ScriptURL]] to scriptURL." We accomplish this by
210 // deleting the existing registration and registering a new one.
211 // TODO(michaeln): Deactivate the live existing_registration object and
212 // eventually call storage->DeleteVersionResources() when it no longer has any
214 context_
->storage()->DeleteRegistration(
215 existing_registration
->id(),
216 existing_registration
->script_url().GetOrigin(),
217 base::Bind(&ServiceWorkerRegisterJob::RegisterAndContinue
,
218 weak_factory_
.GetWeakPtr()));
221 void ServiceWorkerRegisterJob::ContinueWithUpdate(
222 ServiceWorkerStatusCode status
,
223 const scoped_refptr
<ServiceWorkerRegistration
>& existing_registration
) {
224 DCHECK_EQ(UPDATE_JOB
, job_type_
);
225 if (status
!= SERVICE_WORKER_OK
) {
230 if (existing_registration
!= registration()) {
231 Complete(SERVICE_WORKER_ERROR_NOT_FOUND
);
238 // Creates a new ServiceWorkerRegistration.
239 void ServiceWorkerRegisterJob::RegisterAndContinue(
240 ServiceWorkerStatusCode status
) {
242 if (status
!= SERVICE_WORKER_OK
) {
243 // Abort this registration job.
248 set_registration(new ServiceWorkerRegistration(
249 pattern_
, script_url_
, context_
->storage()->NewRegistrationId(),
251 AssociateProviderHostsToRegistration(registration());
255 // This function corresponds to the spec's [[Update]] algorithm.
256 void ServiceWorkerRegisterJob::UpdateAndContinue() {
258 context_
->storage()->NotifyInstallingRegistration(registration());
260 // TODO(falken): "If serviceWorkerRegistration.installingWorker is not null.."
261 // then terminate the installing worker. It doesn't make sense to implement
262 // yet since we always activate the worker if install completed, so there can
263 // be no installing worker at this point.
265 // "Let serviceWorker be a newly-created ServiceWorker object..." and start
267 set_new_version(new ServiceWorkerVersion(
268 registration(), context_
->storage()->NewVersionId(), context_
));
270 bool pause_after_download
= job_type_
== UPDATE_JOB
;
271 if (pause_after_download
)
272 new_version()->embedded_worker()->AddListener(this);
273 new_version()->StartWorkerWithCandidateProcesses(
274 pending_process_ids_
,
275 pause_after_download
,
276 base::Bind(&ServiceWorkerRegisterJob::OnStartWorkerFinished
,
277 weak_factory_
.GetWeakPtr()));
280 void ServiceWorkerRegisterJob::OnStartWorkerFinished(
281 ServiceWorkerStatusCode status
) {
282 // "If serviceWorker fails to start up..." then reject the promise with an
284 if (status
!= SERVICE_WORKER_OK
) {
289 // "Resolve promise with serviceWorker."
290 DCHECK(!registration()->installing_version());
291 ResolvePromise(status
, registration(), new_version());
292 InstallAndContinue();
295 // This function corresponds to the spec's _Install algorithm.
296 void ServiceWorkerRegisterJob::InstallAndContinue() {
299 // "3. Set registration.installingWorker to worker."
300 registration()->SetInstallingVersion(new_version());
302 // "4. Run the [[UpdateState]] algorithm passing registration.installingWorker
303 // and "installing" as the arguments."
304 new_version()->SetStatus(ServiceWorkerVersion::INSTALLING
);
306 // TODO(nhiroki,michaeln): "5. Fire a simple event named updatefound..."
308 // "6. Fire an event named install..."
309 new_version()->DispatchInstallEvent(
311 base::Bind(&ServiceWorkerRegisterJob::OnInstallFinished
,
312 weak_factory_
.GetWeakPtr()));
315 void ServiceWorkerRegisterJob::OnInstallFinished(
316 ServiceWorkerStatusCode status
) {
317 // TODO(kinuko,falken): For some error cases (e.g. ServiceWorker is
318 // unexpectedly terminated) we may want to retry sending the event again.
319 if (status
!= SERVICE_WORKER_OK
) {
320 // "8. If installFailed is true, then:..."
326 context_
->storage()->StoreRegistration(
329 base::Bind(&ServiceWorkerRegisterJob::OnStoreRegistrationComplete
,
330 weak_factory_
.GetWeakPtr()));
333 void ServiceWorkerRegisterJob::OnStoreRegistrationComplete(
334 ServiceWorkerStatusCode status
) {
335 if (status
!= SERVICE_WORKER_OK
) {
340 // "9. If registration.waitingWorker is not null, then:..."
341 if (registration()->waiting_version()) {
342 // "1. Run the [[UpdateState]] algorithm passing registration.waitingWorker
343 // and "redundant" as the arguments."
344 registration()->waiting_version()->SetStatus(
345 ServiceWorkerVersion::REDUNDANT
);
348 // "10. Set registration.waitingWorker to registration.installingWorker."
349 // "11. Set registration.installingWorker to null."
350 registration()->SetWaitingVersion(new_version());
352 // "12. Run the [[UpdateState]] algorithm passing registration.waitingWorker
353 // and "installed" as the arguments."
354 new_version()->SetStatus(ServiceWorkerVersion::INSTALLED
);
356 // TODO(michaeln): "13. If activateImmediate is true, then..."
358 // "14. Wait until no document is using registration as their
359 // Service Worker registration."
360 registration()->ActivateWaitingVersionWhenReady();
362 Complete(SERVICE_WORKER_OK
);
365 void ServiceWorkerRegisterJob::Complete(ServiceWorkerStatusCode status
) {
366 CompleteInternal(status
);
367 context_
->job_coordinator()->FinishJob(pattern_
, this);
370 void ServiceWorkerRegisterJob::CompleteInternal(
371 ServiceWorkerStatusCode status
) {
373 if (status
!= SERVICE_WORKER_OK
) {
374 if (registration()) {
376 registration()->UnsetVersion(new_version());
377 new_version()->Doom();
379 if (!registration()->waiting_version() &&
380 !registration()->active_version()) {
381 registration()->NotifyRegistrationFailed();
382 context_
->storage()->DeleteRegistration(
383 registration()->id(),
384 registration()->script_url().GetOrigin(),
385 base::Bind(&ServiceWorkerUtils::NoOpStatusCallback
));
388 if (!is_promise_resolved_
)
389 ResolvePromise(status
, NULL
, NULL
);
391 DCHECK(callbacks_
.empty());
392 if (registration()) {
393 context_
->storage()->NotifyDoneInstallingRegistration(
394 registration(), new_version(), status
);
397 new_version()->embedded_worker()->RemoveListener(this);
400 void ServiceWorkerRegisterJob::ResolvePromise(
401 ServiceWorkerStatusCode status
,
402 ServiceWorkerRegistration
* registration
,
403 ServiceWorkerVersion
* version
) {
404 DCHECK(!is_promise_resolved_
);
405 is_promise_resolved_
= true;
406 promise_resolved_status_
= status
;
407 promise_resolved_registration_
= registration
;
408 promise_resolved_version_
= version
;
409 for (std::vector
<RegistrationCallback
>::iterator it
= callbacks_
.begin();
410 it
!= callbacks_
.end();
412 it
->Run(status
, registration
, version
);
417 void ServiceWorkerRegisterJob::OnPausedAfterDownload() {
418 // This happens prior to OnStartWorkerFinished time.
419 scoped_refptr
<ServiceWorkerVersion
> current_version
=
420 registration()->active_version();
421 DCHECK(current_version
);
422 int64 current_script_id
=
423 current_version
->script_cache_map()->Lookup(script_url_
);
424 int64 new_script_id
=
425 new_version()->script_cache_map()->Lookup(script_url_
);
427 // TODO(michaeln): It would be better to compare as the new resource
428 // is being downloaded and to avoid writing it to disk until we know
430 context_
->storage()->CompareScriptResources(
431 current_script_id
, new_script_id
,
432 base::Bind(&ServiceWorkerRegisterJob::OnCompareScriptResourcesComplete
,
433 weak_factory_
.GetWeakPtr(),
437 bool ServiceWorkerRegisterJob::OnMessageReceived(const IPC::Message
& message
) {
441 void ServiceWorkerRegisterJob::OnCompareScriptResourcesComplete(
442 ServiceWorkerVersion
* current_version
,
443 ServiceWorkerStatusCode status
,
446 ResolvePromise(SERVICE_WORKER_OK
, registration(), current_version
);
447 Complete(SERVICE_WORKER_ERROR_EXISTS
);
451 // Proceed with really starting the worker.
452 new_version()->embedded_worker()->ResumeAfterDownload();
453 new_version()->embedded_worker()->RemoveListener(this);
456 void ServiceWorkerRegisterJob::AssociateProviderHostsToRegistration(
457 ServiceWorkerRegistration
* registration
) {
458 DCHECK(registration
);
459 for (scoped_ptr
<ServiceWorkerContextCore::ProviderHostIterator
> it
=
460 context_
->GetProviderHostIterator();
461 !it
->IsAtEnd(); it
->Advance()) {
462 ServiceWorkerProviderHost
* host
= it
->GetProviderHost();
463 if (ServiceWorkerUtils::ScopeMatches(registration
->pattern(),
464 host
->document_url())) {
465 if (host
->CanAssociateRegistration(registration
))
466 host
->AssociateRegistration(registration
);
471 } // namespace content