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#include "net/base/net_errors.h"
16
17namespace content {
18
19namespace {
20
21void RunSoon(const base::Closure& closure) {
22  base::MessageLoop::current()->PostTask(FROM_HERE, closure);
23}
24
25}  // namespace
26
27typedef ServiceWorkerRegisterJobBase::RegistrationJobType RegistrationJobType;
28
29ServiceWorkerRegisterJob::ServiceWorkerRegisterJob(
30    base::WeakPtr<ServiceWorkerContextCore> context,
31    const GURL& pattern,
32    const GURL& script_url)
33    : context_(context),
34      job_type_(REGISTRATION_JOB),
35      pattern_(pattern),
36      script_url_(script_url),
37      phase_(INITIAL),
38      is_promise_resolved_(false),
39      promise_resolved_status_(SERVICE_WORKER_OK),
40      weak_factory_(this) {}
41
42ServiceWorkerRegisterJob::ServiceWorkerRegisterJob(
43    base::WeakPtr<ServiceWorkerContextCore> context,
44    ServiceWorkerRegistration* registration)
45    : context_(context),
46      job_type_(UPDATE_JOB),
47      pattern_(registration->pattern()),
48      script_url_(registration->GetNewestVersion()->script_url()),
49      phase_(INITIAL),
50      is_promise_resolved_(false),
51      promise_resolved_status_(SERVICE_WORKER_OK),
52      weak_factory_(this) {
53  internal_.registration = registration;
54}
55
56ServiceWorkerRegisterJob::~ServiceWorkerRegisterJob() {
57  DCHECK(!context_ ||
58         phase_ == INITIAL || phase_ == COMPLETE || phase_ == ABORT)
59      << "Jobs should only be interrupted during shutdown.";
60}
61
62void ServiceWorkerRegisterJob::AddCallback(
63    const RegistrationCallback& callback,
64    ServiceWorkerProviderHost* provider_host) {
65  if (!is_promise_resolved_) {
66    callbacks_.push_back(callback);
67    if (provider_host)
68      provider_host->AddScopedProcessReferenceToPattern(pattern_);
69    return;
70  }
71  RunSoon(base::Bind(
72      callback, promise_resolved_status_,
73      promise_resolved_registration_, promise_resolved_version_));
74}
75
76void ServiceWorkerRegisterJob::Start() {
77  SetPhase(START);
78  ServiceWorkerStorage::FindRegistrationCallback next_step;
79  if (job_type_ == REGISTRATION_JOB) {
80    next_step = base::Bind(
81        &ServiceWorkerRegisterJob::ContinueWithRegistration,
82        weak_factory_.GetWeakPtr());
83  } else {
84    next_step = base::Bind(
85        &ServiceWorkerRegisterJob::ContinueWithUpdate,
86        weak_factory_.GetWeakPtr());
87  }
88
89  scoped_refptr<ServiceWorkerRegistration> registration =
90      context_->storage()->GetUninstallingRegistration(pattern_);
91  if (registration.get())
92    RunSoon(base::Bind(next_step, SERVICE_WORKER_OK, registration));
93  else
94    context_->storage()->FindRegistrationForPattern(pattern_, next_step);
95}
96
97void ServiceWorkerRegisterJob::Abort() {
98  SetPhase(ABORT);
99  CompleteInternal(SERVICE_WORKER_ERROR_ABORT);
100  // Don't have to call FinishJob() because the caller takes care of removing
101  // the jobs from the queue.
102}
103
104bool ServiceWorkerRegisterJob::Equals(ServiceWorkerRegisterJobBase* job) {
105  if (job->GetType() != GetType())
106    return false;
107  ServiceWorkerRegisterJob* register_job =
108      static_cast<ServiceWorkerRegisterJob*>(job);
109  return register_job->pattern_ == pattern_ &&
110         register_job->script_url_ == script_url_;
111}
112
113RegistrationJobType ServiceWorkerRegisterJob::GetType() {
114  return job_type_;
115}
116
117ServiceWorkerRegisterJob::Internal::Internal() {}
118
119ServiceWorkerRegisterJob::Internal::~Internal() {}
120
121void ServiceWorkerRegisterJob::set_registration(
122    const scoped_refptr<ServiceWorkerRegistration>& registration) {
123  DCHECK(phase_ == START || phase_ == REGISTER) << phase_;
124  DCHECK(!internal_.registration.get());
125  internal_.registration = registration;
126}
127
128ServiceWorkerRegistration* ServiceWorkerRegisterJob::registration() {
129  DCHECK(phase_ >= REGISTER || job_type_ == UPDATE_JOB) << phase_;
130  return internal_.registration.get();
131}
132
133void ServiceWorkerRegisterJob::set_new_version(
134    ServiceWorkerVersion* version) {
135  DCHECK(phase_ == UPDATE) << phase_;
136  DCHECK(!internal_.new_version.get());
137  internal_.new_version = version;
138}
139
140ServiceWorkerVersion* ServiceWorkerRegisterJob::new_version() {
141  DCHECK(phase_ >= UPDATE) << phase_;
142  return internal_.new_version.get();
143}
144
145void ServiceWorkerRegisterJob::set_uninstalling_registration(
146    const scoped_refptr<ServiceWorkerRegistration>& registration) {
147  DCHECK_EQ(phase_, WAIT_FOR_UNINSTALL);
148  internal_.uninstalling_registration = registration;
149}
150
151ServiceWorkerRegistration*
152ServiceWorkerRegisterJob::uninstalling_registration() {
153  DCHECK_EQ(phase_, WAIT_FOR_UNINSTALL);
154  return internal_.uninstalling_registration.get();
155}
156
157void ServiceWorkerRegisterJob::SetPhase(Phase phase) {
158  switch (phase) {
159    case INITIAL:
160      NOTREACHED();
161      break;
162    case START:
163      DCHECK(phase_ == INITIAL) << phase_;
164      break;
165    case WAIT_FOR_UNINSTALL:
166      DCHECK(phase_ == START) << phase_;
167      break;
168    case REGISTER:
169      DCHECK(phase_ == START || phase_ == WAIT_FOR_UNINSTALL) << phase_;
170      break;
171    case UPDATE:
172      DCHECK(phase_ == START || phase_ == REGISTER) << phase_;
173      break;
174    case INSTALL:
175      DCHECK(phase_ == UPDATE) << phase_;
176      break;
177    case STORE:
178      DCHECK(phase_ == INSTALL) << phase_;
179      break;
180    case COMPLETE:
181      DCHECK(phase_ != INITIAL && phase_ != COMPLETE) << phase_;
182      break;
183    case ABORT:
184      break;
185  }
186  phase_ = phase;
187}
188
189// This function corresponds to the steps in [[Register]] following
190// "Let registration be the result of running the [[GetRegistration]] algorithm.
191// Throughout this file, comments in quotes are excerpts from the spec.
192void ServiceWorkerRegisterJob::ContinueWithRegistration(
193    ServiceWorkerStatusCode status,
194    const scoped_refptr<ServiceWorkerRegistration>& existing_registration) {
195  DCHECK_EQ(REGISTRATION_JOB, job_type_);
196  if (status != SERVICE_WORKER_ERROR_NOT_FOUND && status != SERVICE_WORKER_OK) {
197    Complete(status);
198    return;
199  }
200
201  if (!existing_registration.get() || existing_registration->is_uninstalled()) {
202    RegisterAndContinue(SERVICE_WORKER_OK);
203    return;
204  }
205
206  DCHECK(existing_registration->GetNewestVersion());
207  // "If scriptURL is equal to registration.[[ScriptURL]], then:"
208  if (existing_registration->GetNewestVersion()->script_url() == script_url_) {
209    // "Set registration.[[Uninstalling]] to false."
210    existing_registration->AbortPendingClear(base::Bind(
211        &ServiceWorkerRegisterJob::ContinueWithRegistrationForSameScriptUrl,
212        weak_factory_.GetWeakPtr(),
213        existing_registration));
214    return;
215  }
216
217  if (existing_registration->is_uninstalling()) {
218    // "Wait until the Record {[[key]], [[value]]} entry of its
219    // [[ScopeToRegistrationMap]] where registation.scope matches entry.[[key]]
220    // is deleted."
221    WaitForUninstall(existing_registration);
222    return;
223  }
224
225  // "Set registration.[[Uninstalling]] to false."
226  DCHECK(!existing_registration->is_uninstalling());
227
228  // "Return the result of running the [[Update]] algorithm, or its equivalent,
229  // passing registration as the argument."
230  set_registration(existing_registration);
231  UpdateAndContinue();
232}
233
234void ServiceWorkerRegisterJob::ContinueWithUpdate(
235    ServiceWorkerStatusCode status,
236    const scoped_refptr<ServiceWorkerRegistration>& existing_registration) {
237  DCHECK_EQ(UPDATE_JOB, job_type_);
238  if (status != SERVICE_WORKER_OK) {
239    Complete(status);
240    return;
241  }
242
243  if (existing_registration.get() != registration()) {
244    Complete(SERVICE_WORKER_ERROR_NOT_FOUND);
245    return;
246  }
247
248  // A previous job may have unregistered or installed a new version to this
249  // registration.
250  if (registration()->is_uninstalling() ||
251      registration()->GetNewestVersion()->script_url() != script_url_) {
252    Complete(SERVICE_WORKER_ERROR_NOT_FOUND);
253    return;
254  }
255
256  // TODO(michaeln): If the last update check was less than 24 hours
257  // ago, depending on the freshness of the cached worker script we
258  // may be able to complete the update job right here.
259
260  UpdateAndContinue();
261}
262
263// Creates a new ServiceWorkerRegistration.
264void ServiceWorkerRegisterJob::RegisterAndContinue(
265    ServiceWorkerStatusCode status) {
266  SetPhase(REGISTER);
267  if (status != SERVICE_WORKER_OK) {
268    // Abort this registration job.
269    Complete(status);
270    return;
271  }
272
273  set_registration(new ServiceWorkerRegistration(
274      pattern_, context_->storage()->NewRegistrationId(), context_));
275  AssociateProviderHostsToRegistration(registration());
276  UpdateAndContinue();
277}
278
279void ServiceWorkerRegisterJob::WaitForUninstall(
280    const scoped_refptr<ServiceWorkerRegistration>& existing_registration) {
281  SetPhase(WAIT_FOR_UNINSTALL);
282  set_uninstalling_registration(existing_registration);
283  uninstalling_registration()->AddListener(this);
284}
285
286void ServiceWorkerRegisterJob::ContinueWithRegistrationForSameScriptUrl(
287    const scoped_refptr<ServiceWorkerRegistration>& existing_registration,
288    ServiceWorkerStatusCode status) {
289  if (status != SERVICE_WORKER_OK) {
290    Complete(status);
291    return;
292  }
293  set_registration(existing_registration);
294
295  // TODO(falken): Follow the spec: resolve the promise
296  // with the newest version.
297
298  if (!existing_registration->active_version()) {
299    UpdateAndContinue();
300    return;
301  }
302
303  ResolvePromise(status,
304                 existing_registration.get(),
305                 existing_registration->active_version());
306  Complete(SERVICE_WORKER_OK);
307}
308
309// This function corresponds to the spec's [[Update]] algorithm.
310void ServiceWorkerRegisterJob::UpdateAndContinue() {
311  SetPhase(UPDATE);
312  context_->storage()->NotifyInstallingRegistration(registration());
313
314  // TODO(falken): "If serviceWorkerRegistration.installingWorker is not null.."
315  // then terminate the installing worker. It doesn't make sense to implement
316  // yet since we always activate the worker if install completed, so there can
317  // be no installing worker at this point.
318
319  // "Let serviceWorker be a newly-created ServiceWorker object..." and start
320  // the worker.
321  set_new_version(new ServiceWorkerVersion(registration(),
322                                           script_url_,
323                                           context_->storage()->NewVersionId(),
324                                           context_));
325
326  bool pause_after_download = job_type_ == UPDATE_JOB;
327  if (pause_after_download)
328    new_version()->embedded_worker()->AddListener(this);
329  new_version()->StartWorker(
330      pause_after_download,
331      base::Bind(&ServiceWorkerRegisterJob::OnStartWorkerFinished,
332                 weak_factory_.GetWeakPtr()));
333}
334
335void ServiceWorkerRegisterJob::OnStartWorkerFinished(
336    ServiceWorkerStatusCode status) {
337  if (status == SERVICE_WORKER_OK) {
338    InstallAndContinue();
339    return;
340  }
341
342  // "If serviceWorker fails to start up..." then reject the promise with an
343  // error and abort. When there is a main script network error, the status will
344  // be updated to a more specific one.
345  const net::URLRequestStatus& main_script_status =
346      new_version()->script_cache_map()->main_script_status();
347  if (main_script_status.status() != net::URLRequestStatus::SUCCESS) {
348    switch (main_script_status.error()) {
349      case net::ERR_INSECURE_RESPONSE:
350      case net::ERR_UNSAFE_REDIRECT:
351        status = SERVICE_WORKER_ERROR_SECURITY;
352        break;
353      case net::ERR_ABORTED:
354        status = SERVICE_WORKER_ERROR_ABORT;
355        break;
356      case net::ERR_FAILED:
357        status = SERVICE_WORKER_ERROR_NETWORK;
358        break;
359      default:
360        NOTREACHED();
361    }
362  }
363  Complete(status);
364}
365
366// This function corresponds to the spec's [[Install]] algorithm.
367void ServiceWorkerRegisterJob::InstallAndContinue() {
368  SetPhase(INSTALL);
369
370  // "2. Set registration.installingWorker to worker."
371  registration()->SetInstallingVersion(new_version());
372
373  // "3. Resolve promise with registration."
374  ResolvePromise(SERVICE_WORKER_OK, registration(), new_version());
375
376  // "4. Run the [[UpdateState]] algorithm passing registration.installingWorker
377  // and "installing" as the arguments."
378  new_version()->SetStatus(ServiceWorkerVersion::INSTALLING);
379
380  // "5. Fire a simple event named updatefound..."
381  registration()->NotifyUpdateFound();
382
383  // "6. Fire an event named install..."
384  new_version()->DispatchInstallEvent(
385      -1,
386      base::Bind(&ServiceWorkerRegisterJob::OnInstallFinished,
387                 weak_factory_.GetWeakPtr()));
388}
389
390void ServiceWorkerRegisterJob::OnInstallFinished(
391    ServiceWorkerStatusCode status) {
392  // TODO(kinuko,falken): For some error cases (e.g. ServiceWorker is
393  // unexpectedly terminated) we may want to retry sending the event again.
394  if (status != SERVICE_WORKER_OK) {
395    // "8. If installFailed is true, then:..."
396    Complete(status);
397    return;
398  }
399
400  SetPhase(STORE);
401  registration()->set_last_update_check(base::Time::Now());
402  context_->storage()->StoreRegistration(
403      registration(),
404      new_version(),
405      base::Bind(&ServiceWorkerRegisterJob::OnStoreRegistrationComplete,
406                 weak_factory_.GetWeakPtr()));
407}
408
409void ServiceWorkerRegisterJob::OnStoreRegistrationComplete(
410    ServiceWorkerStatusCode status) {
411  if (status != SERVICE_WORKER_OK) {
412    Complete(status);
413    return;
414  }
415
416  // "9. If registration.waitingWorker is not null, then:..."
417  if (registration()->waiting_version()) {
418    // "1. Run the [[UpdateState]] algorithm passing registration.waitingWorker
419    // and "redundant" as the arguments."
420    registration()->waiting_version()->SetStatus(
421        ServiceWorkerVersion::REDUNDANT);
422  }
423
424  // "10. Set registration.waitingWorker to registration.installingWorker."
425  // "11. Set registration.installingWorker to null."
426  registration()->SetWaitingVersion(new_version());
427
428  // "12. Run the [[UpdateState]] algorithm passing registration.waitingWorker
429  // and "installed" as the arguments."
430  new_version()->SetStatus(ServiceWorkerVersion::INSTALLED);
431
432  // TODO(michaeln): "13. If activateImmediate is true, then..."
433
434  // "14. Wait until no document is using registration as their
435  // Service Worker registration."
436  registration()->ActivateWaitingVersionWhenReady();
437
438  Complete(SERVICE_WORKER_OK);
439}
440
441void ServiceWorkerRegisterJob::Complete(ServiceWorkerStatusCode status) {
442  CompleteInternal(status);
443  context_->job_coordinator()->FinishJob(pattern_, this);
444}
445
446void ServiceWorkerRegisterJob::CompleteInternal(
447    ServiceWorkerStatusCode status) {
448  SetPhase(COMPLETE);
449  if (status != SERVICE_WORKER_OK) {
450    if (registration()) {
451      if (new_version()) {
452        registration()->UnsetVersion(new_version());
453        new_version()->Doom();
454      }
455      if (!registration()->waiting_version() &&
456          !registration()->active_version()) {
457        registration()->NotifyRegistrationFailed();
458        context_->storage()->DeleteRegistration(
459            registration()->id(),
460            registration()->pattern().GetOrigin(),
461            base::Bind(&ServiceWorkerUtils::NoOpStatusCallback));
462      }
463    }
464    if (!is_promise_resolved_)
465      ResolvePromise(status, NULL, NULL);
466  }
467  DCHECK(callbacks_.empty());
468  if (registration()) {
469    context_->storage()->NotifyDoneInstallingRegistration(
470        registration(), new_version(), status);
471  }
472  if (new_version())
473    new_version()->embedded_worker()->RemoveListener(this);
474}
475
476void ServiceWorkerRegisterJob::ResolvePromise(
477    ServiceWorkerStatusCode status,
478    ServiceWorkerRegistration* registration,
479    ServiceWorkerVersion* version) {
480  DCHECK(!is_promise_resolved_);
481  is_promise_resolved_ = true;
482  promise_resolved_status_ = status;
483  promise_resolved_registration_ = registration;
484  promise_resolved_version_ = version;
485  for (std::vector<RegistrationCallback>::iterator it = callbacks_.begin();
486       it != callbacks_.end();
487       ++it) {
488    it->Run(status, registration, version);
489  }
490  callbacks_.clear();
491}
492
493void ServiceWorkerRegisterJob::OnPausedAfterDownload() {
494  // This happens prior to OnStartWorkerFinished time.
495  scoped_refptr<ServiceWorkerVersion> most_recent_version =
496      registration()->waiting_version() ?
497          registration()->waiting_version() :
498          registration()->active_version();
499  DCHECK(most_recent_version.get());
500  int64 most_recent_script_id =
501      most_recent_version->script_cache_map()->Lookup(script_url_);
502  int64 new_script_id =
503      new_version()->script_cache_map()->Lookup(script_url_);
504
505  // TODO(michaeln): It would be better to compare as the new resource
506  // is being downloaded and to avoid writing it to disk until we know
507  // its needed.
508  context_->storage()->CompareScriptResources(
509      most_recent_script_id, new_script_id,
510      base::Bind(&ServiceWorkerRegisterJob::OnCompareScriptResourcesComplete,
511                 weak_factory_.GetWeakPtr(),
512                 most_recent_version));
513}
514
515bool ServiceWorkerRegisterJob::OnMessageReceived(const IPC::Message& message) {
516  return false;
517}
518
519void ServiceWorkerRegisterJob::OnRegistrationFinishedUninstalling(
520    ServiceWorkerRegistration* existing_registration) {
521  DCHECK_EQ(phase_, WAIT_FOR_UNINSTALL);
522  DCHECK_EQ(existing_registration, uninstalling_registration());
523  existing_registration->RemoveListener(this);
524  set_uninstalling_registration(NULL);
525  RegisterAndContinue(SERVICE_WORKER_OK);
526}
527
528void ServiceWorkerRegisterJob::OnCompareScriptResourcesComplete(
529    ServiceWorkerVersion* most_recent_version,
530    ServiceWorkerStatusCode status,
531    bool are_equal) {
532  if (are_equal) {
533    // Only bump the last check time when we've bypassed the browser cache.
534    base::TimeDelta time_since_last_check =
535        base::Time::Now() - registration()->last_update_check();
536    if (time_since_last_check > base::TimeDelta::FromHours(24)) {
537      registration()->set_last_update_check(base::Time::Now());
538      context_->storage()->UpdateLastUpdateCheckTime(registration());
539    }
540
541    ResolvePromise(SERVICE_WORKER_OK, registration(), most_recent_version);
542    Complete(SERVICE_WORKER_ERROR_EXISTS);
543    return;
544  }
545
546  // Proceed with really starting the worker.
547  new_version()->embedded_worker()->ResumeAfterDownload();
548  new_version()->embedded_worker()->RemoveListener(this);
549}
550
551void ServiceWorkerRegisterJob::AssociateProviderHostsToRegistration(
552    ServiceWorkerRegistration* registration) {
553  DCHECK(registration);
554  for (scoped_ptr<ServiceWorkerContextCore::ProviderHostIterator> it =
555           context_->GetProviderHostIterator();
556       !it->IsAtEnd(); it->Advance()) {
557    ServiceWorkerProviderHost* host = it->GetProviderHost();
558    if (ServiceWorkerUtils::ScopeMatches(registration->pattern(),
559                                         host->document_url())) {
560      if (host->CanAssociateRegistration(registration))
561        host->AssociateRegistration(registration);
562    }
563  }
564}
565
566}  // namespace content
567