Upstream version 8.37.180.0
[platform/framework/web/crosswalk.git] / src / content / browser / service_worker / service_worker_register_job.cc
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.
4
5 #include "content/browser/service_worker/service_worker_register_job.h"
6
7 #include <vector>
8
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
16 namespace content {
17
18 namespace {
19
20 void RunSoon(const base::Closure& closure) {
21   base::MessageLoop::current()->PostTask(FROM_HERE, closure);
22 }
23
24 }
25
26 typedef ServiceWorkerRegisterJobBase::RegistrationJobType RegistrationJobType;
27
28 ServiceWorkerRegisterJob::ServiceWorkerRegisterJob(
29     base::WeakPtr<ServiceWorkerContextCore> context,
30     const GURL& pattern,
31     const GURL& script_url)
32     : context_(context),
33       pattern_(pattern),
34       script_url_(script_url),
35       phase_(INITIAL),
36       is_promise_resolved_(false),
37       promise_resolved_status_(SERVICE_WORKER_OK),
38       weak_factory_(this) {}
39
40 ServiceWorkerRegisterJob::~ServiceWorkerRegisterJob() {
41   DCHECK(!context_ ||
42          phase_ == INITIAL || phase_ == COMPLETE || phase_ == ABORT)
43       << "Jobs should only be interrupted during shutdown.";
44 }
45
46 void ServiceWorkerRegisterJob::AddCallback(const RegistrationCallback& callback,
47                                            int process_id) {
48   if (!is_promise_resolved_) {
49     callbacks_.push_back(callback);
50     if (process_id != -1 && (phase_ < UPDATE || !pending_version()))
51       pending_process_ids_.push_back(process_id);
52     return;
53   }
54   RunSoon(base::Bind(
55       callback, promise_resolved_status_,
56       promise_resolved_registration_, promise_resolved_version_));
57 }
58
59 void ServiceWorkerRegisterJob::Start() {
60   SetPhase(START);
61   context_->storage()->FindRegistrationForPattern(
62       pattern_,
63       base::Bind(
64           &ServiceWorkerRegisterJob::HandleExistingRegistrationAndContinue,
65           weak_factory_.GetWeakPtr()));
66 }
67
68 void ServiceWorkerRegisterJob::Abort() {
69   SetPhase(ABORT);
70   CompleteInternal(SERVICE_WORKER_ERROR_ABORT);
71   // Don't have to call FinishJob() because the caller takes care of removing
72   // the jobs from the queue.
73 }
74
75 bool ServiceWorkerRegisterJob::Equals(ServiceWorkerRegisterJobBase* job) {
76   if (job->GetType() != GetType())
77     return false;
78   ServiceWorkerRegisterJob* register_job =
79       static_cast<ServiceWorkerRegisterJob*>(job);
80   return register_job->pattern_ == pattern_ &&
81          register_job->script_url_ == script_url_;
82 }
83
84 RegistrationJobType ServiceWorkerRegisterJob::GetType() {
85   return REGISTRATION;
86 }
87
88 ServiceWorkerRegisterJob::Internal::Internal() {}
89
90 ServiceWorkerRegisterJob::Internal::~Internal() {}
91
92 void ServiceWorkerRegisterJob::set_registration(
93     ServiceWorkerRegistration* registration) {
94   DCHECK(phase_ == START || phase_ == REGISTER) << phase_;
95   DCHECK(!internal_.registration);
96   internal_.registration = registration;
97 }
98
99 ServiceWorkerRegistration* ServiceWorkerRegisterJob::registration() {
100   DCHECK(phase_ >= REGISTER) << phase_;
101   return internal_.registration;
102 }
103
104 void ServiceWorkerRegisterJob::set_pending_version(
105     ServiceWorkerVersion* version) {
106   DCHECK(phase_ == UPDATE) << phase_;
107   DCHECK(!internal_.pending_version);
108   internal_.pending_version = version;
109 }
110
111 ServiceWorkerVersion* ServiceWorkerRegisterJob::pending_version() {
112   DCHECK(phase_ >= UPDATE) << phase_;
113   return internal_.pending_version;
114 }
115
116 void ServiceWorkerRegisterJob::SetPhase(Phase phase) {
117   switch (phase) {
118     case INITIAL:
119       NOTREACHED();
120       break;
121     case START:
122       DCHECK(phase_ == INITIAL) << phase_;
123       break;
124     case REGISTER:
125       DCHECK(phase_ == START) << phase_;
126       break;
127     case UPDATE:
128       DCHECK(phase_ == START || phase_ == REGISTER) << phase_;
129       break;
130     case INSTALL:
131       DCHECK(phase_ == UPDATE) << phase_;
132       break;
133     case STORE:
134       DCHECK(phase_ == INSTALL) << phase_;
135       break;
136     case ACTIVATE:
137       DCHECK(phase_ == STORE) << phase_;
138       break;
139     case COMPLETE:
140       DCHECK(phase_ != INITIAL && phase_ != COMPLETE) << phase_;
141       break;
142     case ABORT:
143       break;
144   }
145   phase_ = phase;
146 }
147
148 // This function corresponds to the steps in Register following
149 // "Let serviceWorkerRegistration be _GetRegistration(scope)"
150 // |existing_registration| corresponds to serviceWorkerRegistration.
151 // Throughout this file, comments in quotes are excerpts from the spec.
152 void ServiceWorkerRegisterJob::HandleExistingRegistrationAndContinue(
153     ServiceWorkerStatusCode status,
154     const scoped_refptr<ServiceWorkerRegistration>& existing_registration) {
155   // On unexpected error, abort this registration job.
156   if (status != SERVICE_WORKER_ERROR_NOT_FOUND && status != SERVICE_WORKER_OK) {
157     Complete(status);
158     return;
159   }
160
161   // "If serviceWorkerRegistration is not null and script is equal to
162   // serviceWorkerRegistration.scriptUrl..." resolve with the existing
163   // registration and abort.
164   if (existing_registration.get() &&
165       existing_registration->script_url() == script_url_) {
166     set_registration(existing_registration);
167     // If there's no active version, go ahead to Update (this isn't in the spec
168     // but seems reasonable, and without SoftUpdate implemented we can never
169     // Update otherwise).
170     if (!existing_registration->active_version()) {
171       UpdateAndContinue(status);
172       return;
173     }
174     ResolvePromise(
175         status, existing_registration, existing_registration->active_version());
176     Complete(SERVICE_WORKER_OK);
177     return;
178   }
179
180   // "If serviceWorkerRegistration is null..." create a new registration.
181   if (!existing_registration.get()) {
182     RegisterAndContinue(SERVICE_WORKER_OK);
183     return;
184   }
185
186   // On script URL mismatch, "set serviceWorkerRegistration.scriptUrl to
187   // script." We accomplish this by deleting the existing registration and
188   // registering a new one.
189   // TODO(falken): Match the spec. We now throw away the active_version_ and
190   // waiting_version_ of the existing registration, which isn't in the spec.
191   // TODO(michaeln): Deactivate the live existing_registration object and
192   // eventually call storage->DeleteVersionResources()
193   // when it no longer has any controllees.
194   context_->storage()->DeleteRegistration(
195       existing_registration->id(),
196       existing_registration->script_url().GetOrigin(),
197       base::Bind(&ServiceWorkerRegisterJob::RegisterAndContinue,
198                  weak_factory_.GetWeakPtr()));
199 }
200
201 // Creates a new ServiceWorkerRegistration.
202 void ServiceWorkerRegisterJob::RegisterAndContinue(
203     ServiceWorkerStatusCode status) {
204   SetPhase(REGISTER);
205   if (status != SERVICE_WORKER_OK) {
206     // Abort this registration job.
207     Complete(status);
208     return;
209   }
210
211   set_registration(new ServiceWorkerRegistration(
212       pattern_, script_url_, context_->storage()->NewRegistrationId(),
213       context_));
214   context_->storage()->NotifyInstallingRegistration(registration());
215   UpdateAndContinue(SERVICE_WORKER_OK);
216 }
217
218 // This function corresponds to the spec's _Update algorithm.
219 void ServiceWorkerRegisterJob::UpdateAndContinue(
220     ServiceWorkerStatusCode status) {
221   SetPhase(UPDATE);
222   if (status != SERVICE_WORKER_OK) {
223     // Abort this registration job.
224     Complete(status);
225     return;
226   }
227
228   // TODO(falken): "If serviceWorkerRegistration.installingWorker is not null.."
229   // then terminate the installing worker. It doesn't make sense to implement
230   // yet since we always activate the worker if install completed, so there can
231   // be no installing worker at this point.
232   // TODO(nhiroki): Check 'installing_version()' instead when it's supported.
233   DCHECK(!registration()->waiting_version());
234
235   // "Let serviceWorker be a newly-created ServiceWorker object..." and start
236   // the worker.
237   set_pending_version(new ServiceWorkerVersion(
238       registration(), context_->storage()->NewVersionId(), context_));
239
240   // TODO(michaeln): Start the worker into a paused state where the
241   // script resource is downloaded but not yet evaluated.
242   pending_version()->StartWorkerWithCandidateProcesses(
243       pending_process_ids_,
244       base::Bind(&ServiceWorkerRegisterJob::OnStartWorkerFinished,
245                  weak_factory_.GetWeakPtr()));
246 }
247
248 void ServiceWorkerRegisterJob::OnStartWorkerFinished(
249     ServiceWorkerStatusCode status) {
250   // "If serviceWorker fails to start up..." then reject the promise with an
251   // error and abort.
252   if (status != SERVICE_WORKER_OK) {
253     Complete(status);
254     return;
255   }
256
257   // TODO(michaeln): Compare the old and new script.
258   // If different unpause the worker and continue with
259   // the job. If the same ResolvePromise with the current
260   // version and complete the job, throwing away the new version
261   // since there's nothing new.
262
263   // "Resolve promise with serviceWorker."
264   // Although the spec doesn't set waitingWorker until after resolving the
265   // promise, our system's resolving works by passing ServiceWorkerRegistration
266   // to the callbacks, so waitingWorker must be set first.
267   DCHECK(!registration()->waiting_version());
268   registration()->set_waiting_version(pending_version());
269   ResolvePromise(status, registration(), pending_version());
270
271   AssociateWaitingVersionToDocuments(context_, pending_version());
272
273   InstallAndContinue();
274 }
275
276 // This function corresponds to the spec's _Install algorithm.
277 void ServiceWorkerRegisterJob::InstallAndContinue() {
278   SetPhase(INSTALL);
279   // "Set serviceWorkerRegistration.installingWorker._state to installing."
280   // "Fire install event on the associated ServiceWorkerGlobalScope object."
281   pending_version()->DispatchInstallEvent(
282       -1,
283       base::Bind(&ServiceWorkerRegisterJob::OnInstallFinished,
284                  weak_factory_.GetWeakPtr()));
285 }
286
287 void ServiceWorkerRegisterJob::OnInstallFinished(
288     ServiceWorkerStatusCode status) {
289   // "If any handler called waitUntil()..." and the resulting promise
290   // is rejected, abort.
291   // TODO(kinuko,falken): For some error cases (e.g. ServiceWorker is
292   // unexpectedly terminated) we may want to retry sending the event again.
293   if (status != SERVICE_WORKER_OK) {
294     Complete(status);
295     return;
296   }
297
298   SetPhase(STORE);
299   context_->storage()->StoreRegistration(
300       registration(),
301       pending_version(),
302       base::Bind(&ServiceWorkerRegisterJob::OnStoreRegistrationComplete,
303                  weak_factory_.GetWeakPtr()));
304 }
305
306 void ServiceWorkerRegisterJob::OnStoreRegistrationComplete(
307     ServiceWorkerStatusCode status) {
308   if (status != SERVICE_WORKER_OK) {
309     Complete(status);
310     return;
311   }
312
313   ActivateAndContinue();
314 }
315
316 // This function corresponds to the spec's _Activate algorithm.
317 void ServiceWorkerRegisterJob::ActivateAndContinue() {
318   SetPhase(ACTIVATE);
319
320   // "If existingWorker is not null, then: wait for exitingWorker to finish
321   // handling any in-progress requests."
322   // See if we already have an active_version for the scope and it has
323   // controllee documents (if so activating the new version should wait
324   // until we have no documents controlled by the version).
325   if (registration()->active_version() &&
326       registration()->active_version()->HasControllee()) {
327     // TODO(kinuko,falken): Currently we immediately return if the existing
328     // registration already has an active version, so we shouldn't come
329     // this way.
330     NOTREACHED();
331     // TODO(falken): Register an continuation task to wait for NoControllees
332     // notification so that we can resume activation later (see comments
333     // in ServiceWorkerVersion::RemoveControllee).
334     Complete(SERVICE_WORKER_OK);
335     return;
336   }
337
338   // "Set serviceWorkerRegistration.waitingWorker to null."
339   // "Set serviceWorkerRegistration.activeWorker to activatingWorker."
340   DisassociateWaitingVersionFromDocuments(
341       context_, pending_version()->version_id());
342   registration()->set_waiting_version(NULL);
343   DCHECK(!registration()->active_version());
344   registration()->set_active_version(pending_version());
345
346   // "Set serviceWorkerRegistration.activeWorker._state to activating."
347   // "Fire activate event on the associated ServiceWorkerGlobalScope object."
348   // "Set serviceWorkerRegistration.activeWorker._state to active."
349   pending_version()->DispatchActivateEvent(
350       base::Bind(&ServiceWorkerRegisterJob::OnActivateFinished,
351                  weak_factory_.GetWeakPtr()));
352 }
353
354 void ServiceWorkerRegisterJob::OnActivateFinished(
355     ServiceWorkerStatusCode status) {
356   // "If any handler called waitUntil()..." and the resulting promise
357   // is rejected, abort.
358   // TODO(kinuko,falken): For some error cases (e.g. ServiceWorker is
359   // unexpectedly terminated) we may want to retry sending the event again.
360   if (status != SERVICE_WORKER_OK) {
361     registration()->set_active_version(NULL);
362     Complete(status);
363     return;
364   }
365   context_->storage()->UpdateToActiveState(
366       registration(),
367       base::Bind(&ServiceWorkerUtils::NoOpStatusCallback));
368   Complete(SERVICE_WORKER_OK);
369 }
370
371 void ServiceWorkerRegisterJob::Complete(ServiceWorkerStatusCode status) {
372   CompleteInternal(status);
373   context_->job_coordinator()->FinishJob(pattern_, this);
374 }
375
376 void ServiceWorkerRegisterJob::CompleteInternal(
377     ServiceWorkerStatusCode status) {
378   SetPhase(COMPLETE);
379   if (status != SERVICE_WORKER_OK) {
380     if (registration() && registration()->waiting_version()) {
381       DisassociateWaitingVersionFromDocuments(
382           context_, registration()->waiting_version()->version_id());
383       registration()->set_waiting_version(NULL);
384     }
385     if (registration() && !registration()->active_version()) {
386       context_->storage()->DeleteRegistration(
387           registration()->id(),
388           registration()->script_url().GetOrigin(),
389           base::Bind(&ServiceWorkerUtils::NoOpStatusCallback));
390     }
391     if (!is_promise_resolved_)
392       ResolvePromise(status, NULL, NULL);
393   }
394   DCHECK(callbacks_.empty());
395   if (registration()) {
396     context_->storage()->NotifyDoneInstallingRegistration(
397         registration(), pending_version(), status);
398   }
399 }
400
401 void ServiceWorkerRegisterJob::ResolvePromise(
402     ServiceWorkerStatusCode status,
403     ServiceWorkerRegistration* registration,
404     ServiceWorkerVersion* version) {
405   DCHECK(!is_promise_resolved_);
406   is_promise_resolved_ = true;
407   promise_resolved_status_ = status;
408   promise_resolved_registration_ = registration;
409   promise_resolved_version_ = version;
410   for (std::vector<RegistrationCallback>::iterator it = callbacks_.begin();
411        it != callbacks_.end();
412        ++it) {
413     it->Run(status, registration, version);
414   }
415   callbacks_.clear();
416 }
417
418 // static
419 void ServiceWorkerRegisterJob::AssociateWaitingVersionToDocuments(
420     base::WeakPtr<ServiceWorkerContextCore> context,
421     ServiceWorkerVersion* version) {
422   DCHECK(context);
423   DCHECK(version);
424
425   for (scoped_ptr<ServiceWorkerContextCore::ProviderHostIterator> it =
426            context->GetProviderHostIterator();
427        !it->IsAtEnd();
428        it->Advance()) {
429     ServiceWorkerProviderHost* host = it->GetProviderHost();
430     if (!host->IsContextAlive())
431       continue;
432     if (ServiceWorkerUtils::ScopeMatches(version->scope(),
433                                          host->document_url())) {
434       // The spec's _Update algorithm says, "upgrades active version to a new
435       // version for the same URL scope.", so skip if the scope (registration)
436       // of |version| is different from that of the current active/waiting
437       // version.
438       if (!host->ValidateVersionForAssociation(version))
439         continue;
440
441       // TODO(nhiroki): Keep |host->waiting_version()| to be replaced and set
442       // status of them to 'redandunt' after breaking the loop.
443
444       host->SetWaitingVersion(version);
445       // TODO(nhiroki): Set |host|'s installing version to null.
446     }
447   }
448 }
449
450 // static
451 void ServiceWorkerRegisterJob::DisassociateWaitingVersionFromDocuments(
452     base::WeakPtr<ServiceWorkerContextCore> context,
453     int64 version_id) {
454   DCHECK(context);
455   for (scoped_ptr<ServiceWorkerContextCore::ProviderHostIterator> it =
456            context->GetProviderHostIterator();
457        !it->IsAtEnd();
458        it->Advance()) {
459     ServiceWorkerProviderHost* host = it->GetProviderHost();
460     if (!host->IsContextAlive())
461       continue;
462     if (host->waiting_version() &&
463         host->waiting_version()->version_id() == version_id) {
464       host->SetWaitingVersion(NULL);
465     }
466   }
467 }
468
469 }  // namespace content