Blink roll 25b6bd3a7a131ffe68d809546ad1a20707915cdc:3a503f41ae42e5b79cfcd2ff10e65afde...
[chromium-blink-merge.git] / content / browser / service_worker / service_worker_register_job.cc
blobb68cd30f8199d6812f9ace3a66c4f1534cb5e43d
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"
15 #include "net/base/net_errors.h"
17 namespace content {
19 namespace {
21 void RunSoon(const base::Closure& closure) {
22 base::MessageLoop::current()->PostTask(FROM_HERE, closure);
25 } // namespace
27 typedef ServiceWorkerRegisterJobBase::RegistrationJobType RegistrationJobType;
29 ServiceWorkerRegisterJob::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) {}
42 ServiceWorkerRegisterJob::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;
56 ServiceWorkerRegisterJob::~ServiceWorkerRegisterJob() {
57 DCHECK(!context_ ||
58 phase_ == INITIAL || phase_ == COMPLETE || phase_ == ABORT)
59 << "Jobs should only be interrupted during shutdown.";
62 void 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;
71 RunSoon(base::Bind(
72 callback, promise_resolved_status_, promise_resolved_registration_));
75 void ServiceWorkerRegisterJob::Start() {
76 SetPhase(START);
77 ServiceWorkerStorage::FindRegistrationCallback next_step;
78 if (job_type_ == REGISTRATION_JOB) {
79 next_step = base::Bind(
80 &ServiceWorkerRegisterJob::ContinueWithRegistration,
81 weak_factory_.GetWeakPtr());
82 } else {
83 next_step = base::Bind(
84 &ServiceWorkerRegisterJob::ContinueWithUpdate,
85 weak_factory_.GetWeakPtr());
88 scoped_refptr<ServiceWorkerRegistration> registration =
89 context_->storage()->GetUninstallingRegistration(pattern_);
90 if (registration.get())
91 RunSoon(base::Bind(next_step, SERVICE_WORKER_OK, registration));
92 else
93 context_->storage()->FindRegistrationForPattern(pattern_, next_step);
96 void ServiceWorkerRegisterJob::Abort() {
97 SetPhase(ABORT);
98 CompleteInternal(SERVICE_WORKER_ERROR_ABORT);
99 // Don't have to call FinishJob() because the caller takes care of removing
100 // the jobs from the queue.
103 bool ServiceWorkerRegisterJob::Equals(ServiceWorkerRegisterJobBase* job) {
104 if (job->GetType() != GetType())
105 return false;
106 ServiceWorkerRegisterJob* register_job =
107 static_cast<ServiceWorkerRegisterJob*>(job);
108 return register_job->pattern_ == pattern_ &&
109 register_job->script_url_ == script_url_;
112 RegistrationJobType ServiceWorkerRegisterJob::GetType() {
113 return job_type_;
116 ServiceWorkerRegisterJob::Internal::Internal() {}
118 ServiceWorkerRegisterJob::Internal::~Internal() {}
120 void ServiceWorkerRegisterJob::set_registration(
121 const scoped_refptr<ServiceWorkerRegistration>& registration) {
122 DCHECK(phase_ == START || phase_ == REGISTER) << phase_;
123 DCHECK(!internal_.registration.get());
124 internal_.registration = registration;
127 ServiceWorkerRegistration* ServiceWorkerRegisterJob::registration() {
128 DCHECK(phase_ >= REGISTER || job_type_ == UPDATE_JOB) << phase_;
129 return internal_.registration.get();
132 void ServiceWorkerRegisterJob::set_new_version(
133 ServiceWorkerVersion* version) {
134 DCHECK(phase_ == UPDATE) << phase_;
135 DCHECK(!internal_.new_version.get());
136 internal_.new_version = version;
139 ServiceWorkerVersion* ServiceWorkerRegisterJob::new_version() {
140 DCHECK(phase_ >= UPDATE) << phase_;
141 return internal_.new_version.get();
144 void ServiceWorkerRegisterJob::set_uninstalling_registration(
145 const scoped_refptr<ServiceWorkerRegistration>& registration) {
146 DCHECK_EQ(phase_, WAIT_FOR_UNINSTALL);
147 internal_.uninstalling_registration = registration;
150 ServiceWorkerRegistration*
151 ServiceWorkerRegisterJob::uninstalling_registration() {
152 DCHECK_EQ(phase_, WAIT_FOR_UNINSTALL);
153 return internal_.uninstalling_registration.get();
156 void ServiceWorkerRegisterJob::SetPhase(Phase phase) {
157 switch (phase) {
158 case INITIAL:
159 NOTREACHED();
160 break;
161 case START:
162 DCHECK(phase_ == INITIAL) << phase_;
163 break;
164 case WAIT_FOR_UNINSTALL:
165 DCHECK(phase_ == START) << phase_;
166 break;
167 case REGISTER:
168 DCHECK(phase_ == START || phase_ == WAIT_FOR_UNINSTALL) << phase_;
169 break;
170 case UPDATE:
171 DCHECK(phase_ == START || phase_ == REGISTER) << phase_;
172 break;
173 case INSTALL:
174 DCHECK(phase_ == UPDATE) << phase_;
175 break;
176 case STORE:
177 DCHECK(phase_ == INSTALL) << phase_;
178 break;
179 case COMPLETE:
180 DCHECK(phase_ != INITIAL && phase_ != COMPLETE) << phase_;
181 break;
182 case ABORT:
183 break;
185 phase_ = phase;
188 // This function corresponds to the steps in [[Register]] following
189 // "Let registration be the result of running the [[GetRegistration]] algorithm.
190 // Throughout this file, comments in quotes are excerpts from the spec.
191 void ServiceWorkerRegisterJob::ContinueWithRegistration(
192 ServiceWorkerStatusCode status,
193 const scoped_refptr<ServiceWorkerRegistration>& existing_registration) {
194 DCHECK_EQ(REGISTRATION_JOB, job_type_);
195 if (status != SERVICE_WORKER_ERROR_NOT_FOUND && status != SERVICE_WORKER_OK) {
196 Complete(status);
197 return;
200 if (!existing_registration.get() || existing_registration->is_uninstalled()) {
201 RegisterAndContinue(SERVICE_WORKER_OK);
202 return;
205 DCHECK(existing_registration->GetNewestVersion());
206 // "If scriptURL is equal to registration.[[ScriptURL]], then:"
207 if (existing_registration->GetNewestVersion()->script_url() == script_url_) {
208 // "Set registration.[[Uninstalling]] to false."
209 existing_registration->AbortPendingClear(base::Bind(
210 &ServiceWorkerRegisterJob::ContinueWithRegistrationForSameScriptUrl,
211 weak_factory_.GetWeakPtr(),
212 existing_registration));
213 return;
216 if (existing_registration->is_uninstalling()) {
217 // "Wait until the Record {[[key]], [[value]]} entry of its
218 // [[ScopeToRegistrationMap]] where registation.scope matches entry.[[key]]
219 // is deleted."
220 WaitForUninstall(existing_registration);
221 return;
224 // "Set registration.[[Uninstalling]] to false."
225 DCHECK(!existing_registration->is_uninstalling());
227 // "Return the result of running the [[Update]] algorithm, or its equivalent,
228 // passing registration as the argument."
229 set_registration(existing_registration);
230 UpdateAndContinue();
233 void ServiceWorkerRegisterJob::ContinueWithUpdate(
234 ServiceWorkerStatusCode status,
235 const scoped_refptr<ServiceWorkerRegistration>& existing_registration) {
236 DCHECK_EQ(UPDATE_JOB, job_type_);
237 if (status != SERVICE_WORKER_OK) {
238 Complete(status);
239 return;
242 if (existing_registration.get() != registration()) {
243 Complete(SERVICE_WORKER_ERROR_NOT_FOUND);
244 return;
247 // A previous job may have unregistered or installed a new version to this
248 // registration.
249 if (registration()->is_uninstalling() ||
250 registration()->GetNewestVersion()->script_url() != script_url_) {
251 Complete(SERVICE_WORKER_ERROR_NOT_FOUND);
252 return;
255 // TODO(michaeln): If the last update check was less than 24 hours
256 // ago, depending on the freshness of the cached worker script we
257 // may be able to complete the update job right here.
259 UpdateAndContinue();
262 // Creates a new ServiceWorkerRegistration.
263 void ServiceWorkerRegisterJob::RegisterAndContinue(
264 ServiceWorkerStatusCode status) {
265 SetPhase(REGISTER);
266 if (status != SERVICE_WORKER_OK) {
267 // Abort this registration job.
268 Complete(status);
269 return;
272 set_registration(new ServiceWorkerRegistration(
273 pattern_, context_->storage()->NewRegistrationId(), context_));
274 AssociateProviderHostsToRegistration(registration());
275 UpdateAndContinue();
278 void ServiceWorkerRegisterJob::WaitForUninstall(
279 const scoped_refptr<ServiceWorkerRegistration>& existing_registration) {
280 SetPhase(WAIT_FOR_UNINSTALL);
281 set_uninstalling_registration(existing_registration);
282 uninstalling_registration()->AddListener(this);
285 void ServiceWorkerRegisterJob::ContinueWithRegistrationForSameScriptUrl(
286 const scoped_refptr<ServiceWorkerRegistration>& existing_registration,
287 ServiceWorkerStatusCode status) {
288 if (status != SERVICE_WORKER_OK) {
289 Complete(status);
290 return;
292 set_registration(existing_registration);
294 // "If newestWorker is not null, and scriptURL is equal to
295 // newestWorker.scriptURL, then:
296 // Return a promise resolved with registration."
297 // We resolve only if there's an active version. If there's not,
298 // then there is either no version or only a waiting version from
299 // the last browser session; it makes sense to proceed with registration in
300 // either case.
301 DCHECK(!existing_registration->installing_version());
302 if (existing_registration->active_version()) {
303 ResolvePromise(status, existing_registration.get());
304 Complete(SERVICE_WORKER_OK);
305 return;
308 // "Return the result of running the [[Update]] algorithm, or its equivalent,
309 // passing registration as the argument."
310 UpdateAndContinue();
313 // This function corresponds to the spec's [[Update]] algorithm.
314 void ServiceWorkerRegisterJob::UpdateAndContinue() {
315 SetPhase(UPDATE);
316 context_->storage()->NotifyInstallingRegistration(registration());
318 // "Let worker be a new ServiceWorker object..." and start
319 // the worker.
320 set_new_version(new ServiceWorkerVersion(registration(),
321 script_url_,
322 context_->storage()->NewVersionId(),
323 context_));
325 bool pause_after_download = job_type_ == UPDATE_JOB;
326 if (pause_after_download)
327 new_version()->embedded_worker()->AddListener(this);
328 new_version()->StartWorker(
329 pause_after_download,
330 base::Bind(&ServiceWorkerRegisterJob::OnStartWorkerFinished,
331 weak_factory_.GetWeakPtr()));
334 void ServiceWorkerRegisterJob::OnStartWorkerFinished(
335 ServiceWorkerStatusCode status) {
336 if (status == SERVICE_WORKER_OK) {
337 InstallAndContinue();
338 return;
341 // "If serviceWorker fails to start up..." then reject the promise with an
342 // error and abort. When there is a main script network error, the status will
343 // be updated to a more specific one.
344 const net::URLRequestStatus& main_script_status =
345 new_version()->script_cache_map()->main_script_status();
346 if (main_script_status.status() != net::URLRequestStatus::SUCCESS) {
347 switch (main_script_status.error()) {
348 case net::ERR_INSECURE_RESPONSE:
349 case net::ERR_UNSAFE_REDIRECT:
350 status = SERVICE_WORKER_ERROR_SECURITY;
351 break;
352 case net::ERR_ABORTED:
353 status = SERVICE_WORKER_ERROR_ABORT;
354 break;
355 default:
356 status = SERVICE_WORKER_ERROR_NETWORK;
359 Complete(status);
362 // This function corresponds to the spec's [[Install]] algorithm.
363 void ServiceWorkerRegisterJob::InstallAndContinue() {
364 SetPhase(INSTALL);
366 // "Set registration.installingWorker to worker."
367 DCHECK(!registration()->installing_version());
368 registration()->SetInstallingVersion(new_version());
370 // "Run the Update State algorithm passing registration's installing worker
371 // and installing as the arguments."
372 new_version()->SetStatus(ServiceWorkerVersion::INSTALLING);
374 // "Resolve registrationPromise with registration."
375 ResolvePromise(SERVICE_WORKER_OK, registration());
377 // "Fire a simple event named updatefound..."
378 registration()->NotifyUpdateFound();
380 // "Fire an event named install..."
381 new_version()->DispatchInstallEvent(
383 base::Bind(&ServiceWorkerRegisterJob::OnInstallFinished,
384 weak_factory_.GetWeakPtr()));
387 void ServiceWorkerRegisterJob::OnInstallFinished(
388 ServiceWorkerStatusCode status) {
389 // TODO(kinuko,falken): For some error cases (e.g. ServiceWorker is
390 // unexpectedly terminated) we may want to retry sending the event again.
391 if (status != SERVICE_WORKER_OK) {
392 // "8. If installFailed is true, then:..."
393 Complete(status);
394 return;
397 SetPhase(STORE);
398 registration()->set_last_update_check(base::Time::Now());
399 context_->storage()->StoreRegistration(
400 registration(),
401 new_version(),
402 base::Bind(&ServiceWorkerRegisterJob::OnStoreRegistrationComplete,
403 weak_factory_.GetWeakPtr()));
406 void ServiceWorkerRegisterJob::OnStoreRegistrationComplete(
407 ServiceWorkerStatusCode status) {
408 if (status != SERVICE_WORKER_OK) {
409 Complete(status);
410 return;
413 // "9. If registration.waitingWorker is not null, then:..."
414 if (registration()->waiting_version()) {
415 // "1. Run the [[UpdateState]] algorithm passing registration.waitingWorker
416 // and "redundant" as the arguments."
417 registration()->waiting_version()->SetStatus(
418 ServiceWorkerVersion::REDUNDANT);
421 // "10. Set registration.waitingWorker to registration.installingWorker."
422 // "11. Set registration.installingWorker to null."
423 registration()->SetWaitingVersion(new_version());
425 // "12. Run the [[UpdateState]] algorithm passing registration.waitingWorker
426 // and "installed" as the arguments."
427 new_version()->SetStatus(ServiceWorkerVersion::INSTALLED);
429 // TODO(michaeln): "13. If activateImmediate is true, then..."
431 // "14. Wait until no document is using registration as their
432 // Service Worker registration."
433 registration()->ActivateWaitingVersionWhenReady();
435 Complete(SERVICE_WORKER_OK);
438 void ServiceWorkerRegisterJob::Complete(ServiceWorkerStatusCode status) {
439 CompleteInternal(status);
440 context_->job_coordinator()->FinishJob(pattern_, this);
443 void ServiceWorkerRegisterJob::CompleteInternal(
444 ServiceWorkerStatusCode status) {
445 SetPhase(COMPLETE);
446 if (status != SERVICE_WORKER_OK) {
447 if (registration()) {
448 if (new_version()) {
449 registration()->UnsetVersion(new_version());
450 new_version()->Doom();
452 if (!registration()->waiting_version() &&
453 !registration()->active_version()) {
454 registration()->NotifyRegistrationFailed();
455 context_->storage()->DeleteRegistration(
456 registration()->id(),
457 registration()->pattern().GetOrigin(),
458 base::Bind(&ServiceWorkerUtils::NoOpStatusCallback));
461 if (!is_promise_resolved_)
462 ResolvePromise(status, NULL);
464 DCHECK(callbacks_.empty());
465 if (registration()) {
466 context_->storage()->NotifyDoneInstallingRegistration(
467 registration(), new_version(), status);
469 if (new_version())
470 new_version()->embedded_worker()->RemoveListener(this);
473 void ServiceWorkerRegisterJob::ResolvePromise(
474 ServiceWorkerStatusCode status,
475 ServiceWorkerRegistration* registration) {
476 DCHECK(!is_promise_resolved_);
477 is_promise_resolved_ = true;
478 promise_resolved_status_ = status;
479 promise_resolved_registration_ = registration;
480 for (std::vector<RegistrationCallback>::iterator it = callbacks_.begin();
481 it != callbacks_.end();
482 ++it) {
483 it->Run(status, registration);
485 callbacks_.clear();
488 void ServiceWorkerRegisterJob::OnPausedAfterDownload() {
489 // This happens prior to OnStartWorkerFinished time.
490 scoped_refptr<ServiceWorkerVersion> most_recent_version =
491 registration()->waiting_version() ?
492 registration()->waiting_version() :
493 registration()->active_version();
494 DCHECK(most_recent_version.get());
495 int64 most_recent_script_id =
496 most_recent_version->script_cache_map()->LookupResourceId(script_url_);
497 int64 new_script_id =
498 new_version()->script_cache_map()->LookupResourceId(script_url_);
500 // TODO(michaeln): It would be better to compare as the new resource
501 // is being downloaded and to avoid writing it to disk until we know
502 // its needed.
503 context_->storage()->CompareScriptResources(
504 most_recent_script_id,
505 new_script_id,
506 base::Bind(&ServiceWorkerRegisterJob::OnCompareScriptResourcesComplete,
507 weak_factory_.GetWeakPtr()));
510 bool ServiceWorkerRegisterJob::OnMessageReceived(const IPC::Message& message) {
511 return false;
514 void ServiceWorkerRegisterJob::OnRegistrationFinishedUninstalling(
515 ServiceWorkerRegistration* existing_registration) {
516 DCHECK_EQ(phase_, WAIT_FOR_UNINSTALL);
517 DCHECK_EQ(existing_registration, uninstalling_registration());
518 existing_registration->RemoveListener(this);
519 set_uninstalling_registration(NULL);
520 RegisterAndContinue(SERVICE_WORKER_OK);
523 void ServiceWorkerRegisterJob::OnCompareScriptResourcesComplete(
524 ServiceWorkerStatusCode status,
525 bool are_equal) {
526 if (are_equal) {
527 // Only bump the last check time when we've bypassed the browser cache.
528 base::TimeDelta time_since_last_check =
529 base::Time::Now() - registration()->last_update_check();
530 if (time_since_last_check > base::TimeDelta::FromHours(24)) {
531 registration()->set_last_update_check(base::Time::Now());
532 context_->storage()->UpdateLastUpdateCheckTime(registration());
535 ResolvePromise(SERVICE_WORKER_OK, registration());
536 Complete(SERVICE_WORKER_ERROR_EXISTS);
537 return;
540 // Proceed with really starting the worker.
541 new_version()->embedded_worker()->ResumeAfterDownload();
542 new_version()->embedded_worker()->RemoveListener(this);
545 void ServiceWorkerRegisterJob::AssociateProviderHostsToRegistration(
546 ServiceWorkerRegistration* registration) {
547 DCHECK(registration);
548 for (scoped_ptr<ServiceWorkerContextCore::ProviderHostIterator> it =
549 context_->GetProviderHostIterator();
550 !it->IsAtEnd(); it->Advance()) {
551 ServiceWorkerProviderHost* host = it->GetProviderHost();
552 if (ServiceWorkerUtils::ScopeMatches(registration->pattern(),
553 host->document_url())) {
554 if (host->CanAssociateRegistration(registration))
555 host->AssociateRegistration(registration);
560 } // namespace content