[Storage] Blob Storage Refactoring pt 1:
[chromium-blink-merge.git] / content / browser / service_worker / service_worker_register_job.cc
blob635496830bf58154c9d7685c79a394efe665dd97
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 "content/common/service_worker/service_worker_types.h"
16 #include "net/base/net_errors.h"
18 namespace content {
20 namespace {
22 void RunSoon(const base::Closure& closure) {
23 base::MessageLoop::current()->PostTask(FROM_HERE, closure);
26 } // namespace
28 typedef ServiceWorkerRegisterJobBase::RegistrationJobType RegistrationJobType;
30 ServiceWorkerRegisterJob::ServiceWorkerRegisterJob(
31 base::WeakPtr<ServiceWorkerContextCore> context,
32 const GURL& pattern,
33 const GURL& script_url)
34 : context_(context),
35 job_type_(REGISTRATION_JOB),
36 pattern_(pattern),
37 script_url_(script_url),
38 phase_(INITIAL),
39 doom_installing_worker_(false),
40 is_promise_resolved_(false),
41 promise_resolved_status_(SERVICE_WORKER_OK),
42 weak_factory_(this) {
45 ServiceWorkerRegisterJob::ServiceWorkerRegisterJob(
46 base::WeakPtr<ServiceWorkerContextCore> context,
47 ServiceWorkerRegistration* registration)
48 : context_(context),
49 job_type_(UPDATE_JOB),
50 pattern_(registration->pattern()),
51 script_url_(registration->GetNewestVersion()->script_url()),
52 phase_(INITIAL),
53 doom_installing_worker_(false),
54 is_promise_resolved_(false),
55 promise_resolved_status_(SERVICE_WORKER_OK),
56 weak_factory_(this) {
57 internal_.registration = registration;
60 ServiceWorkerRegisterJob::~ServiceWorkerRegisterJob() {
61 DCHECK(!context_ ||
62 phase_ == INITIAL || phase_ == COMPLETE || phase_ == ABORT)
63 << "Jobs should only be interrupted during shutdown.";
66 void ServiceWorkerRegisterJob::AddCallback(
67 const RegistrationCallback& callback,
68 ServiceWorkerProviderHost* provider_host) {
69 if (!is_promise_resolved_) {
70 callbacks_.push_back(callback);
71 if (provider_host)
72 provider_host->AddScopedProcessReferenceToPattern(pattern_);
73 return;
75 RunSoon(base::Bind(callback, promise_resolved_status_,
76 promise_resolved_status_message_,
77 promise_resolved_registration_));
80 void ServiceWorkerRegisterJob::Start() {
81 SetPhase(START);
82 ServiceWorkerStorage::FindRegistrationCallback next_step;
83 if (job_type_ == REGISTRATION_JOB) {
84 next_step = base::Bind(
85 &ServiceWorkerRegisterJob::ContinueWithRegistration,
86 weak_factory_.GetWeakPtr());
87 } else {
88 next_step = base::Bind(
89 &ServiceWorkerRegisterJob::ContinueWithUpdate,
90 weak_factory_.GetWeakPtr());
93 scoped_refptr<ServiceWorkerRegistration> registration =
94 context_->storage()->GetUninstallingRegistration(pattern_);
95 if (registration.get())
96 RunSoon(base::Bind(next_step, SERVICE_WORKER_OK, registration));
97 else
98 context_->storage()->FindRegistrationForPattern(pattern_, next_step);
101 void ServiceWorkerRegisterJob::Abort() {
102 SetPhase(ABORT);
103 CompleteInternal(SERVICE_WORKER_ERROR_ABORT, std::string());
104 // Don't have to call FinishJob() because the caller takes care of removing
105 // the jobs from the queue.
108 bool ServiceWorkerRegisterJob::Equals(ServiceWorkerRegisterJobBase* job) const {
109 if (job->GetType() != GetType())
110 return false;
111 ServiceWorkerRegisterJob* register_job =
112 static_cast<ServiceWorkerRegisterJob*>(job);
113 return register_job->pattern_ == pattern_ &&
114 register_job->script_url_ == script_url_;
117 RegistrationJobType ServiceWorkerRegisterJob::GetType() const {
118 return job_type_;
121 void ServiceWorkerRegisterJob::DoomInstallingWorker() {
122 doom_installing_worker_ = true;
123 if (phase_ == INSTALL)
124 Complete(SERVICE_WORKER_ERROR_INSTALL_WORKER_FAILED, std::string());
127 ServiceWorkerRegisterJob::Internal::Internal() {}
129 ServiceWorkerRegisterJob::Internal::~Internal() {}
131 void ServiceWorkerRegisterJob::set_registration(
132 const scoped_refptr<ServiceWorkerRegistration>& registration) {
133 DCHECK(phase_ == START || phase_ == REGISTER) << phase_;
134 DCHECK(!internal_.registration.get());
135 internal_.registration = registration;
138 ServiceWorkerRegistration* ServiceWorkerRegisterJob::registration() {
139 DCHECK(phase_ >= REGISTER || job_type_ == UPDATE_JOB) << phase_;
140 return internal_.registration.get();
143 void ServiceWorkerRegisterJob::set_new_version(
144 ServiceWorkerVersion* version) {
145 DCHECK(phase_ == UPDATE) << phase_;
146 DCHECK(!internal_.new_version.get());
147 internal_.new_version = version;
150 ServiceWorkerVersion* ServiceWorkerRegisterJob::new_version() {
151 DCHECK(phase_ >= UPDATE) << phase_;
152 return internal_.new_version.get();
155 void ServiceWorkerRegisterJob::set_uninstalling_registration(
156 const scoped_refptr<ServiceWorkerRegistration>& registration) {
157 DCHECK_EQ(phase_, WAIT_FOR_UNINSTALL);
158 internal_.uninstalling_registration = registration;
161 ServiceWorkerRegistration*
162 ServiceWorkerRegisterJob::uninstalling_registration() {
163 DCHECK_EQ(phase_, WAIT_FOR_UNINSTALL);
164 return internal_.uninstalling_registration.get();
167 void ServiceWorkerRegisterJob::SetPhase(Phase phase) {
168 switch (phase) {
169 case INITIAL:
170 NOTREACHED();
171 break;
172 case START:
173 DCHECK(phase_ == INITIAL) << phase_;
174 break;
175 case WAIT_FOR_UNINSTALL:
176 DCHECK(phase_ == START) << phase_;
177 break;
178 case REGISTER:
179 DCHECK(phase_ == START || phase_ == WAIT_FOR_UNINSTALL) << phase_;
180 break;
181 case UPDATE:
182 DCHECK(phase_ == START || phase_ == REGISTER) << phase_;
183 break;
184 case INSTALL:
185 DCHECK(phase_ == UPDATE) << phase_;
186 break;
187 case STORE:
188 DCHECK(phase_ == INSTALL) << phase_;
189 break;
190 case COMPLETE:
191 DCHECK(phase_ != INITIAL && phase_ != COMPLETE) << phase_;
192 break;
193 case ABORT:
194 break;
196 phase_ = phase;
199 // This function corresponds to the steps in [[Register]] following
200 // "Let registration be the result of running the [[GetRegistration]] algorithm.
201 // Throughout this file, comments in quotes are excerpts from the spec.
202 void ServiceWorkerRegisterJob::ContinueWithRegistration(
203 ServiceWorkerStatusCode status,
204 const scoped_refptr<ServiceWorkerRegistration>& existing_registration) {
205 DCHECK_EQ(REGISTRATION_JOB, job_type_);
206 if (status != SERVICE_WORKER_ERROR_NOT_FOUND && status != SERVICE_WORKER_OK) {
207 Complete(status);
208 return;
211 if (!existing_registration.get() || existing_registration->is_uninstalled()) {
212 RegisterAndContinue(SERVICE_WORKER_OK);
213 return;
216 DCHECK(existing_registration->GetNewestVersion());
217 // "If scriptURL is equal to registration.[[ScriptURL]], then:"
218 if (existing_registration->GetNewestVersion()->script_url() == script_url_) {
219 // "Set registration.[[Uninstalling]] to false."
220 existing_registration->AbortPendingClear(base::Bind(
221 &ServiceWorkerRegisterJob::ContinueWithRegistrationForSameScriptUrl,
222 weak_factory_.GetWeakPtr(),
223 existing_registration));
224 return;
227 if (existing_registration->is_uninstalling()) {
228 // "Wait until the Record {[[key]], [[value]]} entry of its
229 // [[ScopeToRegistrationMap]] where registation.scope matches entry.[[key]]
230 // is deleted."
231 WaitForUninstall(existing_registration);
232 return;
235 // "Set registration.[[Uninstalling]] to false."
236 DCHECK(!existing_registration->is_uninstalling());
238 // "Return the result of running the [[Update]] algorithm, or its equivalent,
239 // passing registration as the argument."
240 set_registration(existing_registration);
241 UpdateAndContinue();
244 void ServiceWorkerRegisterJob::ContinueWithUpdate(
245 ServiceWorkerStatusCode status,
246 const scoped_refptr<ServiceWorkerRegistration>& existing_registration) {
247 DCHECK_EQ(UPDATE_JOB, job_type_);
248 if (status != SERVICE_WORKER_OK) {
249 Complete(status);
250 return;
253 if (existing_registration.get() != registration()) {
254 Complete(SERVICE_WORKER_ERROR_NOT_FOUND);
255 return;
258 // A previous job may have unregistered or installed a new version to this
259 // registration.
260 if (registration()->is_uninstalling() ||
261 registration()->GetNewestVersion()->script_url() != script_url_) {
262 Complete(SERVICE_WORKER_ERROR_NOT_FOUND);
263 return;
266 // TODO(michaeln): If the last update check was less than 24 hours
267 // ago, depending on the freshness of the cached worker script we
268 // may be able to complete the update job right here.
270 UpdateAndContinue();
273 // Creates a new ServiceWorkerRegistration.
274 void ServiceWorkerRegisterJob::RegisterAndContinue(
275 ServiceWorkerStatusCode status) {
276 SetPhase(REGISTER);
277 if (status != SERVICE_WORKER_OK) {
278 Complete(status);
279 return;
282 set_registration(new ServiceWorkerRegistration(
283 pattern_, context_->storage()->NewRegistrationId(), context_));
284 AssociateProviderHostsToRegistration(registration());
285 UpdateAndContinue();
288 void ServiceWorkerRegisterJob::WaitForUninstall(
289 const scoped_refptr<ServiceWorkerRegistration>& existing_registration) {
290 SetPhase(WAIT_FOR_UNINSTALL);
291 set_uninstalling_registration(existing_registration);
292 uninstalling_registration()->AddListener(this);
295 void ServiceWorkerRegisterJob::ContinueWithRegistrationForSameScriptUrl(
296 const scoped_refptr<ServiceWorkerRegistration>& existing_registration,
297 ServiceWorkerStatusCode status) {
298 if (status != SERVICE_WORKER_OK) {
299 Complete(status);
300 return;
302 set_registration(existing_registration);
304 // "If newestWorker is not null, and scriptURL is equal to
305 // newestWorker.scriptURL, then:
306 // Return a promise resolved with registration."
307 // We resolve only if there's an active version. If there's not,
308 // then there is either no version or only a waiting version from
309 // the last browser session; it makes sense to proceed with registration in
310 // either case.
311 DCHECK(!existing_registration->installing_version());
312 if (existing_registration->active_version()) {
313 ResolvePromise(status, std::string(), existing_registration.get());
314 Complete(SERVICE_WORKER_OK);
315 return;
318 // "Return the result of running the [[Update]] algorithm, or its equivalent,
319 // passing registration as the argument."
320 UpdateAndContinue();
323 // This function corresponds to the spec's [[Update]] algorithm.
324 void ServiceWorkerRegisterJob::UpdateAndContinue() {
325 SetPhase(UPDATE);
326 context_->storage()->NotifyInstallingRegistration(registration());
328 // "Let worker be a new ServiceWorker object..." and start
329 // the worker.
330 set_new_version(new ServiceWorkerVersion(registration(),
331 script_url_,
332 context_->storage()->NewVersionId(),
333 context_));
335 bool pause_after_download = job_type_ == UPDATE_JOB;
336 if (pause_after_download)
337 new_version()->embedded_worker()->AddListener(this);
338 new_version()->StartWorker(
339 pause_after_download,
340 base::Bind(&ServiceWorkerRegisterJob::OnStartWorkerFinished,
341 weak_factory_.GetWeakPtr()));
344 void ServiceWorkerRegisterJob::OnStartWorkerFinished(
345 ServiceWorkerStatusCode status) {
346 if (status == SERVICE_WORKER_OK) {
347 InstallAndContinue();
348 return;
351 // "If serviceWorker fails to start up..." then reject the promise with an
352 // error and abort. When there is a main script network error, the status will
353 // be updated to a more specific one.
354 const net::URLRequestStatus& main_script_status =
355 new_version()->script_cache_map()->main_script_status();
356 std::string message;
357 if (main_script_status.status() != net::URLRequestStatus::SUCCESS) {
358 switch (main_script_status.error()) {
359 case net::ERR_INSECURE_RESPONSE:
360 case net::ERR_UNSAFE_REDIRECT:
361 status = SERVICE_WORKER_ERROR_SECURITY;
362 break;
363 case net::ERR_ABORTED:
364 status = SERVICE_WORKER_ERROR_ABORT;
365 break;
366 default:
367 status = SERVICE_WORKER_ERROR_NETWORK;
369 message = new_version()->script_cache_map()->main_script_status_message();
370 if (message.empty())
371 message = kFetchScriptError;
373 Complete(status, message);
376 // This function corresponds to the spec's [[Install]] algorithm.
377 void ServiceWorkerRegisterJob::InstallAndContinue() {
378 SetPhase(INSTALL);
380 // "Set registration.installingWorker to worker."
381 DCHECK(!registration()->installing_version());
382 registration()->SetInstallingVersion(new_version());
384 // "Run the Update State algorithm passing registration's installing worker
385 // and installing as the arguments."
386 new_version()->SetStatus(ServiceWorkerVersion::INSTALLING);
388 // "Resolve registrationPromise with registration."
389 ResolvePromise(SERVICE_WORKER_OK, std::string(), registration());
391 // "Fire a simple event named updatefound..."
392 registration()->NotifyUpdateFound();
394 // "Fire an event named install..."
395 new_version()->DispatchInstallEvent(
397 base::Bind(&ServiceWorkerRegisterJob::OnInstallFinished,
398 weak_factory_.GetWeakPtr()));
400 // A subsequent registration job may terminate our installing worker. It can
401 // only do so after we've started the worker and dispatched the install
402 // event, as those are atomic substeps in the [[Install]] algorithm.
403 if (doom_installing_worker_)
404 Complete(SERVICE_WORKER_ERROR_INSTALL_WORKER_FAILED);
407 void ServiceWorkerRegisterJob::OnInstallFinished(
408 ServiceWorkerStatusCode status) {
409 // TODO(kinuko,falken): For some error cases (e.g. ServiceWorker is
410 // unexpectedly terminated) we may want to retry sending the event again.
411 if (status != SERVICE_WORKER_OK) {
412 // "8. If installFailed is true, then:..."
413 Complete(status);
414 return;
417 SetPhase(STORE);
418 registration()->set_last_update_check(base::Time::Now());
419 context_->storage()->StoreRegistration(
420 registration(),
421 new_version(),
422 base::Bind(&ServiceWorkerRegisterJob::OnStoreRegistrationComplete,
423 weak_factory_.GetWeakPtr()));
426 void ServiceWorkerRegisterJob::OnStoreRegistrationComplete(
427 ServiceWorkerStatusCode status) {
428 if (status != SERVICE_WORKER_OK) {
429 Complete(status);
430 return;
433 // "9. If registration.waitingWorker is not null, then:..."
434 if (registration()->waiting_version()) {
435 // "1. Run the [[UpdateState]] algorithm passing registration.waitingWorker
436 // and "redundant" as the arguments."
437 registration()->waiting_version()->SetStatus(
438 ServiceWorkerVersion::REDUNDANT);
441 // "10. Set registration.waitingWorker to registration.installingWorker."
442 // "11. Set registration.installingWorker to null."
443 registration()->SetWaitingVersion(new_version());
445 // "12. Run the [[UpdateState]] algorithm passing registration.waitingWorker
446 // and "installed" as the arguments."
447 new_version()->SetStatus(ServiceWorkerVersion::INSTALLED);
449 // "If registration's waiting worker's skip waiting flag is set:" then
450 // activate the worker immediately otherwise "wait until no service worker
451 // client is using registration as their service worker registration."
452 registration()->ActivateWaitingVersionWhenReady();
454 Complete(SERVICE_WORKER_OK);
457 void ServiceWorkerRegisterJob::Complete(ServiceWorkerStatusCode status) {
458 Complete(status, std::string());
461 void ServiceWorkerRegisterJob::Complete(ServiceWorkerStatusCode status,
462 const std::string& status_message) {
463 CompleteInternal(status, status_message);
464 context_->job_coordinator()->FinishJob(pattern_, this);
467 void ServiceWorkerRegisterJob::CompleteInternal(
468 ServiceWorkerStatusCode status,
469 const std::string& status_message) {
470 SetPhase(COMPLETE);
471 if (status != SERVICE_WORKER_OK) {
472 if (registration()) {
473 if (new_version()) {
474 registration()->UnsetVersion(new_version());
475 new_version()->Doom();
477 if (!registration()->waiting_version() &&
478 !registration()->active_version()) {
479 registration()->NotifyRegistrationFailed();
480 context_->storage()->DeleteRegistration(
481 registration()->id(),
482 registration()->pattern().GetOrigin(),
483 base::Bind(&ServiceWorkerUtils::NoOpStatusCallback));
486 if (!is_promise_resolved_)
487 ResolvePromise(status, status_message, NULL);
489 DCHECK(callbacks_.empty());
490 if (registration()) {
491 context_->storage()->NotifyDoneInstallingRegistration(
492 registration(), new_version(), status);
494 if (new_version())
495 new_version()->embedded_worker()->RemoveListener(this);
498 void ServiceWorkerRegisterJob::ResolvePromise(
499 ServiceWorkerStatusCode status,
500 const std::string& status_message,
501 ServiceWorkerRegistration* registration) {
502 DCHECK(!is_promise_resolved_);
504 is_promise_resolved_ = true;
505 promise_resolved_status_ = status;
506 promise_resolved_status_message_ = status_message,
507 promise_resolved_registration_ = registration;
508 for (std::vector<RegistrationCallback>::iterator it = callbacks_.begin();
509 it != callbacks_.end();
510 ++it) {
511 it->Run(status, status_message, registration);
513 callbacks_.clear();
516 void ServiceWorkerRegisterJob::OnPausedAfterDownload() {
517 // This happens prior to OnStartWorkerFinished time.
518 scoped_refptr<ServiceWorkerVersion> most_recent_version =
519 registration()->waiting_version() ?
520 registration()->waiting_version() :
521 registration()->active_version();
522 DCHECK(most_recent_version.get());
523 int64 most_recent_script_id =
524 most_recent_version->script_cache_map()->LookupResourceId(script_url_);
525 int64 new_script_id =
526 new_version()->script_cache_map()->LookupResourceId(script_url_);
528 // TODO(michaeln): It would be better to compare as the new resource
529 // is being downloaded and to avoid writing it to disk until we know
530 // its needed.
531 context_->storage()->CompareScriptResources(
532 most_recent_script_id,
533 new_script_id,
534 base::Bind(&ServiceWorkerRegisterJob::OnCompareScriptResourcesComplete,
535 weak_factory_.GetWeakPtr()));
538 bool ServiceWorkerRegisterJob::OnMessageReceived(const IPC::Message& message) {
539 return false;
542 void ServiceWorkerRegisterJob::OnRegistrationFinishedUninstalling(
543 ServiceWorkerRegistration* existing_registration) {
544 DCHECK_EQ(phase_, WAIT_FOR_UNINSTALL);
545 DCHECK_EQ(existing_registration, uninstalling_registration());
546 existing_registration->RemoveListener(this);
547 set_uninstalling_registration(NULL);
548 RegisterAndContinue(SERVICE_WORKER_OK);
551 void ServiceWorkerRegisterJob::OnCompareScriptResourcesComplete(
552 ServiceWorkerStatusCode status,
553 bool are_equal) {
554 if (are_equal) {
555 // Only bump the last check time when we've bypassed the browser cache.
556 base::TimeDelta time_since_last_check =
557 base::Time::Now() - registration()->last_update_check();
558 if (time_since_last_check > base::TimeDelta::FromHours(24)) {
559 registration()->set_last_update_check(base::Time::Now());
560 context_->storage()->UpdateLastUpdateCheckTime(registration());
563 ResolvePromise(SERVICE_WORKER_OK, std::string(), registration());
564 Complete(SERVICE_WORKER_ERROR_EXISTS);
565 return;
568 // Proceed with really starting the worker.
569 new_version()->embedded_worker()->ResumeAfterDownload();
570 new_version()->embedded_worker()->RemoveListener(this);
573 void ServiceWorkerRegisterJob::AssociateProviderHostsToRegistration(
574 ServiceWorkerRegistration* registration) {
575 DCHECK(registration);
576 for (scoped_ptr<ServiceWorkerContextCore::ProviderHostIterator> it =
577 context_->GetProviderHostIterator();
578 !it->IsAtEnd(); it->Advance()) {
579 ServiceWorkerProviderHost* host = it->GetProviderHost();
580 if (ServiceWorkerUtils::ScopeMatches(registration->pattern(),
581 host->document_url())) {
582 if (host->CanAssociateRegistration(registration))
583 host->AssociateRegistration(registration);
588 } // namespace content