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
)
34 script_url_(script_url
),
36 is_promise_resolved_(false),
37 promise_resolved_status_(SERVICE_WORKER_OK
),
38 weak_factory_(this) {}
40 ServiceWorkerRegisterJob::~ServiceWorkerRegisterJob() {
41 DCHECK(phase_
== INITIAL
|| phase_
== COMPLETE
);
44 void ServiceWorkerRegisterJob::AddCallback(const RegistrationCallback
& callback
,
46 if (!is_promise_resolved_
) {
47 callbacks_
.push_back(callback
);
48 if (process_id
!= -1 && (phase_
< UPDATE
|| !pending_version()))
49 pending_process_ids_
.push_back(process_id
);
53 callback
, promise_resolved_status_
,
54 promise_resolved_registration_
, promise_resolved_version_
));
57 void ServiceWorkerRegisterJob::Start() {
59 context_
->storage()->FindRegistrationForPattern(
62 &ServiceWorkerRegisterJob::HandleExistingRegistrationAndContinue
,
63 weak_factory_
.GetWeakPtr()));
66 bool ServiceWorkerRegisterJob::Equals(ServiceWorkerRegisterJobBase
* job
) {
67 if (job
->GetType() != GetType())
69 ServiceWorkerRegisterJob
* register_job
=
70 static_cast<ServiceWorkerRegisterJob
*>(job
);
71 return register_job
->pattern_
== pattern_
&&
72 register_job
->script_url_
== script_url_
;
75 RegistrationJobType
ServiceWorkerRegisterJob::GetType() {
79 ServiceWorkerRegisterJob::Internal::Internal() {}
81 ServiceWorkerRegisterJob::Internal::~Internal() {}
83 void ServiceWorkerRegisterJob::set_registration(
84 ServiceWorkerRegistration
* registration
) {
85 DCHECK(phase_
== START
|| phase_
== REGISTER
) << phase_
;
86 DCHECK(!internal_
.registration
);
87 internal_
.registration
= registration
;
90 ServiceWorkerRegistration
* ServiceWorkerRegisterJob::registration() {
91 DCHECK(phase_
>= REGISTER
) << phase_
;
92 DCHECK(internal_
.registration
);
93 return internal_
.registration
;
96 void ServiceWorkerRegisterJob::set_pending_version(
97 ServiceWorkerVersion
* version
) {
98 DCHECK(phase_
== UPDATE
|| phase_
== ACTIVATE
) << phase_
;
99 DCHECK(!internal_
.pending_version
|| !version
);
100 internal_
.pending_version
= version
;
103 ServiceWorkerVersion
* ServiceWorkerRegisterJob::pending_version() {
104 DCHECK(phase_
>= UPDATE
) << phase_
;
105 return internal_
.pending_version
;
108 void ServiceWorkerRegisterJob::SetPhase(Phase phase
) {
114 DCHECK(phase_
== INITIAL
) << phase_
;
117 DCHECK(phase_
== START
) << phase_
;
120 DCHECK(phase_
== START
|| phase_
== REGISTER
) << phase_
;
123 DCHECK(phase_
== UPDATE
) << phase_
;
126 DCHECK(phase_
== INSTALL
) << phase_
;
129 DCHECK(phase_
== STORE
) << phase_
;
132 DCHECK(phase_
!= INITIAL
&& phase_
!= COMPLETE
) << phase_
;
138 // This function corresponds to the steps in Register following
139 // "Let serviceWorkerRegistration be _GetRegistration(scope)"
140 // |existing_registration| corresponds to serviceWorkerRegistration.
141 // Throughout this file, comments in quotes are excerpts from the spec.
142 void ServiceWorkerRegisterJob::HandleExistingRegistrationAndContinue(
143 ServiceWorkerStatusCode status
,
144 const scoped_refptr
<ServiceWorkerRegistration
>& existing_registration
) {
145 // On unexpected error, abort this registration job.
146 if (status
!= SERVICE_WORKER_ERROR_NOT_FOUND
&& status
!= SERVICE_WORKER_OK
) {
151 // "If serviceWorkerRegistration is not null and script is equal to
152 // serviceWorkerRegistration.scriptUrl..." resolve with the existing
153 // registration and abort.
154 if (existing_registration
.get() &&
155 existing_registration
->script_url() == script_url_
) {
156 set_registration(existing_registration
);
157 // If there's no active version, go ahead to Update (this isn't in the spec
158 // but seems reasonable, and without SoftUpdate implemented we can never
159 // Update otherwise).
160 if (!existing_registration
->active_version()) {
161 UpdateAndContinue(status
);
165 status
, existing_registration
, existing_registration
->active_version());
166 Complete(SERVICE_WORKER_OK
);
170 // "If serviceWorkerRegistration is null..." create a new registration.
171 if (!existing_registration
.get()) {
172 RegisterAndContinue(SERVICE_WORKER_OK
);
176 // On script URL mismatch, "set serviceWorkerRegistration.scriptUrl to
177 // script." We accomplish this by deleting the existing registration and
178 // registering a new one.
179 // TODO(falken): Match the spec. We now throw away the active_version_ and
180 // pending_version_ of the existing registration, which isn't in the spec.
181 // TODO(michaeln): Deactivate the live existing_registration object and
182 // eventually call storage->DeleteVersionResources()
183 // when it no longer has any controllees.
184 context_
->storage()->DeleteRegistration(
185 existing_registration
->id(),
186 base::Bind(&ServiceWorkerRegisterJob::RegisterAndContinue
,
187 weak_factory_
.GetWeakPtr()));
190 // Creates a new ServiceWorkerRegistration.
191 void ServiceWorkerRegisterJob::RegisterAndContinue(
192 ServiceWorkerStatusCode status
) {
194 if (status
!= SERVICE_WORKER_OK
) {
195 // Abort this registration job.
200 set_registration(new ServiceWorkerRegistration(
201 pattern_
, script_url_
, context_
->storage()->NewRegistrationId(),
203 context_
->storage()->NotifyInstallingRegistration(registration());
204 UpdateAndContinue(SERVICE_WORKER_OK
);
207 // This function corresponds to the spec's _Update algorithm.
208 void ServiceWorkerRegisterJob::UpdateAndContinue(
209 ServiceWorkerStatusCode status
) {
211 if (status
!= SERVICE_WORKER_OK
) {
212 // Abort this registration job.
217 // TODO(falken): "If serviceWorkerRegistration.pendingWorker is not null..."
218 // then terminate the pending worker. It doesn't make sense to implement yet
219 // since we always activate the worker if install completed, so there can be
220 // no pending worker at this point.
221 DCHECK(!registration()->pending_version());
223 // "Let serviceWorker be a newly-created ServiceWorker object..." and start
225 set_pending_version(new ServiceWorkerVersion(
226 registration(), context_
->storage()->NewVersionId(), context_
));
228 // TODO(michaeln): Start the worker into a paused state where the
229 // script resource is downloaded but not yet evaluated.
230 pending_version()->StartWorkerWithCandidateProcesses(
231 pending_process_ids_
,
232 base::Bind(&ServiceWorkerRegisterJob::OnStartWorkerFinished
,
233 weak_factory_
.GetWeakPtr()));
236 void ServiceWorkerRegisterJob::OnStartWorkerFinished(
237 ServiceWorkerStatusCode status
) {
238 // "If serviceWorker fails to start up..." then reject the promise with an
240 if (status
!= SERVICE_WORKER_OK
) {
245 // TODO(michaeln): Compare the old and new script.
246 // If different unpause the worker and continue with
247 // the job. If the same ResolvePromise with the current
248 // version and complete the job, throwing away the new version
249 // since there's nothing new.
251 // "Resolve promise with serviceWorker."
252 // Although the spec doesn't set pendingWorker until after resolving the
253 // promise, our system's resolving works by passing ServiceWorkerRegistration
254 // to the callbacks, so pendingWorker must be set first.
255 DCHECK(!registration()->pending_version());
256 registration()->set_pending_version(pending_version());
257 ResolvePromise(status
, registration(), pending_version());
259 AssociatePendingVersionToDocuments(pending_version());
261 InstallAndContinue();
264 // This function corresponds to the spec's _Install algorithm.
265 void ServiceWorkerRegisterJob::InstallAndContinue() {
267 // "Set serviceWorkerRegistration.pendingWorker._state to installing."
268 // "Fire install event on the associated ServiceWorkerGlobalScope object."
269 pending_version()->DispatchInstallEvent(
271 base::Bind(&ServiceWorkerRegisterJob::OnInstallFinished
,
272 weak_factory_
.GetWeakPtr()));
275 void ServiceWorkerRegisterJob::OnInstallFinished(
276 ServiceWorkerStatusCode status
) {
277 // "If any handler called waitUntil()..." and the resulting promise
278 // is rejected, abort.
279 // TODO(kinuko,falken): For some error cases (e.g. ServiceWorker is
280 // unexpectedly terminated) we may want to retry sending the event again.
281 if (status
!= SERVICE_WORKER_OK
) {
287 context_
->storage()->StoreRegistration(
290 base::Bind(&ServiceWorkerRegisterJob::OnStoreRegistrationComplete
,
291 weak_factory_
.GetWeakPtr()));
294 void ServiceWorkerRegisterJob::OnStoreRegistrationComplete(
295 ServiceWorkerStatusCode status
) {
296 if (status
!= SERVICE_WORKER_OK
) {
301 ActivateAndContinue();
304 // This function corresponds to the spec's _Activate algorithm.
305 void ServiceWorkerRegisterJob::ActivateAndContinue() {
308 // "If existingWorker is not null, then: wait for exitingWorker to finish
309 // handling any in-progress requests."
310 // See if we already have an active_version for the scope and it has
311 // controllee documents (if so activating the new version should wait
312 // until we have no documents controlled by the version).
313 if (registration()->active_version() &&
314 registration()->active_version()->HasControllee()) {
315 // TODO(kinuko,falken): Currently we immediately return if the existing
316 // registration already has an active version, so we shouldn't come
319 // TODO(falken): Register an continuation task to wait for NoControllees
320 // notification so that we can resume activation later (see comments
321 // in ServiceWorkerVersion::RemoveControllee).
322 Complete(SERVICE_WORKER_OK
);
326 // "Set serviceWorkerRegistration.pendingWorker to null."
327 // "Set serviceWorkerRegistration.activeWorker to activatingWorker."
328 registration()->set_pending_version(NULL
);
329 AssociatePendingVersionToDocuments(NULL
);
330 DCHECK(!registration()->active_version());
331 registration()->set_active_version(pending_version());
333 // "Set serviceWorkerRegistration.activeWorker._state to activating."
334 // "Fire activate event on the associated ServiceWorkerGlobalScope object."
335 // "Set serviceWorkerRegistration.activeWorker._state to active."
336 pending_version()->DispatchActivateEvent(
337 base::Bind(&ServiceWorkerRegisterJob::OnActivateFinished
,
338 weak_factory_
.GetWeakPtr()));
341 void ServiceWorkerRegisterJob::OnActivateFinished(
342 ServiceWorkerStatusCode status
) {
343 // "If any handler called waitUntil()..." and the resulting promise
344 // is rejected, abort.
345 // TODO(kinuko,falken): For some error cases (e.g. ServiceWorker is
346 // unexpectedly terminated) we may want to retry sending the event again.
347 if (status
!= SERVICE_WORKER_OK
) {
348 registration()->set_active_version(NULL
);
352 context_
->storage()->UpdateToActiveState(
354 base::Bind(&ServiceWorkerUtils::NoOpStatusCallback
));
355 Complete(SERVICE_WORKER_OK
);
358 void ServiceWorkerRegisterJob::Complete(ServiceWorkerStatusCode status
) {
360 if (status
!= SERVICE_WORKER_OK
) {
361 if (registration() && registration()->pending_version()) {
362 AssociatePendingVersionToDocuments(NULL
);
363 registration()->set_pending_version(NULL
);
364 // TODO(michaeln): Take care of deleteting the version's
365 // script resources too.
367 if (registration() && !registration()->active_version()) {
368 context_
->storage()->DeleteRegistration(
369 registration()->id(),
370 base::Bind(&ServiceWorkerUtils::NoOpStatusCallback
));
372 if (!is_promise_resolved_
)
373 ResolvePromise(status
, NULL
, NULL
);
375 DCHECK(callbacks_
.empty());
376 context_
->storage()->NotifyDoneInstallingRegistration(registration());
377 context_
->job_coordinator()->FinishJob(pattern_
, this);
380 void ServiceWorkerRegisterJob::ResolvePromise(
381 ServiceWorkerStatusCode status
,
382 ServiceWorkerRegistration
* registration
,
383 ServiceWorkerVersion
* version
) {
384 DCHECK(!is_promise_resolved_
);
385 is_promise_resolved_
= true;
386 promise_resolved_status_
= status
;
387 promise_resolved_registration_
= registration
;
388 promise_resolved_version_
= version
;
389 for (std::vector
<RegistrationCallback
>::iterator it
= callbacks_
.begin();
390 it
!= callbacks_
.end();
392 it
->Run(status
, registration
, version
);
397 void ServiceWorkerRegisterJob::AssociatePendingVersionToDocuments(
398 ServiceWorkerVersion
* version
) {
399 // TODO(michaeln): This needs to respect the longest prefix wins
400 // when it comes to finding a registration for a document url.
401 // This should should utilize storage->FindRegistrationForDocument().
402 for (scoped_ptr
<ServiceWorkerContextCore::ProviderHostIterator
> it
=
403 context_
->GetProviderHostIterator();
406 ServiceWorkerProviderHost
* provider_host
= it
->GetProviderHost();
407 if (ServiceWorkerUtils::ScopeMatches(pattern_
,
408 provider_host
->document_url()))
409 provider_host
->SetPendingVersion(version
);
413 } // namespace content