Service Worker: in Unregister, wait until after the active worker no longer controls...
[chromium-blink-merge.git] / content / browser / service_worker / service_worker_register_job.cc
blob389fa7f5e6eb320c7af923b7267c4186081092c3
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"
7 #include <vector>
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"
16 namespace content {
18 namespace {
20 void RunSoon(const base::Closure& closure) {
21 base::MessageLoop::current()->PostTask(FROM_HERE, closure);
24 } // namespace
26 typedef ServiceWorkerRegisterJobBase::RegistrationJobType RegistrationJobType;
28 ServiceWorkerRegisterJob::ServiceWorkerRegisterJob(
29 base::WeakPtr<ServiceWorkerContextCore> context,
30 const GURL& pattern,
31 const GURL& script_url)
32 : context_(context),
33 job_type_(REGISTRATION_JOB),
34 pattern_(pattern),
35 script_url_(script_url),
36 phase_(INITIAL),
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)
44 : context_(context),
45 job_type_(UPDATE_JOB),
46 pattern_(registration->pattern()),
47 script_url_(registration->script_url()),
48 phase_(INITIAL),
49 is_promise_resolved_(false),
50 promise_resolved_status_(SERVICE_WORKER_OK),
51 weak_factory_(this) {
52 internal_.registration = registration;
55 ServiceWorkerRegisterJob::~ServiceWorkerRegisterJob() {
56 DCHECK(!context_ ||
57 phase_ == INITIAL || phase_ == COMPLETE || phase_ == ABORT)
58 << "Jobs should only be interrupted during shutdown.";
61 void ServiceWorkerRegisterJob::AddCallback(const RegistrationCallback& callback,
62 int process_id) {
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);
67 return;
69 RunSoon(base::Bind(
70 callback, promise_resolved_status_,
71 promise_resolved_registration_, promise_resolved_version_));
74 void ServiceWorkerRegisterJob::Start() {
75 SetPhase(START);
76 ServiceWorkerStorage::FindRegistrationCallback next_step;
77 if (job_type_ == REGISTRATION_JOB) {
78 next_step = base::Bind(
79 &ServiceWorkerRegisterJob::ContinueWithRegistration,
80 weak_factory_.GetWeakPtr());
81 } else {
82 next_step = base::Bind(
83 &ServiceWorkerRegisterJob::ContinueWithUpdate,
84 weak_factory_.GetWeakPtr());
87 scoped_refptr<ServiceWorkerRegistration> registration =
88 context_->storage()->GetUninstallingRegistration(pattern_);
89 if (registration)
90 RunSoon(base::Bind(next_step, SERVICE_WORKER_OK, registration));
91 else
92 context_->storage()->FindRegistrationForPattern(pattern_, next_step);
95 void ServiceWorkerRegisterJob::Abort() {
96 SetPhase(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())
104 return false;
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() {
112 return job_type_;
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) {
144 switch (phase) {
145 case INITIAL:
146 NOTREACHED();
147 break;
148 case START:
149 DCHECK(phase_ == INITIAL) << phase_;
150 break;
151 case REGISTER:
152 DCHECK(phase_ == START) << phase_;
153 break;
154 case UPDATE:
155 DCHECK(phase_ == START || phase_ == REGISTER) << phase_;
156 break;
157 case INSTALL:
158 DCHECK(phase_ == UPDATE) << phase_;
159 break;
160 case STORE:
161 DCHECK(phase_ == INSTALL) << phase_;
162 break;
163 case COMPLETE:
164 DCHECK(phase_ != INITIAL && phase_ != COMPLETE) << phase_;
165 break;
166 case ABORT:
167 break;
169 phase_ = 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) {
180 Complete(status);
181 return;
184 if (!existing_registration) {
185 RegisterAndContinue(SERVICE_WORKER_OK);
186 return;
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()) {
199 UpdateAndContinue();
200 return;
203 ResolvePromise(
204 status, existing_registration, existing_registration->active_version());
205 Complete(SERVICE_WORKER_OK);
206 return;
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
213 // controllees.
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) {
226 Complete(status);
227 return;
230 if (existing_registration != registration()) {
231 Complete(SERVICE_WORKER_ERROR_NOT_FOUND);
232 return;
235 UpdateAndContinue();
238 // Creates a new ServiceWorkerRegistration.
239 void ServiceWorkerRegisterJob::RegisterAndContinue(
240 ServiceWorkerStatusCode status) {
241 SetPhase(REGISTER);
242 if (status != SERVICE_WORKER_OK) {
243 // Abort this registration job.
244 Complete(status);
245 return;
248 set_registration(new ServiceWorkerRegistration(
249 pattern_, script_url_, context_->storage()->NewRegistrationId(),
250 context_));
251 AssociateProviderHostsToRegistration(registration());
252 UpdateAndContinue();
255 // This function corresponds to the spec's [[Update]] algorithm.
256 void ServiceWorkerRegisterJob::UpdateAndContinue() {
257 SetPhase(UPDATE);
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
266 // the worker.
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
283 // error and abort.
284 if (status != SERVICE_WORKER_OK) {
285 Complete(status);
286 return;
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() {
297 SetPhase(INSTALL);
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:..."
321 Complete(status);
322 return;
325 SetPhase(STORE);
326 context_->storage()->StoreRegistration(
327 registration(),
328 new_version(),
329 base::Bind(&ServiceWorkerRegisterJob::OnStoreRegistrationComplete,
330 weak_factory_.GetWeakPtr()));
333 void ServiceWorkerRegisterJob::OnStoreRegistrationComplete(
334 ServiceWorkerStatusCode status) {
335 if (status != SERVICE_WORKER_OK) {
336 Complete(status);
337 return;
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) {
372 SetPhase(COMPLETE);
373 if (status != SERVICE_WORKER_OK) {
374 if (registration()) {
375 if (new_version()) {
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);
396 if (new_version())
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();
411 ++it) {
412 it->Run(status, registration, version);
414 callbacks_.clear();
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
429 // its needed.
430 context_->storage()->CompareScriptResources(
431 current_script_id, new_script_id,
432 base::Bind(&ServiceWorkerRegisterJob::OnCompareScriptResourcesComplete,
433 weak_factory_.GetWeakPtr(),
434 current_version));
437 bool ServiceWorkerRegisterJob::OnMessageReceived(const IPC::Message& message) {
438 return false;
441 void ServiceWorkerRegisterJob::OnCompareScriptResourcesComplete(
442 ServiceWorkerVersion* current_version,
443 ServiceWorkerStatusCode status,
444 bool are_equal) {
445 if (are_equal) {
446 ResolvePromise(SERVICE_WORKER_OK, registration(), current_version);
447 Complete(SERVICE_WORKER_ERROR_EXISTS);
448 return;
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