Upstream version 9.38.198.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 }  // namespace
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       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) {}
40
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;
53 }
54
55 ServiceWorkerRegisterJob::~ServiceWorkerRegisterJob() {
56   DCHECK(!context_ ||
57          phase_ == INITIAL || phase_ == COMPLETE || phase_ == ABORT)
58       << "Jobs should only be interrupted during shutdown.";
59 }
60
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;
68   }
69   RunSoon(base::Bind(
70       callback, promise_resolved_status_,
71       promise_resolved_registration_, promise_resolved_version_));
72 }
73
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());
85   }
86
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);
93 }
94
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.
100 }
101
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_;
109 }
110
111 RegistrationJobType ServiceWorkerRegisterJob::GetType() {
112   return job_type_;
113 }
114
115 ServiceWorkerRegisterJob::Internal::Internal() {}
116
117 ServiceWorkerRegisterJob::Internal::~Internal() {}
118
119 void ServiceWorkerRegisterJob::set_registration(
120     ServiceWorkerRegistration* registration) {
121   DCHECK(phase_ == START || phase_ == REGISTER) << phase_;
122   DCHECK(!internal_.registration);
123   internal_.registration = registration;
124 }
125
126 ServiceWorkerRegistration* ServiceWorkerRegisterJob::registration() {
127   DCHECK(phase_ >= REGISTER || job_type_ == UPDATE_JOB) << phase_;
128   return internal_.registration;
129 }
130
131 void ServiceWorkerRegisterJob::set_new_version(
132     ServiceWorkerVersion* version) {
133   DCHECK(phase_ == UPDATE) << phase_;
134   DCHECK(!internal_.new_version);
135   internal_.new_version = version;
136 }
137
138 ServiceWorkerVersion* ServiceWorkerRegisterJob::new_version() {
139   DCHECK(phase_ >= UPDATE) << phase_;
140   return internal_.new_version;
141 }
142
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;
168   }
169   phase_ = phase;
170 }
171
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;
182   }
183
184   if (!existing_registration) {
185     RegisterAndContinue(SERVICE_WORKER_OK);
186     return;
187   }
188
189   // "Set registration.[[Uninstalling]] to false."
190   existing_registration->AbortPendingClear();
191
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);
197
198     if (!existing_registration->active_version()) {
199       UpdateAndContinue();
200       return;
201     }
202
203     ResolvePromise(
204         status, existing_registration, existing_registration->active_version());
205     Complete(SERVICE_WORKER_OK);
206     return;
207   }
208
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()));
219 }
220
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;
228   }
229
230   if (existing_registration != registration()) {
231     Complete(SERVICE_WORKER_ERROR_NOT_FOUND);
232     return;
233   }
234
235   // TODO(michaeln): If the last update check was less than 24 hours
236   // ago, depending on the freshness of the cached worker script we
237   // may be able to complete the update job right here.
238
239   UpdateAndContinue();
240 }
241
242 // Creates a new ServiceWorkerRegistration.
243 void ServiceWorkerRegisterJob::RegisterAndContinue(
244     ServiceWorkerStatusCode status) {
245   SetPhase(REGISTER);
246   if (status != SERVICE_WORKER_OK) {
247     // Abort this registration job.
248     Complete(status);
249     return;
250   }
251
252   set_registration(new ServiceWorkerRegistration(
253       pattern_, script_url_, context_->storage()->NewRegistrationId(),
254       context_));
255   AssociateProviderHostsToRegistration(registration());
256   UpdateAndContinue();
257 }
258
259 // This function corresponds to the spec's [[Update]] algorithm.
260 void ServiceWorkerRegisterJob::UpdateAndContinue() {
261   SetPhase(UPDATE);
262   context_->storage()->NotifyInstallingRegistration(registration());
263
264   // TODO(falken): "If serviceWorkerRegistration.installingWorker is not null.."
265   // then terminate the installing worker. It doesn't make sense to implement
266   // yet since we always activate the worker if install completed, so there can
267   // be no installing worker at this point.
268
269   // "Let serviceWorker be a newly-created ServiceWorker object..." and start
270   // the worker.
271   set_new_version(new ServiceWorkerVersion(
272       registration(), context_->storage()->NewVersionId(), context_));
273
274   bool pause_after_download = job_type_ == UPDATE_JOB;
275   if (pause_after_download)
276     new_version()->embedded_worker()->AddListener(this);
277   new_version()->StartWorkerWithCandidateProcesses(
278       pending_process_ids_,
279       pause_after_download,
280       base::Bind(&ServiceWorkerRegisterJob::OnStartWorkerFinished,
281                  weak_factory_.GetWeakPtr()));
282 }
283
284 void ServiceWorkerRegisterJob::OnStartWorkerFinished(
285     ServiceWorkerStatusCode status) {
286   // "If serviceWorker fails to start up..." then reject the promise with an
287   // error and abort.
288   if (status != SERVICE_WORKER_OK) {
289     Complete(status);
290     return;
291   }
292
293   // "Resolve promise with serviceWorker."
294   DCHECK(!registration()->installing_version());
295   ResolvePromise(status, registration(), new_version());
296   InstallAndContinue();
297 }
298
299 // This function corresponds to the spec's _Install algorithm.
300 void ServiceWorkerRegisterJob::InstallAndContinue() {
301   SetPhase(INSTALL);
302
303   // "3. Set registration.installingWorker to worker."
304   registration()->SetInstallingVersion(new_version());
305
306   // "4. Run the [[UpdateState]] algorithm passing registration.installingWorker
307   // and "installing" as the arguments."
308   new_version()->SetStatus(ServiceWorkerVersion::INSTALLING);
309
310   // TODO(nhiroki,michaeln): "5. Fire a simple event named updatefound..."
311
312   // "6. Fire an event named install..."
313   new_version()->DispatchInstallEvent(
314       -1,
315       base::Bind(&ServiceWorkerRegisterJob::OnInstallFinished,
316                  weak_factory_.GetWeakPtr()));
317 }
318
319 void ServiceWorkerRegisterJob::OnInstallFinished(
320     ServiceWorkerStatusCode status) {
321   // TODO(kinuko,falken): For some error cases (e.g. ServiceWorker is
322   // unexpectedly terminated) we may want to retry sending the event again.
323   if (status != SERVICE_WORKER_OK) {
324     // "8. If installFailed is true, then:..."
325     Complete(status);
326     return;
327   }
328
329   SetPhase(STORE);
330   registration()->set_last_update_check(base::Time::Now());
331   context_->storage()->StoreRegistration(
332       registration(),
333       new_version(),
334       base::Bind(&ServiceWorkerRegisterJob::OnStoreRegistrationComplete,
335                  weak_factory_.GetWeakPtr()));
336 }
337
338 void ServiceWorkerRegisterJob::OnStoreRegistrationComplete(
339     ServiceWorkerStatusCode status) {
340   if (status != SERVICE_WORKER_OK) {
341     Complete(status);
342     return;
343   }
344
345   // "9. If registration.waitingWorker is not null, then:..."
346   if (registration()->waiting_version()) {
347     // "1. Run the [[UpdateState]] algorithm passing registration.waitingWorker
348     // and "redundant" as the arguments."
349     registration()->waiting_version()->SetStatus(
350         ServiceWorkerVersion::REDUNDANT);
351   }
352
353   // "10. Set registration.waitingWorker to registration.installingWorker."
354   // "11. Set registration.installingWorker to null."
355   registration()->SetWaitingVersion(new_version());
356
357   // "12. Run the [[UpdateState]] algorithm passing registration.waitingWorker
358   // and "installed" as the arguments."
359   new_version()->SetStatus(ServiceWorkerVersion::INSTALLED);
360
361   // TODO(michaeln): "13. If activateImmediate is true, then..."
362
363   // "14. Wait until no document is using registration as their
364   // Service Worker registration."
365   registration()->ActivateWaitingVersionWhenReady();
366
367   Complete(SERVICE_WORKER_OK);
368 }
369
370 void ServiceWorkerRegisterJob::Complete(ServiceWorkerStatusCode status) {
371   CompleteInternal(status);
372   context_->job_coordinator()->FinishJob(pattern_, this);
373 }
374
375 void ServiceWorkerRegisterJob::CompleteInternal(
376     ServiceWorkerStatusCode status) {
377   SetPhase(COMPLETE);
378   if (status != SERVICE_WORKER_OK) {
379     if (registration()) {
380       if (new_version()) {
381         registration()->UnsetVersion(new_version());
382         new_version()->Doom();
383       }
384       if (!registration()->waiting_version() &&
385           !registration()->active_version()) {
386         registration()->NotifyRegistrationFailed();
387         context_->storage()->DeleteRegistration(
388             registration()->id(),
389             registration()->script_url().GetOrigin(),
390             base::Bind(&ServiceWorkerUtils::NoOpStatusCallback));
391       }
392     }
393     if (!is_promise_resolved_)
394       ResolvePromise(status, NULL, NULL);
395   }
396   DCHECK(callbacks_.empty());
397   if (registration()) {
398     context_->storage()->NotifyDoneInstallingRegistration(
399         registration(), new_version(), status);
400   }
401   if (new_version())
402     new_version()->embedded_worker()->RemoveListener(this);
403 }
404
405 void ServiceWorkerRegisterJob::ResolvePromise(
406     ServiceWorkerStatusCode status,
407     ServiceWorkerRegistration* registration,
408     ServiceWorkerVersion* version) {
409   DCHECK(!is_promise_resolved_);
410   is_promise_resolved_ = true;
411   promise_resolved_status_ = status;
412   promise_resolved_registration_ = registration;
413   promise_resolved_version_ = version;
414   for (std::vector<RegistrationCallback>::iterator it = callbacks_.begin();
415        it != callbacks_.end();
416        ++it) {
417     it->Run(status, registration, version);
418   }
419   callbacks_.clear();
420 }
421
422 void ServiceWorkerRegisterJob::OnPausedAfterDownload() {
423   // This happens prior to OnStartWorkerFinished time.
424   scoped_refptr<ServiceWorkerVersion> most_recent_version =
425       registration()->waiting_version() ?
426           registration()->waiting_version() :
427           registration()->active_version();
428   DCHECK(most_recent_version);
429   int64 most_recent_script_id =
430       most_recent_version->script_cache_map()->Lookup(script_url_);
431   int64 new_script_id =
432       new_version()->script_cache_map()->Lookup(script_url_);
433
434   // TODO(michaeln): It would be better to compare as the new resource
435   // is being downloaded and to avoid writing it to disk until we know
436   // its needed.
437   context_->storage()->CompareScriptResources(
438       most_recent_script_id, new_script_id,
439       base::Bind(&ServiceWorkerRegisterJob::OnCompareScriptResourcesComplete,
440                  weak_factory_.GetWeakPtr(),
441                  most_recent_version));
442 }
443
444 bool ServiceWorkerRegisterJob::OnMessageReceived(const IPC::Message& message) {
445   return false;
446 }
447
448 void ServiceWorkerRegisterJob::OnCompareScriptResourcesComplete(
449     ServiceWorkerVersion* most_recent_version,
450     ServiceWorkerStatusCode status,
451     bool are_equal) {
452   if (are_equal) {
453     // Only bump the last check time when we've bypassed the browser cache.
454     base::TimeDelta time_since_last_check =
455         base::Time::Now() - registration()->last_update_check();
456     if (time_since_last_check > base::TimeDelta::FromHours(24)) {
457       registration()->set_last_update_check(base::Time::Now());
458       context_->storage()->UpdateLastUpdateCheckTime(registration());
459     }
460
461     ResolvePromise(SERVICE_WORKER_OK, registration(), most_recent_version);
462     Complete(SERVICE_WORKER_ERROR_EXISTS);
463     return;
464   }
465
466   // Proceed with really starting the worker.
467   new_version()->embedded_worker()->ResumeAfterDownload();
468   new_version()->embedded_worker()->RemoveListener(this);
469 }
470
471 void ServiceWorkerRegisterJob::AssociateProviderHostsToRegistration(
472     ServiceWorkerRegistration* registration) {
473   DCHECK(registration);
474   for (scoped_ptr<ServiceWorkerContextCore::ProviderHostIterator> it =
475            context_->GetProviderHostIterator();
476        !it->IsAtEnd(); it->Advance()) {
477     ServiceWorkerProviderHost* host = it->GetProviderHost();
478     if (ServiceWorkerUtils::ScopeMatches(registration->pattern(),
479                                          host->document_url())) {
480       if (host->CanAssociateRegistration(registration))
481         host->AssociateRegistration(registration);
482     }
483   }
484 }
485
486 }  // namespace content