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 existing_registration->script_url().GetOrigin(),
187 base::Bind(&ServiceWorkerRegisterJob::RegisterAndContinue,
188 weak_factory_.GetWeakPtr()));
191 // Creates a new ServiceWorkerRegistration.
192 void ServiceWorkerRegisterJob::RegisterAndContinue(
193 ServiceWorkerStatusCode status) {
195 if (status != SERVICE_WORKER_OK) {
196 // Abort this registration job.
201 set_registration(new ServiceWorkerRegistration(
202 pattern_, script_url_, context_->storage()->NewRegistrationId(),
204 context_->storage()->NotifyInstallingRegistration(registration());
205 UpdateAndContinue(SERVICE_WORKER_OK);
208 // This function corresponds to the spec's _Update algorithm.
209 void ServiceWorkerRegisterJob::UpdateAndContinue(
210 ServiceWorkerStatusCode status) {
212 if (status != SERVICE_WORKER_OK) {
213 // Abort this registration job.
218 // TODO(falken): "If serviceWorkerRegistration.pendingWorker is not null..."
219 // then terminate the pending worker. It doesn't make sense to implement yet
220 // since we always activate the worker if install completed, so there can be
221 // no pending worker at this point.
222 DCHECK(!registration()->pending_version());
224 // "Let serviceWorker be a newly-created ServiceWorker object..." and start
226 set_pending_version(new ServiceWorkerVersion(
227 registration(), context_->storage()->NewVersionId(), context_));
229 // TODO(michaeln): Start the worker into a paused state where the
230 // script resource is downloaded but not yet evaluated.
231 pending_version()->StartWorkerWithCandidateProcesses(
232 pending_process_ids_,
233 base::Bind(&ServiceWorkerRegisterJob::OnStartWorkerFinished,
234 weak_factory_.GetWeakPtr()));
237 void ServiceWorkerRegisterJob::OnStartWorkerFinished(
238 ServiceWorkerStatusCode status) {
239 // "If serviceWorker fails to start up..." then reject the promise with an
241 if (status != SERVICE_WORKER_OK) {
246 // TODO(michaeln): Compare the old and new script.
247 // If different unpause the worker and continue with
248 // the job. If the same ResolvePromise with the current
249 // version and complete the job, throwing away the new version
250 // since there's nothing new.
252 // "Resolve promise with serviceWorker."
253 // Although the spec doesn't set pendingWorker until after resolving the
254 // promise, our system's resolving works by passing ServiceWorkerRegistration
255 // to the callbacks, so pendingWorker must be set first.
256 DCHECK(!registration()->pending_version());
257 registration()->set_pending_version(pending_version());
258 ResolvePromise(status, registration(), pending_version());
260 AssociatePendingVersionToDocuments(pending_version());
262 InstallAndContinue();
265 // This function corresponds to the spec's _Install algorithm.
266 void ServiceWorkerRegisterJob::InstallAndContinue() {
268 // "Set serviceWorkerRegistration.pendingWorker._state to installing."
269 // "Fire install event on the associated ServiceWorkerGlobalScope object."
270 pending_version()->DispatchInstallEvent(
272 base::Bind(&ServiceWorkerRegisterJob::OnInstallFinished,
273 weak_factory_.GetWeakPtr()));
276 void ServiceWorkerRegisterJob::OnInstallFinished(
277 ServiceWorkerStatusCode status) {
278 // "If any handler called waitUntil()..." and the resulting promise
279 // is rejected, abort.
280 // TODO(kinuko,falken): For some error cases (e.g. ServiceWorker is
281 // unexpectedly terminated) we may want to retry sending the event again.
282 if (status != SERVICE_WORKER_OK) {
288 context_->storage()->StoreRegistration(
291 base::Bind(&ServiceWorkerRegisterJob::OnStoreRegistrationComplete,
292 weak_factory_.GetWeakPtr()));
295 void ServiceWorkerRegisterJob::OnStoreRegistrationComplete(
296 ServiceWorkerStatusCode status) {
297 if (status != SERVICE_WORKER_OK) {
302 ActivateAndContinue();
305 // This function corresponds to the spec's _Activate algorithm.
306 void ServiceWorkerRegisterJob::ActivateAndContinue() {
309 // "If existingWorker is not null, then: wait for exitingWorker to finish
310 // handling any in-progress requests."
311 // See if we already have an active_version for the scope and it has
312 // controllee documents (if so activating the new version should wait
313 // until we have no documents controlled by the version).
314 if (registration()->active_version() &&
315 registration()->active_version()->HasControllee()) {
316 // TODO(kinuko,falken): Currently we immediately return if the existing
317 // registration already has an active version, so we shouldn't come
320 // TODO(falken): Register an continuation task to wait for NoControllees
321 // notification so that we can resume activation later (see comments
322 // in ServiceWorkerVersion::RemoveControllee).
323 Complete(SERVICE_WORKER_OK);
327 // "Set serviceWorkerRegistration.pendingWorker to null."
328 // "Set serviceWorkerRegistration.activeWorker to activatingWorker."
329 registration()->set_pending_version(NULL);
330 AssociatePendingVersionToDocuments(NULL);
331 DCHECK(!registration()->active_version());
332 registration()->set_active_version(pending_version());
334 // "Set serviceWorkerRegistration.activeWorker._state to activating."
335 // "Fire activate event on the associated ServiceWorkerGlobalScope object."
336 // "Set serviceWorkerRegistration.activeWorker._state to active."
337 pending_version()->DispatchActivateEvent(
338 base::Bind(&ServiceWorkerRegisterJob::OnActivateFinished,
339 weak_factory_.GetWeakPtr()));
342 void ServiceWorkerRegisterJob::OnActivateFinished(
343 ServiceWorkerStatusCode status) {
344 // "If any handler called waitUntil()..." and the resulting promise
345 // is rejected, abort.
346 // TODO(kinuko,falken): For some error cases (e.g. ServiceWorker is
347 // unexpectedly terminated) we may want to retry sending the event again.
348 if (status != SERVICE_WORKER_OK) {
349 registration()->set_active_version(NULL);
353 context_->storage()->UpdateToActiveState(
355 base::Bind(&ServiceWorkerUtils::NoOpStatusCallback));
356 Complete(SERVICE_WORKER_OK);
359 void ServiceWorkerRegisterJob::Complete(ServiceWorkerStatusCode status) {
361 if (status != SERVICE_WORKER_OK) {
362 if (registration() && registration()->pending_version()) {
363 AssociatePendingVersionToDocuments(NULL);
364 registration()->set_pending_version(NULL);
365 // TODO(michaeln): Take care of deleteting the version's
366 // script resources too.
368 if (registration() && !registration()->active_version()) {
369 context_->storage()->DeleteRegistration(
370 registration()->id(),
371 registration()->script_url().GetOrigin(),
372 base::Bind(&ServiceWorkerUtils::NoOpStatusCallback));
374 if (!is_promise_resolved_)
375 ResolvePromise(status, NULL, NULL);
377 DCHECK(callbacks_.empty());
378 context_->storage()->NotifyDoneInstallingRegistration(registration());
379 context_->job_coordinator()->FinishJob(pattern_, this);
382 void ServiceWorkerRegisterJob::ResolvePromise(
383 ServiceWorkerStatusCode status,
384 ServiceWorkerRegistration* registration,
385 ServiceWorkerVersion* version) {
386 DCHECK(!is_promise_resolved_);
387 is_promise_resolved_ = true;
388 promise_resolved_status_ = status;
389 promise_resolved_registration_ = registration;
390 promise_resolved_version_ = version;
391 for (std::vector<RegistrationCallback>::iterator it = callbacks_.begin();
392 it != callbacks_.end();
394 it->Run(status, registration, version);
399 void ServiceWorkerRegisterJob::AssociatePendingVersionToDocuments(
400 ServiceWorkerVersion* version) {
401 // TODO(michaeln): This needs to respect the longest prefix wins
402 // when it comes to finding a registration for a document url.
403 // This should should utilize storage->FindRegistrationForDocument().
404 for (scoped_ptr<ServiceWorkerContextCore::ProviderHostIterator> it =
405 context_->GetProviderHostIterator();
408 ServiceWorkerProviderHost* provider_host = it->GetProviderHost();
409 if (ServiceWorkerUtils::ScopeMatches(pattern_,
410 provider_host->document_url()))
411 provider_host->SetPendingVersion(version);
415 } // namespace content