Upstream version 7.36.149.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(phase_ == INITIAL || phase_ == COMPLETE);
42 }
43
44 void ServiceWorkerRegisterJob::AddCallback(const RegistrationCallback& callback,
45                                            int process_id) {
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);
50     return;
51   }
52   RunSoon(base::Bind(
53       callback, promise_resolved_status_,
54       promise_resolved_registration_, promise_resolved_version_));
55 }
56
57 void ServiceWorkerRegisterJob::Start() {
58   SetPhase(START);
59   context_->storage()->FindRegistrationForPattern(
60       pattern_,
61       base::Bind(
62           &ServiceWorkerRegisterJob::HandleExistingRegistrationAndContinue,
63           weak_factory_.GetWeakPtr()));
64 }
65
66 bool ServiceWorkerRegisterJob::Equals(ServiceWorkerRegisterJobBase* job) {
67   if (job->GetType() != GetType())
68     return false;
69   ServiceWorkerRegisterJob* register_job =
70       static_cast<ServiceWorkerRegisterJob*>(job);
71   return register_job->pattern_ == pattern_ &&
72          register_job->script_url_ == script_url_;
73 }
74
75 RegistrationJobType ServiceWorkerRegisterJob::GetType() {
76   return REGISTRATION;
77 }
78
79 ServiceWorkerRegisterJob::Internal::Internal() {}
80
81 ServiceWorkerRegisterJob::Internal::~Internal() {}
82
83 void ServiceWorkerRegisterJob::set_registration(
84     ServiceWorkerRegistration* registration) {
85   DCHECK(phase_ == START || phase_ == REGISTER) << phase_;
86   DCHECK(!internal_.registration);
87   internal_.registration = registration;
88 }
89
90 ServiceWorkerRegistration* ServiceWorkerRegisterJob::registration() {
91   DCHECK(phase_ >= REGISTER) << phase_;
92   DCHECK(internal_.registration);
93   return internal_.registration;
94 }
95
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;
101 }
102
103 ServiceWorkerVersion* ServiceWorkerRegisterJob::pending_version() {
104   DCHECK(phase_ >= UPDATE) << phase_;
105   return internal_.pending_version;
106 }
107
108 void ServiceWorkerRegisterJob::SetPhase(Phase phase) {
109   switch (phase) {
110     case INITIAL:
111       NOTREACHED();
112       break;
113     case START:
114       DCHECK(phase_ == INITIAL) << phase_;
115       break;
116     case REGISTER:
117       DCHECK(phase_ == START) << phase_;
118       break;
119     case UPDATE:
120       DCHECK(phase_ == START || phase_ == REGISTER) << phase_;
121       break;
122     case INSTALL:
123       DCHECK(phase_ == UPDATE) << phase_;
124       break;
125     case STORE:
126       DCHECK(phase_ == INSTALL) << phase_;
127       break;
128     case ACTIVATE:
129       DCHECK(phase_ == STORE) << phase_;
130       break;
131     case COMPLETE:
132       DCHECK(phase_ != INITIAL && phase_ != COMPLETE) << phase_;
133       break;
134   }
135   phase_ = phase;
136 }
137
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) {
147     Complete(status);
148     return;
149   }
150
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);
162       return;
163     }
164     ResolvePromise(
165         status, existing_registration, existing_registration->active_version());
166     Complete(SERVICE_WORKER_OK);
167     return;
168   }
169
170   // "If serviceWorkerRegistration is null..." create a new registration.
171   if (!existing_registration.get()) {
172     RegisterAndContinue(SERVICE_WORKER_OK);
173     return;
174   }
175
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()));
189 }
190
191 // Creates a new ServiceWorkerRegistration.
192 void ServiceWorkerRegisterJob::RegisterAndContinue(
193     ServiceWorkerStatusCode status) {
194   SetPhase(REGISTER);
195   if (status != SERVICE_WORKER_OK) {
196     // Abort this registration job.
197     Complete(status);
198     return;
199   }
200
201   set_registration(new ServiceWorkerRegistration(
202       pattern_, script_url_, context_->storage()->NewRegistrationId(),
203       context_));
204   context_->storage()->NotifyInstallingRegistration(registration());
205   UpdateAndContinue(SERVICE_WORKER_OK);
206 }
207
208 // This function corresponds to the spec's _Update algorithm.
209 void ServiceWorkerRegisterJob::UpdateAndContinue(
210     ServiceWorkerStatusCode status) {
211   SetPhase(UPDATE);
212   if (status != SERVICE_WORKER_OK) {
213     // Abort this registration job.
214     Complete(status);
215     return;
216   }
217
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());
223
224   // "Let serviceWorker be a newly-created ServiceWorker object..." and start
225   // the worker.
226   set_pending_version(new ServiceWorkerVersion(
227       registration(), context_->storage()->NewVersionId(), context_));
228
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()));
235 }
236
237 void ServiceWorkerRegisterJob::OnStartWorkerFinished(
238     ServiceWorkerStatusCode status) {
239   // "If serviceWorker fails to start up..." then reject the promise with an
240   // error and abort.
241   if (status != SERVICE_WORKER_OK) {
242     Complete(status);
243     return;
244   }
245
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.
251
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());
259
260   AssociatePendingVersionToDocuments(pending_version());
261
262   InstallAndContinue();
263 }
264
265 // This function corresponds to the spec's _Install algorithm.
266 void ServiceWorkerRegisterJob::InstallAndContinue() {
267   SetPhase(INSTALL);
268   // "Set serviceWorkerRegistration.pendingWorker._state to installing."
269   // "Fire install event on the associated ServiceWorkerGlobalScope object."
270   pending_version()->DispatchInstallEvent(
271       -1,
272       base::Bind(&ServiceWorkerRegisterJob::OnInstallFinished,
273                  weak_factory_.GetWeakPtr()));
274 }
275
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) {
283     Complete(status);
284     return;
285   }
286
287   SetPhase(STORE);
288   context_->storage()->StoreRegistration(
289       registration(),
290       pending_version(),
291       base::Bind(&ServiceWorkerRegisterJob::OnStoreRegistrationComplete,
292                  weak_factory_.GetWeakPtr()));
293 }
294
295 void ServiceWorkerRegisterJob::OnStoreRegistrationComplete(
296     ServiceWorkerStatusCode status) {
297   if (status != SERVICE_WORKER_OK) {
298     Complete(status);
299     return;
300   }
301
302   ActivateAndContinue();
303 }
304
305 // This function corresponds to the spec's _Activate algorithm.
306 void ServiceWorkerRegisterJob::ActivateAndContinue() {
307   SetPhase(ACTIVATE);
308
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
318     // this way.
319     NOTREACHED();
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);
324     return;
325   }
326
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());
333
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()));
340 }
341
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);
350     Complete(status);
351     return;
352   }
353   context_->storage()->UpdateToActiveState(
354       registration(),
355       base::Bind(&ServiceWorkerUtils::NoOpStatusCallback));
356   Complete(SERVICE_WORKER_OK);
357 }
358
359 void ServiceWorkerRegisterJob::Complete(ServiceWorkerStatusCode status) {
360   SetPhase(COMPLETE);
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.
367     }
368     if (registration() && !registration()->active_version()) {
369       context_->storage()->DeleteRegistration(
370           registration()->id(),
371           registration()->script_url().GetOrigin(),
372           base::Bind(&ServiceWorkerUtils::NoOpStatusCallback));
373     }
374     if (!is_promise_resolved_)
375       ResolvePromise(status, NULL, NULL);
376   }
377   DCHECK(callbacks_.empty());
378   context_->storage()->NotifyDoneInstallingRegistration(registration());
379   context_->job_coordinator()->FinishJob(pattern_, this);
380 }
381
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();
393        ++it) {
394     it->Run(status, registration, version);
395   }
396   callbacks_.clear();
397 }
398
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();
406        !it->IsAtEnd();
407        it->Advance()) {
408     ServiceWorkerProviderHost* provider_host = it->GetProviderHost();
409     if (ServiceWorkerUtils::ScopeMatches(pattern_,
410                                          provider_host->document_url()))
411       provider_host->SetPendingVersion(version);
412   }
413 }
414
415 }  // namespace content