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_storage.h"
6
7 #include <string>
8
9 #include "base/bind_helpers.h"
10 #include "base/message_loop/message_loop.h"
11 #include "base/sequenced_task_runner.h"
12 #include "base/task_runner_util.h"
13 #include "content/browser/service_worker/service_worker_context_core.h"
14 #include "content/browser/service_worker/service_worker_disk_cache.h"
15 #include "content/browser/service_worker/service_worker_histograms.h"
16 #include "content/browser/service_worker/service_worker_info.h"
17 #include "content/browser/service_worker/service_worker_registration.h"
18 #include "content/browser/service_worker/service_worker_utils.h"
19 #include "content/browser/service_worker/service_worker_version.h"
20 #include "content/common/service_worker/service_worker_types.h"
21 #include "content/public/browser/browser_thread.h"
22 #include "net/base/net_errors.h"
23 #include "webkit/browser/quota/quota_manager_proxy.h"
24
25 namespace content {
26
27 namespace {
28
RunSoon(const tracked_objects::Location & from_here,const base::Closure & closure)29 void RunSoon(const tracked_objects::Location& from_here,
30 const base::Closure& closure) {
31 base::MessageLoop::current()->PostTask(from_here, closure);
32 }
33
CompleteFindNow(const scoped_refptr<ServiceWorkerRegistration> & registration,ServiceWorkerStatusCode status,const ServiceWorkerStorage::FindRegistrationCallback & callback)34 void CompleteFindNow(
35 const scoped_refptr<ServiceWorkerRegistration>& registration,
36 ServiceWorkerStatusCode status,
37 const ServiceWorkerStorage::FindRegistrationCallback& callback) {
38 callback.Run(status, registration);
39 }
40
CompleteFindSoon(const tracked_objects::Location & from_here,const scoped_refptr<ServiceWorkerRegistration> & registration,ServiceWorkerStatusCode status,const ServiceWorkerStorage::FindRegistrationCallback & callback)41 void CompleteFindSoon(
42 const tracked_objects::Location& from_here,
43 const scoped_refptr<ServiceWorkerRegistration>& registration,
44 ServiceWorkerStatusCode status,
45 const ServiceWorkerStorage::FindRegistrationCallback& callback) {
46 RunSoon(from_here, base::Bind(callback, status, registration));
47 }
48
49 const base::FilePath::CharType kServiceWorkerDirectory[] =
50 FILE_PATH_LITERAL("Service Worker");
51 const base::FilePath::CharType kDatabaseName[] =
52 FILE_PATH_LITERAL("Database");
53 const base::FilePath::CharType kDiskCacheName[] =
54 FILE_PATH_LITERAL("Cache");
55
56 const int kMaxMemDiskCacheSize = 10 * 1024 * 1024;
57 const int kMaxDiskCacheSize = 250 * 1024 * 1024;
58
EmptyCompletionCallback(int)59 void EmptyCompletionCallback(int) {}
60
DatabaseStatusToStatusCode(ServiceWorkerDatabase::Status status)61 ServiceWorkerStatusCode DatabaseStatusToStatusCode(
62 ServiceWorkerDatabase::Status status) {
63 switch (status) {
64 case ServiceWorkerDatabase::STATUS_OK:
65 return SERVICE_WORKER_OK;
66 case ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND:
67 return SERVICE_WORKER_ERROR_NOT_FOUND;
68 case ServiceWorkerDatabase::STATUS_ERROR_MAX:
69 NOTREACHED();
70 default:
71 return SERVICE_WORKER_ERROR_FAILED;
72 }
73 }
74
75 } // namespace
76
InitialData()77 ServiceWorkerStorage::InitialData::InitialData()
78 : next_registration_id(kInvalidServiceWorkerRegistrationId),
79 next_version_id(kInvalidServiceWorkerVersionId),
80 next_resource_id(kInvalidServiceWorkerResourceId) {
81 }
82
~InitialData()83 ServiceWorkerStorage::InitialData::~InitialData() {
84 }
85
ServiceWorkerStorage(const base::FilePath & path,base::WeakPtr<ServiceWorkerContextCore> context,base::SequencedTaskRunner * database_task_runner,base::MessageLoopProxy * disk_cache_thread,quota::QuotaManagerProxy * quota_manager_proxy)86 ServiceWorkerStorage::ServiceWorkerStorage(
87 const base::FilePath& path,
88 base::WeakPtr<ServiceWorkerContextCore> context,
89 base::SequencedTaskRunner* database_task_runner,
90 base::MessageLoopProxy* disk_cache_thread,
91 quota::QuotaManagerProxy* quota_manager_proxy)
92 : next_registration_id_(kInvalidServiceWorkerRegistrationId),
93 next_version_id_(kInvalidServiceWorkerVersionId),
94 next_resource_id_(kInvalidServiceWorkerResourceId),
95 state_(UNINITIALIZED),
96 context_(context),
97 database_task_runner_(database_task_runner),
98 disk_cache_thread_(disk_cache_thread),
99 quota_manager_proxy_(quota_manager_proxy),
100 is_purge_pending_(false),
101 weak_factory_(this) {
102 if (!path.empty())
103 path_ = path.Append(kServiceWorkerDirectory);
104 database_.reset(new ServiceWorkerDatabase(GetDatabasePath()));
105 }
106
~ServiceWorkerStorage()107 ServiceWorkerStorage::~ServiceWorkerStorage() {
108 weak_factory_.InvalidateWeakPtrs();
109 database_task_runner_->DeleteSoon(FROM_HERE, database_.release());
110 }
111
FindRegistrationForDocument(const GURL & document_url,const FindRegistrationCallback & callback)112 void ServiceWorkerStorage::FindRegistrationForDocument(
113 const GURL& document_url,
114 const FindRegistrationCallback& callback) {
115 DCHECK(!document_url.has_ref());
116 if (!LazyInitialize(base::Bind(
117 &ServiceWorkerStorage::FindRegistrationForDocument,
118 weak_factory_.GetWeakPtr(), document_url, callback))) {
119 if (state_ != INITIALIZING || !context_) {
120 CompleteFindNow(scoped_refptr<ServiceWorkerRegistration>(),
121 SERVICE_WORKER_ERROR_FAILED, callback);
122 }
123 return;
124 }
125 DCHECK_EQ(INITIALIZED, state_);
126
127 // See if there are any stored registrations for the origin.
128 if (!ContainsKey(registered_origins_, document_url.GetOrigin())) {
129 // Look for something currently being installed.
130 scoped_refptr<ServiceWorkerRegistration> installing_registration =
131 FindInstallingRegistrationForDocument(document_url);
132 CompleteFindNow(
133 installing_registration,
134 installing_registration ?
135 SERVICE_WORKER_OK : SERVICE_WORKER_ERROR_NOT_FOUND,
136 callback);
137 return;
138 }
139
140 database_task_runner_->PostTask(
141 FROM_HERE,
142 base::Bind(
143 &FindForDocumentInDB,
144 database_.get(),
145 base::MessageLoopProxy::current(),
146 document_url,
147 base::Bind(&ServiceWorkerStorage::DidFindRegistrationForDocument,
148 weak_factory_.GetWeakPtr(), document_url, callback)));
149 }
150
FindRegistrationForPattern(const GURL & scope,const FindRegistrationCallback & callback)151 void ServiceWorkerStorage::FindRegistrationForPattern(
152 const GURL& scope,
153 const FindRegistrationCallback& callback) {
154 if (!LazyInitialize(base::Bind(
155 &ServiceWorkerStorage::FindRegistrationForPattern,
156 weak_factory_.GetWeakPtr(), scope, callback))) {
157 if (state_ != INITIALIZING || !context_) {
158 CompleteFindSoon(FROM_HERE, scoped_refptr<ServiceWorkerRegistration>(),
159 SERVICE_WORKER_ERROR_FAILED, callback);
160 }
161 return;
162 }
163 DCHECK_EQ(INITIALIZED, state_);
164
165 // See if there are any stored registrations for the origin.
166 if (!ContainsKey(registered_origins_, scope.GetOrigin())) {
167 // Look for something currently being installed.
168 scoped_refptr<ServiceWorkerRegistration> installing_registration =
169 FindInstallingRegistrationForPattern(scope);
170 CompleteFindSoon(
171 FROM_HERE, installing_registration,
172 installing_registration ?
173 SERVICE_WORKER_OK : SERVICE_WORKER_ERROR_NOT_FOUND,
174 callback);
175 return;
176 }
177
178 database_task_runner_->PostTask(
179 FROM_HERE,
180 base::Bind(
181 &FindForPatternInDB,
182 database_.get(),
183 base::MessageLoopProxy::current(),
184 scope,
185 base::Bind(&ServiceWorkerStorage::DidFindRegistrationForPattern,
186 weak_factory_.GetWeakPtr(), scope, callback)));
187 }
188
FindRegistrationForId(int64 registration_id,const GURL & origin,const FindRegistrationCallback & callback)189 void ServiceWorkerStorage::FindRegistrationForId(
190 int64 registration_id,
191 const GURL& origin,
192 const FindRegistrationCallback& callback) {
193 if (!LazyInitialize(base::Bind(
194 &ServiceWorkerStorage::FindRegistrationForId,
195 weak_factory_.GetWeakPtr(), registration_id, origin, callback))) {
196 if (state_ != INITIALIZING || !context_) {
197 CompleteFindNow(scoped_refptr<ServiceWorkerRegistration>(),
198 SERVICE_WORKER_ERROR_FAILED, callback);
199 }
200 return;
201 }
202 DCHECK_EQ(INITIALIZED, state_);
203
204 // See if there are any stored registrations for the origin.
205 if (!ContainsKey(registered_origins_, origin)) {
206 // Look for something currently being installed.
207 scoped_refptr<ServiceWorkerRegistration> installing_registration =
208 FindInstallingRegistrationForId(registration_id);
209 CompleteFindNow(
210 installing_registration,
211 installing_registration ?
212 SERVICE_WORKER_OK : SERVICE_WORKER_ERROR_NOT_FOUND,
213 callback);
214 return;
215 }
216
217 scoped_refptr<ServiceWorkerRegistration> registration =
218 context_->GetLiveRegistration(registration_id);
219 if (registration) {
220 CompleteFindNow(registration, SERVICE_WORKER_OK, callback);
221 return;
222 }
223
224 database_task_runner_->PostTask(
225 FROM_HERE,
226 base::Bind(&FindForIdInDB,
227 database_.get(),
228 base::MessageLoopProxy::current(),
229 registration_id, origin,
230 base::Bind(&ServiceWorkerStorage::DidFindRegistrationForId,
231 weak_factory_.GetWeakPtr(), callback)));
232 }
233
GetAllRegistrations(const GetAllRegistrationInfosCallback & callback)234 void ServiceWorkerStorage::GetAllRegistrations(
235 const GetAllRegistrationInfosCallback& callback) {
236 if (!LazyInitialize(base::Bind(
237 &ServiceWorkerStorage::GetAllRegistrations,
238 weak_factory_.GetWeakPtr(), callback))) {
239 if (state_ != INITIALIZING || !context_) {
240 RunSoon(FROM_HERE, base::Bind(
241 callback, std::vector<ServiceWorkerRegistrationInfo>()));
242 }
243 return;
244 }
245 DCHECK_EQ(INITIALIZED, state_);
246
247 RegistrationList* registrations = new RegistrationList;
248 PostTaskAndReplyWithResult(
249 database_task_runner_,
250 FROM_HERE,
251 base::Bind(&ServiceWorkerDatabase::GetAllRegistrations,
252 base::Unretained(database_.get()),
253 base::Unretained(registrations)),
254 base::Bind(&ServiceWorkerStorage::DidGetAllRegistrations,
255 weak_factory_.GetWeakPtr(),
256 callback,
257 base::Owned(registrations)));
258 }
259
StoreRegistration(ServiceWorkerRegistration * registration,ServiceWorkerVersion * version,const StatusCallback & callback)260 void ServiceWorkerStorage::StoreRegistration(
261 ServiceWorkerRegistration* registration,
262 ServiceWorkerVersion* version,
263 const StatusCallback& callback) {
264 DCHECK(registration);
265 DCHECK(version);
266
267 DCHECK(state_ == INITIALIZED || state_ == DISABLED);
268 if (state_ != INITIALIZED || !context_) {
269 RunSoon(FROM_HERE, base::Bind(callback, SERVICE_WORKER_ERROR_FAILED));
270 return;
271 }
272
273 ServiceWorkerDatabase::RegistrationData data;
274 data.registration_id = registration->id();
275 data.scope = registration->pattern();
276 data.script = registration->script_url();
277 data.has_fetch_handler = true;
278 data.version_id = version->version_id();
279 data.last_update_check = base::Time::Now();
280 data.is_active = false; // initially stored in the waiting state
281
282 ResourceList resources;
283 version->script_cache_map()->GetResources(&resources);
284
285 database_task_runner_->PostTask(
286 FROM_HERE,
287 base::Bind(&WriteRegistrationInDB,
288 database_.get(),
289 base::MessageLoopProxy::current(),
290 data, resources,
291 base::Bind(&ServiceWorkerStorage::DidStoreRegistration,
292 weak_factory_.GetWeakPtr(),
293 callback)));
294 }
295
UpdateToActiveState(ServiceWorkerRegistration * registration,const StatusCallback & callback)296 void ServiceWorkerStorage::UpdateToActiveState(
297 ServiceWorkerRegistration* registration,
298 const StatusCallback& callback) {
299 DCHECK(registration);
300
301 DCHECK(state_ == INITIALIZED || state_ == DISABLED);
302 if (state_ != INITIALIZED || !context_) {
303 RunSoon(FROM_HERE, base::Bind(callback, SERVICE_WORKER_ERROR_FAILED));
304 return;
305 }
306
307 PostTaskAndReplyWithResult(
308 database_task_runner_,
309 FROM_HERE,
310 base::Bind(&ServiceWorkerDatabase::UpdateVersionToActive,
311 base::Unretained(database_.get()),
312 registration->id(),
313 registration->script_url().GetOrigin()),
314 base::Bind(&ServiceWorkerStorage::DidUpdateToActiveState,
315 weak_factory_.GetWeakPtr(),
316 callback));
317 }
318
DeleteRegistration(int64 registration_id,const GURL & origin,const StatusCallback & callback)319 void ServiceWorkerStorage::DeleteRegistration(
320 int64 registration_id,
321 const GURL& origin,
322 const StatusCallback& callback) {
323 DCHECK(state_ == INITIALIZED || state_ == DISABLED);
324 if (state_ != INITIALIZED || !context_) {
325 RunSoon(FROM_HERE, base::Bind(callback, SERVICE_WORKER_ERROR_FAILED));
326 return;
327 }
328
329 database_task_runner_->PostTask(
330 FROM_HERE,
331 base::Bind(&DeleteRegistrationFromDB,
332 database_.get(),
333 base::MessageLoopProxy::current(),
334 registration_id, origin,
335 base::Bind(&ServiceWorkerStorage::DidDeleteRegistration,
336 weak_factory_.GetWeakPtr(), origin, callback)));
337
338 // TODO(michaeln): Either its instance should also be
339 // removed from liveregistrations map or the live object
340 // should marked as deleted in some way and not 'findable'
341 // thereafter.
342 }
343
344 scoped_ptr<ServiceWorkerResponseReader>
CreateResponseReader(int64 response_id)345 ServiceWorkerStorage::CreateResponseReader(int64 response_id) {
346 return make_scoped_ptr(
347 new ServiceWorkerResponseReader(response_id, disk_cache()));
348 }
349
350 scoped_ptr<ServiceWorkerResponseWriter>
CreateResponseWriter(int64 response_id)351 ServiceWorkerStorage::CreateResponseWriter(int64 response_id) {
352 return make_scoped_ptr(
353 new ServiceWorkerResponseWriter(response_id, disk_cache()));
354 }
355
StoreUncommittedReponseId(int64 id)356 void ServiceWorkerStorage::StoreUncommittedReponseId(int64 id) {
357 DCHECK_NE(kInvalidServiceWorkerResponseId, id);
358 database_task_runner_->PostTask(
359 FROM_HERE,
360 base::Bind(base::IgnoreResult(
361 &ServiceWorkerDatabase::WriteUncommittedResourceIds),
362 base::Unretained(database_.get()),
363 std::set<int64>(&id, &id + 1)));
364 }
365
DoomUncommittedResponse(int64 id)366 void ServiceWorkerStorage::DoomUncommittedResponse(int64 id) {
367 DCHECK_NE(kInvalidServiceWorkerResponseId, id);
368 database_task_runner_->PostTask(
369 FROM_HERE,
370 base::Bind(base::IgnoreResult(
371 &ServiceWorkerDatabase::PurgeUncommittedResourceIds),
372 base::Unretained(database_.get()),
373 std::set<int64>(&id, &id + 1)));
374 StartPurgingResources(std::vector<int64>(1, id));
375 }
376
NewRegistrationId()377 int64 ServiceWorkerStorage::NewRegistrationId() {
378 if (state_ == DISABLED)
379 return kInvalidServiceWorkerRegistrationId;
380 DCHECK_EQ(INITIALIZED, state_);
381 return next_registration_id_++;
382 }
383
NewVersionId()384 int64 ServiceWorkerStorage::NewVersionId() {
385 if (state_ == DISABLED)
386 return kInvalidServiceWorkerVersionId;
387 DCHECK_EQ(INITIALIZED, state_);
388 return next_version_id_++;
389 }
390
NewResourceId()391 int64 ServiceWorkerStorage::NewResourceId() {
392 if (state_ == DISABLED)
393 return kInvalidServiceWorkerResourceId;
394 DCHECK_EQ(INITIALIZED, state_);
395 return next_resource_id_++;
396 }
397
NotifyInstallingRegistration(ServiceWorkerRegistration * registration)398 void ServiceWorkerStorage::NotifyInstallingRegistration(
399 ServiceWorkerRegistration* registration) {
400 installing_registrations_[registration->id()] = registration;
401 }
402
NotifyDoneInstallingRegistration(ServiceWorkerRegistration * registration,ServiceWorkerVersion * version,ServiceWorkerStatusCode status)403 void ServiceWorkerStorage::NotifyDoneInstallingRegistration(
404 ServiceWorkerRegistration* registration,
405 ServiceWorkerVersion* version,
406 ServiceWorkerStatusCode status) {
407 installing_registrations_.erase(registration->id());
408 if (status != SERVICE_WORKER_OK && version) {
409 ResourceList resources;
410 version->script_cache_map()->GetResources(&resources);
411
412 std::set<int64> ids;
413 for (size_t i = 0; i < resources.size(); ++i)
414 ids.insert(resources[i].resource_id);
415
416 database_task_runner_->PostTask(
417 FROM_HERE,
418 base::Bind(base::IgnoreResult(
419 &ServiceWorkerDatabase::PurgeUncommittedResourceIds),
420 base::Unretained(database_.get()),
421 ids));
422
423 StartPurgingResources(resources);
424 }
425 }
426
GetDatabasePath()427 base::FilePath ServiceWorkerStorage::GetDatabasePath() {
428 if (path_.empty())
429 return base::FilePath();
430 return path_.Append(kDatabaseName);
431 }
432
GetDiskCachePath()433 base::FilePath ServiceWorkerStorage::GetDiskCachePath() {
434 if (path_.empty())
435 return base::FilePath();
436 return path_.Append(kDiskCacheName);
437 }
438
LazyInitialize(const base::Closure & callback)439 bool ServiceWorkerStorage::LazyInitialize(const base::Closure& callback) {
440 if (!context_)
441 return false;
442
443 switch (state_) {
444 case INITIALIZED:
445 return true;
446 case DISABLED:
447 return false;
448 case INITIALIZING:
449 pending_tasks_.push_back(callback);
450 return false;
451 case UNINITIALIZED:
452 pending_tasks_.push_back(callback);
453 // Fall-through.
454 }
455
456 state_ = INITIALIZING;
457 database_task_runner_->PostTask(
458 FROM_HERE,
459 base::Bind(&ReadInitialDataFromDB,
460 database_.get(),
461 base::MessageLoopProxy::current(),
462 base::Bind(&ServiceWorkerStorage::DidReadInitialData,
463 weak_factory_.GetWeakPtr())));
464 return false;
465 }
466
DidReadInitialData(InitialData * data,ServiceWorkerDatabase::Status status)467 void ServiceWorkerStorage::DidReadInitialData(
468 InitialData* data,
469 ServiceWorkerDatabase::Status status) {
470 DCHECK(data);
471 DCHECK_EQ(INITIALIZING, state_);
472
473 if (status == ServiceWorkerDatabase::STATUS_OK) {
474 next_registration_id_ = data->next_registration_id;
475 next_version_id_ = data->next_version_id;
476 next_resource_id_ = data->next_resource_id;
477 registered_origins_.swap(data->origins);
478 state_ = INITIALIZED;
479 } else {
480 // TODO(nhiroki): If status==STATUS_ERROR_CORRUPTED, do corruption recovery
481 // (http://crbug.com/371675).
482 DLOG(WARNING) << "Failed to initialize: " << status;
483 state_ = DISABLED;
484 }
485
486 for (std::vector<base::Closure>::const_iterator it = pending_tasks_.begin();
487 it != pending_tasks_.end(); ++it) {
488 RunSoon(FROM_HERE, *it);
489 }
490 pending_tasks_.clear();
491 }
492
DidFindRegistrationForDocument(const GURL & document_url,const FindRegistrationCallback & callback,const ServiceWorkerDatabase::RegistrationData & data,const ResourceList & resources,ServiceWorkerDatabase::Status status)493 void ServiceWorkerStorage::DidFindRegistrationForDocument(
494 const GURL& document_url,
495 const FindRegistrationCallback& callback,
496 const ServiceWorkerDatabase::RegistrationData& data,
497 const ResourceList& resources,
498 ServiceWorkerDatabase::Status status) {
499 if (status == ServiceWorkerDatabase::STATUS_OK) {
500 callback.Run(SERVICE_WORKER_OK, GetOrCreateRegistration(data, resources));
501 return;
502 }
503
504 if (status == ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND) {
505 // Look for something currently being installed.
506 scoped_refptr<ServiceWorkerRegistration> installing_registration =
507 FindInstallingRegistrationForDocument(document_url);
508 callback.Run(installing_registration ?
509 SERVICE_WORKER_OK : SERVICE_WORKER_ERROR_NOT_FOUND,
510 installing_registration);
511 return;
512 }
513
514 // TODO(nhiroki): Handle database error (http://crbug.com/371675).
515 callback.Run(DatabaseStatusToStatusCode(status),
516 scoped_refptr<ServiceWorkerRegistration>());
517 }
518
DidFindRegistrationForPattern(const GURL & scope,const FindRegistrationCallback & callback,const ServiceWorkerDatabase::RegistrationData & data,const ResourceList & resources,ServiceWorkerDatabase::Status status)519 void ServiceWorkerStorage::DidFindRegistrationForPattern(
520 const GURL& scope,
521 const FindRegistrationCallback& callback,
522 const ServiceWorkerDatabase::RegistrationData& data,
523 const ResourceList& resources,
524 ServiceWorkerDatabase::Status status) {
525 if (status == ServiceWorkerDatabase::STATUS_OK) {
526 callback.Run(SERVICE_WORKER_OK, GetOrCreateRegistration(data, resources));
527 return;
528 }
529
530 if (status == ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND) {
531 scoped_refptr<ServiceWorkerRegistration> installing_registration =
532 FindInstallingRegistrationForPattern(scope);
533 callback.Run(installing_registration ?
534 SERVICE_WORKER_OK : SERVICE_WORKER_ERROR_NOT_FOUND,
535 installing_registration);
536 return;
537 }
538
539 // TODO(nhiroki): Handle database error (http://crbug.com/371675).
540 callback.Run(DatabaseStatusToStatusCode(status),
541 scoped_refptr<ServiceWorkerRegistration>());
542 }
543
DidFindRegistrationForId(const FindRegistrationCallback & callback,const ServiceWorkerDatabase::RegistrationData & data,const ResourceList & resources,ServiceWorkerDatabase::Status status)544 void ServiceWorkerStorage::DidFindRegistrationForId(
545 const FindRegistrationCallback& callback,
546 const ServiceWorkerDatabase::RegistrationData& data,
547 const ResourceList& resources,
548 ServiceWorkerDatabase::Status status) {
549 if (status == ServiceWorkerDatabase::STATUS_OK) {
550 callback.Run(SERVICE_WORKER_OK,
551 GetOrCreateRegistration(data, resources));
552 return;
553 }
554 // TODO(nhiroki): Handle database error (http://crbug.com/371675).
555 callback.Run(DatabaseStatusToStatusCode(status),
556 scoped_refptr<ServiceWorkerRegistration>());
557 }
558
DidGetAllRegistrations(const GetAllRegistrationInfosCallback & callback,RegistrationList * registrations,ServiceWorkerDatabase::Status status)559 void ServiceWorkerStorage::DidGetAllRegistrations(
560 const GetAllRegistrationInfosCallback& callback,
561 RegistrationList* registrations,
562 ServiceWorkerDatabase::Status status) {
563 DCHECK(registrations);
564 if (status != ServiceWorkerDatabase::STATUS_OK) {
565 // TODO(nhiroki): Handle database error (http://crbug.com/371675).
566 callback.Run(std::vector<ServiceWorkerRegistrationInfo>());
567 return;
568 }
569
570 // Add all stored registrations.
571 std::set<int64> pushed_registrations;
572 std::vector<ServiceWorkerRegistrationInfo> infos;
573 for (RegistrationList::const_iterator it = registrations->begin();
574 it != registrations->end(); ++it) {
575 const bool inserted =
576 pushed_registrations.insert(it->registration_id).second;
577 DCHECK(inserted);
578
579 ServiceWorkerRegistration* registration =
580 context_->GetLiveRegistration(it->registration_id);
581 if (registration) {
582 infos.push_back(registration->GetInfo());
583 continue;
584 }
585
586 ServiceWorkerRegistrationInfo info;
587 info.pattern = it->scope;
588 info.script_url = it->script;
589 info.registration_id = it->registration_id;
590 if (ServiceWorkerVersion* version =
591 context_->GetLiveVersion(it->version_id)) {
592 if (it->is_active)
593 info.active_version = version->GetInfo();
594 else
595 info.waiting_version = version->GetInfo();
596 infos.push_back(info);
597 continue;
598 }
599
600 if (it->is_active) {
601 info.active_version.is_null = false;
602 info.active_version.status = ServiceWorkerVersion::ACTIVE;
603 info.active_version.version_id = it->version_id;
604 } else {
605 info.waiting_version.is_null = false;
606 info.waiting_version.status = ServiceWorkerVersion::INSTALLED;
607 info.waiting_version.version_id = it->version_id;
608 }
609 infos.push_back(info);
610 }
611
612 // Add unstored registrations that are being installed.
613 for (RegistrationRefsById::const_iterator it =
614 installing_registrations_.begin();
615 it != installing_registrations_.end(); ++it) {
616 if (pushed_registrations.insert(it->first).second)
617 infos.push_back(it->second->GetInfo());
618 }
619
620 callback.Run(infos);
621 }
622
DidStoreRegistration(const StatusCallback & callback,const GURL & origin,const std::vector<int64> & newly_purgeable_resources,ServiceWorkerDatabase::Status status)623 void ServiceWorkerStorage::DidStoreRegistration(
624 const StatusCallback& callback,
625 const GURL& origin,
626 const std::vector<int64>& newly_purgeable_resources,
627 ServiceWorkerDatabase::Status status) {
628 if (status != ServiceWorkerDatabase::STATUS_OK) {
629 // TODO(nhiroki): Handle database error (http://crbug.com/371675).
630 callback.Run(DatabaseStatusToStatusCode(status));
631 return;
632 }
633 registered_origins_.insert(origin);
634 callback.Run(SERVICE_WORKER_OK);
635 StartPurgingResources(newly_purgeable_resources);
636 }
637
DidUpdateToActiveState(const StatusCallback & callback,ServiceWorkerDatabase::Status status)638 void ServiceWorkerStorage::DidUpdateToActiveState(
639 const StatusCallback& callback,
640 ServiceWorkerDatabase::Status status) {
641 // TODO(nhiroki): Handle database error (http://crbug.com/371675).
642 callback.Run(DatabaseStatusToStatusCode(status));
643 }
644
DidDeleteRegistration(const GURL & origin,const StatusCallback & callback,bool origin_is_deletable,const std::vector<int64> & newly_purgeable_resources,ServiceWorkerDatabase::Status status)645 void ServiceWorkerStorage::DidDeleteRegistration(
646 const GURL& origin,
647 const StatusCallback& callback,
648 bool origin_is_deletable,
649 const std::vector<int64>& newly_purgeable_resources,
650 ServiceWorkerDatabase::Status status) {
651 if (status != ServiceWorkerDatabase::STATUS_OK) {
652 // TODO(nhiroki): Handle database error (http://crbug.com/371675).
653 callback.Run(DatabaseStatusToStatusCode(status));
654 return;
655 }
656 if (origin_is_deletable)
657 registered_origins_.erase(origin);
658 callback.Run(SERVICE_WORKER_OK);
659 StartPurgingResources(newly_purgeable_resources);
660 }
661
662 scoped_refptr<ServiceWorkerRegistration>
GetOrCreateRegistration(const ServiceWorkerDatabase::RegistrationData & data,const ResourceList & resources)663 ServiceWorkerStorage::GetOrCreateRegistration(
664 const ServiceWorkerDatabase::RegistrationData& data,
665 const ResourceList& resources) {
666 scoped_refptr<ServiceWorkerRegistration> registration =
667 context_->GetLiveRegistration(data.registration_id);
668 if (registration)
669 return registration;
670
671 registration = new ServiceWorkerRegistration(
672 data.scope, data.script, data.registration_id, context_);
673 scoped_refptr<ServiceWorkerVersion> version =
674 context_->GetLiveVersion(data.version_id);
675 if (!version) {
676 version = new ServiceWorkerVersion(registration, data.version_id, context_);
677 version->SetStatus(data.is_active ?
678 ServiceWorkerVersion::ACTIVE : ServiceWorkerVersion::INSTALLED);
679 version->script_cache_map()->SetResources(resources);
680 }
681
682 if (version->status() == ServiceWorkerVersion::ACTIVE)
683 registration->set_active_version(version);
684 else if (version->status() == ServiceWorkerVersion::INSTALLED)
685 registration->set_waiting_version(version);
686 else
687 NOTREACHED();
688 // TODO(michaeln): Hmmm, what if DeleteReg was invoked after
689 // the Find result we're returning here? NOTREACHED condition?
690 return registration;
691 }
692
693 ServiceWorkerRegistration*
FindInstallingRegistrationForDocument(const GURL & document_url)694 ServiceWorkerStorage::FindInstallingRegistrationForDocument(
695 const GURL& document_url) {
696 DCHECK(!document_url.has_ref());
697
698 LongestScopeMatcher matcher(document_url);
699 ServiceWorkerRegistration* match = NULL;
700
701 // TODO(nhiroki): This searches over installing registrations linearly and it
702 // couldn't be scalable. Maybe the regs should be partitioned by origin.
703 for (RegistrationRefsById::const_iterator it =
704 installing_registrations_.begin();
705 it != installing_registrations_.end(); ++it) {
706 if (matcher.MatchLongest(it->second->pattern()))
707 match = it->second;
708 }
709 return match;
710 }
711
712 ServiceWorkerRegistration*
FindInstallingRegistrationForPattern(const GURL & scope)713 ServiceWorkerStorage::FindInstallingRegistrationForPattern(
714 const GURL& scope) {
715 for (RegistrationRefsById::const_iterator it =
716 installing_registrations_.begin();
717 it != installing_registrations_.end(); ++it) {
718 if (it->second->pattern() == scope)
719 return it->second;
720 }
721 return NULL;
722 }
723
724 ServiceWorkerRegistration*
FindInstallingRegistrationForId(int64 registration_id)725 ServiceWorkerStorage::FindInstallingRegistrationForId(
726 int64 registration_id) {
727 RegistrationRefsById::const_iterator found =
728 installing_registrations_.find(registration_id);
729 if (found == installing_registrations_.end())
730 return NULL;
731 return found->second;
732 }
733
disk_cache()734 ServiceWorkerDiskCache* ServiceWorkerStorage::disk_cache() {
735 if (disk_cache_)
736 return disk_cache_.get();
737
738 disk_cache_.reset(new ServiceWorkerDiskCache);
739
740 base::FilePath path = GetDiskCachePath();
741 if (path.empty()) {
742 int rv = disk_cache_->InitWithMemBackend(
743 kMaxMemDiskCacheSize,
744 base::Bind(&EmptyCompletionCallback));
745 DCHECK_EQ(net::OK, rv);
746 return disk_cache_.get();
747 }
748
749 int rv = disk_cache_->InitWithDiskBackend(
750 path, kMaxDiskCacheSize, false,
751 disk_cache_thread_.get(),
752 base::Bind(&ServiceWorkerStorage::OnDiskCacheInitialized,
753 weak_factory_.GetWeakPtr()));
754 if (rv != net::ERR_IO_PENDING)
755 OnDiskCacheInitialized(rv);
756
757 return disk_cache_.get();
758 }
759
OnDiskCacheInitialized(int rv)760 void ServiceWorkerStorage::OnDiskCacheInitialized(int rv) {
761 if (rv != net::OK) {
762 LOG(ERROR) << "Failed to open the serviceworker diskcache: "
763 << net::ErrorToString(rv);
764 // TODO(michaeln): DeleteAndStartOver()
765 disk_cache_->Disable();
766 state_ = DISABLED;
767 }
768 ServiceWorkerHistograms::CountInitDiskCacheResult(rv == net::OK);
769 }
770
StartPurgingResources(const std::vector<int64> & ids)771 void ServiceWorkerStorage::StartPurgingResources(
772 const std::vector<int64>& ids) {
773 for (size_t i = 0; i < ids.size(); ++i)
774 purgeable_reource_ids_.push_back(ids[i]);
775 ContinuePurgingResources();
776 }
777
StartPurgingResources(const ResourceList & resources)778 void ServiceWorkerStorage::StartPurgingResources(
779 const ResourceList& resources) {
780 for (size_t i = 0; i < resources.size(); ++i)
781 purgeable_reource_ids_.push_back(resources[i].resource_id);
782 ContinuePurgingResources();
783 }
784
ContinuePurgingResources()785 void ServiceWorkerStorage::ContinuePurgingResources() {
786 if (purgeable_reource_ids_.empty() || is_purge_pending_)
787 return;
788
789 // Do one at a time until we're done, use RunSoon to avoid recursion when
790 // DoomEntry returns immediately.
791 is_purge_pending_ = true;
792 int64 id = purgeable_reource_ids_.front();
793 purgeable_reource_ids_.pop_front();
794 RunSoon(FROM_HERE,
795 base::Bind(&ServiceWorkerStorage::PurgeResource,
796 weak_factory_.GetWeakPtr(), id));
797 }
798
PurgeResource(int64 id)799 void ServiceWorkerStorage::PurgeResource(int64 id) {
800 DCHECK(is_purge_pending_);
801 int rv = disk_cache()->DoomEntry(
802 id, base::Bind(&ServiceWorkerStorage::OnResourcePurged,
803 weak_factory_.GetWeakPtr(), id));
804 if (rv != net::ERR_IO_PENDING)
805 OnResourcePurged(id, rv);
806 }
807
OnResourcePurged(int64 id,int rv)808 void ServiceWorkerStorage::OnResourcePurged(int64 id, int rv) {
809 DCHECK(is_purge_pending_);
810 is_purge_pending_ = false;
811
812 database_task_runner_->PostTask(
813 FROM_HERE,
814 base::Bind(base::IgnoreResult(
815 &ServiceWorkerDatabase::ClearPurgeableResourceIds),
816 base::Unretained(database_.get()),
817 std::set<int64>(&id, &id + 1)));
818
819 ContinuePurgingResources();
820 }
821
ReadInitialDataFromDB(ServiceWorkerDatabase * database,scoped_refptr<base::SequencedTaskRunner> original_task_runner,const InitializeCallback & callback)822 void ServiceWorkerStorage::ReadInitialDataFromDB(
823 ServiceWorkerDatabase* database,
824 scoped_refptr<base::SequencedTaskRunner> original_task_runner,
825 const InitializeCallback& callback) {
826 DCHECK(database);
827 scoped_ptr<ServiceWorkerStorage::InitialData> data(
828 new ServiceWorkerStorage::InitialData());
829
830 ServiceWorkerDatabase::Status status =
831 database->GetNextAvailableIds(&data->next_registration_id,
832 &data->next_version_id,
833 &data->next_resource_id);
834 if (status != ServiceWorkerDatabase::STATUS_OK) {
835 original_task_runner->PostTask(
836 FROM_HERE, base::Bind(callback, base::Owned(data.release()), status));
837 return;
838 }
839
840 status = database->GetOriginsWithRegistrations(&data->origins);
841 original_task_runner->PostTask(
842 FROM_HERE, base::Bind(callback, base::Owned(data.release()), status));
843 }
844
DeleteRegistrationFromDB(ServiceWorkerDatabase * database,scoped_refptr<base::SequencedTaskRunner> original_task_runner,int64 registration_id,const GURL & origin,const DeleteRegistrationCallback & callback)845 void ServiceWorkerStorage::DeleteRegistrationFromDB(
846 ServiceWorkerDatabase* database,
847 scoped_refptr<base::SequencedTaskRunner> original_task_runner,
848 int64 registration_id,
849 const GURL& origin,
850 const DeleteRegistrationCallback& callback) {
851 DCHECK(database);
852
853 std::vector<int64> newly_purgeable_resources;
854 ServiceWorkerDatabase::Status status =
855 database->DeleteRegistration(registration_id, origin,
856 &newly_purgeable_resources);
857 if (status != ServiceWorkerDatabase::STATUS_OK) {
858 original_task_runner->PostTask(
859 FROM_HERE, base::Bind(callback, false, std::vector<int64>(), status));
860 return;
861 }
862
863 // TODO(nhiroki): Add convenient method to ServiceWorkerDatabase to check the
864 // unique origin list.
865 std::vector<ServiceWorkerDatabase::RegistrationData> registrations;
866 status = database->GetRegistrationsForOrigin(origin, ®istrations);
867 if (status != ServiceWorkerDatabase::STATUS_OK) {
868 original_task_runner->PostTask(
869 FROM_HERE, base::Bind(callback, false, std::vector<int64>(), status));
870 return;
871 }
872
873 bool deletable = registrations.empty();
874 original_task_runner->PostTask(
875 FROM_HERE, base::Bind(callback, deletable,
876 newly_purgeable_resources, status));
877 }
878
WriteRegistrationInDB(ServiceWorkerDatabase * database,scoped_refptr<base::SequencedTaskRunner> original_task_runner,const ServiceWorkerDatabase::RegistrationData & data,const ResourceList & resources,const WriteRegistrationCallback & callback)879 void ServiceWorkerStorage::WriteRegistrationInDB(
880 ServiceWorkerDatabase* database,
881 scoped_refptr<base::SequencedTaskRunner> original_task_runner,
882 const ServiceWorkerDatabase::RegistrationData& data,
883 const ResourceList& resources,
884 const WriteRegistrationCallback& callback) {
885 DCHECK(database);
886 std::vector<int64> newly_purgeable_resources;
887 ServiceWorkerDatabase::Status status =
888 database->WriteRegistration(data, resources, &newly_purgeable_resources);
889 original_task_runner->PostTask(
890 FROM_HERE,
891 base::Bind(callback, data.script.GetOrigin(),
892 newly_purgeable_resources, status));
893 }
894
FindForDocumentInDB(ServiceWorkerDatabase * database,scoped_refptr<base::SequencedTaskRunner> original_task_runner,const GURL & document_url,const FindInDBCallback & callback)895 void ServiceWorkerStorage::FindForDocumentInDB(
896 ServiceWorkerDatabase* database,
897 scoped_refptr<base::SequencedTaskRunner> original_task_runner,
898 const GURL& document_url,
899 const FindInDBCallback& callback) {
900 GURL origin = document_url.GetOrigin();
901 RegistrationList registrations;
902 ServiceWorkerDatabase::Status status =
903 database->GetRegistrationsForOrigin(origin, ®istrations);
904 if (status != ServiceWorkerDatabase::STATUS_OK) {
905 original_task_runner->PostTask(
906 FROM_HERE,
907 base::Bind(callback,
908 ServiceWorkerDatabase::RegistrationData(),
909 ResourceList(),
910 status));
911 return;
912 }
913
914 ServiceWorkerDatabase::RegistrationData data;
915 ResourceList resources;
916 status = ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND;
917
918 // Find one with a pattern match.
919 LongestScopeMatcher matcher(document_url);
920 int64 match = kInvalidServiceWorkerRegistrationId;
921 for (size_t i = 0; i < registrations.size(); ++i) {
922 if (matcher.MatchLongest(registrations[i].scope))
923 match = registrations[i].registration_id;
924 }
925
926 if (match != kInvalidServiceWorkerRegistrationId)
927 status = database->ReadRegistration(match, origin, &data, &resources);
928
929 original_task_runner->PostTask(
930 FROM_HERE,
931 base::Bind(callback, data, resources, status));
932 }
933
FindForPatternInDB(ServiceWorkerDatabase * database,scoped_refptr<base::SequencedTaskRunner> original_task_runner,const GURL & scope,const FindInDBCallback & callback)934 void ServiceWorkerStorage::FindForPatternInDB(
935 ServiceWorkerDatabase* database,
936 scoped_refptr<base::SequencedTaskRunner> original_task_runner,
937 const GURL& scope,
938 const FindInDBCallback& callback) {
939 GURL origin = scope.GetOrigin();
940 std::vector<ServiceWorkerDatabase::RegistrationData> registrations;
941 ServiceWorkerDatabase::Status status =
942 database->GetRegistrationsForOrigin(origin, ®istrations);
943 if (status != ServiceWorkerDatabase::STATUS_OK) {
944 original_task_runner->PostTask(
945 FROM_HERE,
946 base::Bind(callback,
947 ServiceWorkerDatabase::RegistrationData(),
948 ResourceList(),
949 status));
950 return;
951 }
952
953 // Find one with an exact matching scope.
954 ServiceWorkerDatabase::RegistrationData data;
955 ResourceList resources;
956 status = ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND;
957 for (RegistrationList::const_iterator it = registrations.begin();
958 it != registrations.end(); ++it) {
959 if (scope != it->scope)
960 continue;
961 status = database->ReadRegistration(it->registration_id, origin,
962 &data, &resources);
963 break; // We're done looping.
964 }
965
966 original_task_runner->PostTask(
967 FROM_HERE,
968 base::Bind(callback, data, resources, status));
969 }
970
FindForIdInDB(ServiceWorkerDatabase * database,scoped_refptr<base::SequencedTaskRunner> original_task_runner,int64 registration_id,const GURL & origin,const FindInDBCallback & callback)971 void ServiceWorkerStorage::FindForIdInDB(
972 ServiceWorkerDatabase* database,
973 scoped_refptr<base::SequencedTaskRunner> original_task_runner,
974 int64 registration_id,
975 const GURL& origin,
976 const FindInDBCallback& callback) {
977 ServiceWorkerDatabase::RegistrationData data;
978 ResourceList resources;
979 ServiceWorkerDatabase::Status status =
980 database->ReadRegistration(registration_id, origin, &data, &resources);
981 original_task_runner->PostTask(
982 FROM_HERE, base::Bind(callback, data, resources, status));
983 }
984
985 } // namespace content
986