Re-subimission of https://codereview.chromium.org/1041213003/
[chromium-blink-merge.git] / content / browser / appcache / appcache_storage_impl.cc
blob3653488f188485da6ad6a75064cfa7cd37bf362d
1 // Copyright (c) 2012 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/appcache/appcache_storage_impl.h"
7 #include <algorithm>
8 #include <functional>
9 #include <set>
10 #include <vector>
12 #include "base/bind.h"
13 #include "base/bind_helpers.h"
14 #include "base/files/file_util.h"
15 #include "base/logging.h"
16 #include "base/message_loop/message_loop.h"
17 #include "base/profiler/scoped_tracker.h"
18 #include "base/single_thread_task_runner.h"
19 #include "base/stl_util.h"
20 #include "base/strings/string_util.h"
21 #include "content/browser/appcache/appcache.h"
22 #include "content/browser/appcache/appcache_database.h"
23 #include "content/browser/appcache/appcache_entry.h"
24 #include "content/browser/appcache/appcache_group.h"
25 #include "content/browser/appcache/appcache_histograms.h"
26 #include "content/browser/appcache/appcache_quota_client.h"
27 #include "content/browser/appcache/appcache_response.h"
28 #include "content/browser/appcache/appcache_service_impl.h"
29 #include "net/base/cache_type.h"
30 #include "net/base/net_errors.h"
31 #include "sql/connection.h"
32 #include "sql/transaction.h"
33 #include "storage/browser/quota/quota_client.h"
34 #include "storage/browser/quota/quota_manager.h"
35 #include "storage/browser/quota/quota_manager_proxy.h"
36 #include "storage/browser/quota/special_storage_policy.h"
38 namespace content {
40 // Hard coded default when not using quota management.
41 static const int kDefaultQuota = 5 * 1024 * 1024;
43 static const int kMaxDiskCacheSize = 250 * 1024 * 1024;
44 static const int kMaxMemDiskCacheSize = 10 * 1024 * 1024;
45 static const base::FilePath::CharType kDiskCacheDirectoryName[] =
46 FILE_PATH_LITERAL("Cache");
48 namespace {
50 // Helpers for clearing data from the AppCacheDatabase.
51 bool DeleteGroupAndRelatedRecords(AppCacheDatabase* database,
52 int64 group_id,
53 std::vector<int64>* deletable_response_ids) {
54 AppCacheDatabase::CacheRecord cache_record;
55 bool success = false;
56 if (database->FindCacheForGroup(group_id, &cache_record)) {
57 database->FindResponseIdsForCacheAsVector(cache_record.cache_id,
58 deletable_response_ids);
59 success =
60 database->DeleteGroup(group_id) &&
61 database->DeleteCache(cache_record.cache_id) &&
62 database->DeleteEntriesForCache(cache_record.cache_id) &&
63 database->DeleteNamespacesForCache(cache_record.cache_id) &&
64 database->DeleteOnlineWhiteListForCache(cache_record.cache_id) &&
65 database->InsertDeletableResponseIds(*deletable_response_ids);
66 } else {
67 NOTREACHED() << "A existing group without a cache is unexpected";
68 success = database->DeleteGroup(group_id);
70 return success;
73 // Destroys |database|. If there is appcache data to be deleted
74 // (|force_keep_session_state| is false), deletes session-only appcache data.
75 void ClearSessionOnlyOrigins(
76 AppCacheDatabase* database,
77 scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy,
78 bool force_keep_session_state) {
79 scoped_ptr<AppCacheDatabase> database_to_delete(database);
81 // If saving session state, only delete the database.
82 if (force_keep_session_state)
83 return;
85 bool has_session_only_appcaches =
86 special_storage_policy.get() &&
87 special_storage_policy->HasSessionOnlyOrigins();
89 // Clearning only session-only databases, and there are none.
90 if (!has_session_only_appcaches)
91 return;
93 std::set<GURL> origins;
94 database->FindOriginsWithGroups(&origins);
95 if (origins.empty())
96 return; // nothing to delete
98 sql::Connection* connection = database->db_connection();
99 if (!connection) {
100 NOTREACHED() << "Missing database connection.";
101 return;
104 std::set<GURL>::const_iterator origin;
105 DCHECK(special_storage_policy.get());
106 for (origin = origins.begin(); origin != origins.end(); ++origin) {
107 if (!special_storage_policy->IsStorageSessionOnly(*origin))
108 continue;
109 if (special_storage_policy->IsStorageProtected(*origin))
110 continue;
112 std::vector<AppCacheDatabase::GroupRecord> groups;
113 database->FindGroupsForOrigin(*origin, &groups);
114 std::vector<AppCacheDatabase::GroupRecord>::const_iterator group;
115 for (group = groups.begin(); group != groups.end(); ++group) {
116 sql::Transaction transaction(connection);
117 if (!transaction.Begin()) {
118 NOTREACHED() << "Failed to start transaction";
119 return;
121 std::vector<int64> deletable_response_ids;
122 bool success = DeleteGroupAndRelatedRecords(database,
123 group->group_id,
124 &deletable_response_ids);
125 success = success && transaction.Commit();
126 DCHECK(success);
127 } // for each group
128 } // for each origin
131 } // namespace
133 // DatabaseTask -----------------------------------------
135 class AppCacheStorageImpl::DatabaseTask
136 : public base::RefCountedThreadSafe<DatabaseTask> {
137 public:
138 explicit DatabaseTask(AppCacheStorageImpl* storage)
139 : storage_(storage), database_(storage->database_),
140 io_thread_(base::MessageLoopProxy::current()) {
141 DCHECK(io_thread_.get());
144 void AddDelegate(DelegateReference* delegate_reference) {
145 delegates_.push_back(make_scoped_refptr(delegate_reference));
148 // Schedules a task to be Run() on the DB thread. Tasks
149 // are run in the order in which they are scheduled.
150 void Schedule();
152 // Called on the DB thread.
153 virtual void Run() = 0;
155 // Called on the IO thread after Run() has completed.
156 virtual void RunCompleted() {}
158 // Once scheduled a task cannot be cancelled, but the
159 // call to RunCompleted may be. This method should only be
160 // called on the IO thread. This is used by AppCacheStorageImpl
161 // to cancel the completion calls when AppCacheStorageImpl is
162 // destructed. This method may be overriden to release or delete
163 // additional data associated with the task that is not DB thread
164 // safe. If overriden, this base class method must be called from
165 // within the override.
166 virtual void CancelCompletion();
168 protected:
169 friend class base::RefCountedThreadSafe<DatabaseTask>;
170 virtual ~DatabaseTask() {}
172 AppCacheStorageImpl* storage_;
173 AppCacheDatabase* database_;
174 DelegateReferenceVector delegates_;
176 private:
177 void CallRun(base::TimeTicks schedule_time);
178 void CallRunCompleted(base::TimeTicks schedule_time);
179 void OnFatalError();
181 scoped_refptr<base::MessageLoopProxy> io_thread_;
184 void AppCacheStorageImpl::DatabaseTask::Schedule() {
185 DCHECK(storage_);
186 DCHECK(io_thread_->BelongsToCurrentThread());
187 if (!storage_->database_)
188 return;
190 if (storage_->db_thread_->PostTask(
191 FROM_HERE,
192 base::Bind(&DatabaseTask::CallRun, this, base::TimeTicks::Now()))) {
193 storage_->scheduled_database_tasks_.push_back(this);
194 } else {
195 NOTREACHED() << "Thread for database tasks is not running.";
199 void AppCacheStorageImpl::DatabaseTask::CancelCompletion() {
200 DCHECK(io_thread_->BelongsToCurrentThread());
201 delegates_.clear();
202 storage_ = NULL;
205 void AppCacheStorageImpl::DatabaseTask::CallRun(
206 base::TimeTicks schedule_time) {
207 AppCacheHistograms::AddTaskQueueTimeSample(
208 base::TimeTicks::Now() - schedule_time);
209 if (!database_->is_disabled()) {
210 base::TimeTicks run_time = base::TimeTicks::Now();
211 Run();
212 AppCacheHistograms::AddTaskRunTimeSample(
213 base::TimeTicks::Now() - run_time);
215 if (database_->was_corruption_detected()) {
216 AppCacheHistograms::CountCorruptionDetected();
217 database_->Disable();
219 if (database_->is_disabled()) {
220 io_thread_->PostTask(
221 FROM_HERE,
222 base::Bind(&DatabaseTask::OnFatalError, this));
225 io_thread_->PostTask(
226 FROM_HERE,
227 base::Bind(&DatabaseTask::CallRunCompleted, this,
228 base::TimeTicks::Now()));
231 void AppCacheStorageImpl::DatabaseTask::CallRunCompleted(
232 base::TimeTicks schedule_time) {
233 AppCacheHistograms::AddCompletionQueueTimeSample(
234 base::TimeTicks::Now() - schedule_time);
235 if (storage_) {
236 DCHECK(io_thread_->BelongsToCurrentThread());
237 DCHECK(storage_->scheduled_database_tasks_.front() == this);
238 storage_->scheduled_database_tasks_.pop_front();
239 base::TimeTicks run_time = base::TimeTicks::Now();
240 RunCompleted();
241 AppCacheHistograms::AddCompletionRunTimeSample(
242 base::TimeTicks::Now() - run_time);
243 delegates_.clear();
247 void AppCacheStorageImpl::DatabaseTask::OnFatalError() {
248 if (storage_) {
249 DCHECK(io_thread_->BelongsToCurrentThread());
250 storage_->Disable();
251 storage_->DeleteAndStartOver();
255 // InitTask -------
257 class AppCacheStorageImpl::InitTask : public DatabaseTask {
258 public:
259 explicit InitTask(AppCacheStorageImpl* storage)
260 : DatabaseTask(storage), last_group_id_(0),
261 last_cache_id_(0), last_response_id_(0),
262 last_deletable_response_rowid_(0) {
263 if (!storage->is_incognito_) {
264 db_file_path_ =
265 storage->cache_directory_.Append(kAppCacheDatabaseName);
266 disk_cache_directory_ =
267 storage->cache_directory_.Append(kDiskCacheDirectoryName);
271 // DatabaseTask:
272 void Run() override;
273 void RunCompleted() override;
275 protected:
276 ~InitTask() override {}
278 private:
279 base::FilePath db_file_path_;
280 base::FilePath disk_cache_directory_;
281 int64 last_group_id_;
282 int64 last_cache_id_;
283 int64 last_response_id_;
284 int64 last_deletable_response_rowid_;
285 std::map<GURL, int64> usage_map_;
288 void AppCacheStorageImpl::InitTask::Run() {
289 tracked_objects::ScopedTracker tracking_profile(
290 FROM_HERE_WITH_EXPLICIT_FUNCTION("AppCacheStorageImpl::InitTask"));
291 // If there is no sql database, ensure there is no disk cache either.
292 if (!db_file_path_.empty() &&
293 !base::PathExists(db_file_path_) &&
294 base::DirectoryExists(disk_cache_directory_)) {
295 base::DeleteFile(disk_cache_directory_, true);
296 if (base::DirectoryExists(disk_cache_directory_)) {
297 database_->Disable(); // This triggers OnFatalError handling.
298 return;
302 database_->FindLastStorageIds(
303 &last_group_id_, &last_cache_id_, &last_response_id_,
304 &last_deletable_response_rowid_);
305 database_->GetAllOriginUsage(&usage_map_);
308 void AppCacheStorageImpl::InitTask::RunCompleted() {
309 storage_->last_group_id_ = last_group_id_;
310 storage_->last_cache_id_ = last_cache_id_;
311 storage_->last_response_id_ = last_response_id_;
312 storage_->last_deletable_response_rowid_ = last_deletable_response_rowid_;
314 if (!storage_->is_disabled()) {
315 storage_->usage_map_.swap(usage_map_);
316 const base::TimeDelta kDelay = base::TimeDelta::FromMinutes(5);
317 base::MessageLoop::current()->PostDelayedTask(
318 FROM_HERE,
319 base::Bind(&AppCacheStorageImpl::DelayedStartDeletingUnusedResponses,
320 storage_->weak_factory_.GetWeakPtr()),
321 kDelay);
324 if (storage_->service()->quota_client())
325 storage_->service()->quota_client()->NotifyAppCacheReady();
328 // DisableDatabaseTask -------
330 class AppCacheStorageImpl::DisableDatabaseTask : public DatabaseTask {
331 public:
332 explicit DisableDatabaseTask(AppCacheStorageImpl* storage)
333 : DatabaseTask(storage) {}
335 // DatabaseTask:
336 void Run() override { database_->Disable(); }
338 protected:
339 ~DisableDatabaseTask() override {}
342 // GetAllInfoTask -------
344 class AppCacheStorageImpl::GetAllInfoTask : public DatabaseTask {
345 public:
346 explicit GetAllInfoTask(AppCacheStorageImpl* storage)
347 : DatabaseTask(storage),
348 info_collection_(new AppCacheInfoCollection()) {
351 // DatabaseTask:
352 void Run() override;
353 void RunCompleted() override;
355 protected:
356 ~GetAllInfoTask() override {}
358 private:
359 scoped_refptr<AppCacheInfoCollection> info_collection_;
362 void AppCacheStorageImpl::GetAllInfoTask::Run() {
363 std::set<GURL> origins;
364 database_->FindOriginsWithGroups(&origins);
365 for (std::set<GURL>::const_iterator origin = origins.begin();
366 origin != origins.end(); ++origin) {
367 AppCacheInfoVector& infos =
368 info_collection_->infos_by_origin[*origin];
369 std::vector<AppCacheDatabase::GroupRecord> groups;
370 database_->FindGroupsForOrigin(*origin, &groups);
371 for (std::vector<AppCacheDatabase::GroupRecord>::const_iterator
372 group = groups.begin();
373 group != groups.end(); ++group) {
374 AppCacheDatabase::CacheRecord cache_record;
375 database_->FindCacheForGroup(group->group_id, &cache_record);
376 AppCacheInfo info;
377 info.manifest_url = group->manifest_url;
378 info.creation_time = group->creation_time;
379 info.size = cache_record.cache_size;
380 info.last_access_time = group->last_access_time;
381 info.last_update_time = cache_record.update_time;
382 info.cache_id = cache_record.cache_id;
383 info.group_id = group->group_id;
384 info.is_complete = true;
385 infos.push_back(info);
390 void AppCacheStorageImpl::GetAllInfoTask::RunCompleted() {
391 DCHECK_EQ(1U, delegates_.size());
392 FOR_EACH_DELEGATE(delegates_, OnAllInfo(info_collection_.get()));
395 // StoreOrLoadTask -------
397 class AppCacheStorageImpl::StoreOrLoadTask : public DatabaseTask {
398 protected:
399 explicit StoreOrLoadTask(AppCacheStorageImpl* storage)
400 : DatabaseTask(storage) {}
401 ~StoreOrLoadTask() override {}
403 bool FindRelatedCacheRecords(int64 cache_id);
404 void CreateCacheAndGroupFromRecords(
405 scoped_refptr<AppCache>* cache, scoped_refptr<AppCacheGroup>* group);
407 AppCacheDatabase::GroupRecord group_record_;
408 AppCacheDatabase::CacheRecord cache_record_;
409 std::vector<AppCacheDatabase::EntryRecord> entry_records_;
410 std::vector<AppCacheDatabase::NamespaceRecord>
411 intercept_namespace_records_;
412 std::vector<AppCacheDatabase::NamespaceRecord>
413 fallback_namespace_records_;
414 std::vector<AppCacheDatabase::OnlineWhiteListRecord>
415 online_whitelist_records_;
418 bool AppCacheStorageImpl::StoreOrLoadTask::FindRelatedCacheRecords(
419 int64 cache_id) {
420 return database_->FindEntriesForCache(cache_id, &entry_records_) &&
421 database_->FindNamespacesForCache(
422 cache_id, &intercept_namespace_records_,
423 &fallback_namespace_records_) &&
424 database_->FindOnlineWhiteListForCache(
425 cache_id, &online_whitelist_records_);
428 void AppCacheStorageImpl::StoreOrLoadTask::CreateCacheAndGroupFromRecords(
429 scoped_refptr<AppCache>* cache, scoped_refptr<AppCacheGroup>* group) {
430 DCHECK(storage_ && cache && group);
432 (*cache) = storage_->working_set_.GetCache(cache_record_.cache_id);
433 if (cache->get()) {
434 (*group) = cache->get()->owning_group();
435 DCHECK(group->get());
436 DCHECK_EQ(group_record_.group_id, group->get()->group_id());
438 // TODO(michaeln): histogram is fishing for clues to crbug/95101
439 if (!cache->get()->GetEntry(group_record_.manifest_url)) {
440 AppCacheHistograms::AddMissingManifestDetectedAtCallsite(
441 AppCacheHistograms::CALLSITE_0);
444 storage_->NotifyStorageAccessed(group_record_.origin);
445 return;
448 (*cache) = new AppCache(storage_, cache_record_.cache_id);
449 cache->get()->InitializeWithDatabaseRecords(
450 cache_record_, entry_records_,
451 intercept_namespace_records_,
452 fallback_namespace_records_,
453 online_whitelist_records_);
454 cache->get()->set_complete(true);
456 (*group) = storage_->working_set_.GetGroup(group_record_.manifest_url);
457 if (group->get()) {
458 DCHECK(group_record_.group_id == group->get()->group_id());
459 group->get()->AddCache(cache->get());
461 // TODO(michaeln): histogram is fishing for clues to crbug/95101
462 if (!cache->get()->GetEntry(group_record_.manifest_url)) {
463 AppCacheHistograms::AddMissingManifestDetectedAtCallsite(
464 AppCacheHistograms::CALLSITE_1);
466 } else {
467 (*group) = new AppCacheGroup(
468 storage_, group_record_.manifest_url,
469 group_record_.group_id);
470 group->get()->set_creation_time(group_record_.creation_time);
471 group->get()->AddCache(cache->get());
473 // TODO(michaeln): histogram is fishing for clues to crbug/95101
474 if (!cache->get()->GetEntry(group_record_.manifest_url)) {
475 AppCacheHistograms::AddMissingManifestDetectedAtCallsite(
476 AppCacheHistograms::CALLSITE_2);
479 DCHECK(group->get()->newest_complete_cache() == cache->get());
481 // We have to update foriegn entries if MarkEntryAsForeignTasks
482 // are in flight.
483 std::vector<GURL> urls;
484 storage_->GetPendingForeignMarkingsForCache(cache->get()->cache_id(), &urls);
485 for (std::vector<GURL>::iterator iter = urls.begin();
486 iter != urls.end(); ++iter) {
487 DCHECK(cache->get()->GetEntry(*iter));
488 cache->get()->GetEntry(*iter)->add_types(AppCacheEntry::FOREIGN);
491 storage_->NotifyStorageAccessed(group_record_.origin);
493 // TODO(michaeln): Maybe verify that the responses we expect to exist
494 // do actually exist in the disk_cache (and if not then what?)
497 // CacheLoadTask -------
499 class AppCacheStorageImpl::CacheLoadTask : public StoreOrLoadTask {
500 public:
501 CacheLoadTask(int64 cache_id, AppCacheStorageImpl* storage)
502 : StoreOrLoadTask(storage), cache_id_(cache_id),
503 success_(false) {}
505 // DatabaseTask:
506 void Run() override;
507 void RunCompleted() override;
509 protected:
510 ~CacheLoadTask() override {}
512 private:
513 int64 cache_id_;
514 bool success_;
517 void AppCacheStorageImpl::CacheLoadTask::Run() {
518 tracked_objects::ScopedTracker tracking_profile(
519 FROM_HERE_WITH_EXPLICIT_FUNCTION("AppCacheStorageImpl::CacheLoadTask"));
520 success_ =
521 database_->FindCache(cache_id_, &cache_record_) &&
522 database_->FindGroup(cache_record_.group_id, &group_record_) &&
523 FindRelatedCacheRecords(cache_id_);
525 if (success_)
526 database_->LazyUpdateLastAccessTime(group_record_.group_id,
527 base::Time::Now());
530 void AppCacheStorageImpl::CacheLoadTask::RunCompleted() {
531 storage_->pending_cache_loads_.erase(cache_id_);
532 scoped_refptr<AppCache> cache;
533 scoped_refptr<AppCacheGroup> group;
534 if (success_ && !storage_->is_disabled()) {
535 storage_->LazilyCommitLastAccessTimes();
536 DCHECK(cache_record_.cache_id == cache_id_);
537 CreateCacheAndGroupFromRecords(&cache, &group);
539 FOR_EACH_DELEGATE(delegates_, OnCacheLoaded(cache.get(), cache_id_));
542 // GroupLoadTask -------
544 class AppCacheStorageImpl::GroupLoadTask : public StoreOrLoadTask {
545 public:
546 GroupLoadTask(GURL manifest_url, AppCacheStorageImpl* storage)
547 : StoreOrLoadTask(storage), manifest_url_(manifest_url),
548 success_(false) {}
550 // DatabaseTask:
551 void Run() override;
552 void RunCompleted() override;
554 protected:
555 ~GroupLoadTask() override {}
557 private:
558 GURL manifest_url_;
559 bool success_;
562 void AppCacheStorageImpl::GroupLoadTask::Run() {
563 tracked_objects::ScopedTracker tracking_profile(
564 FROM_HERE_WITH_EXPLICIT_FUNCTION("AppCacheStorageImpl::GroupLoadTask"));
565 success_ =
566 database_->FindGroupForManifestUrl(manifest_url_, &group_record_) &&
567 database_->FindCacheForGroup(group_record_.group_id, &cache_record_) &&
568 FindRelatedCacheRecords(cache_record_.cache_id);
570 if (success_)
571 database_->LazyUpdateLastAccessTime(group_record_.group_id,
572 base::Time::Now());
575 void AppCacheStorageImpl::GroupLoadTask::RunCompleted() {
576 storage_->pending_group_loads_.erase(manifest_url_);
577 scoped_refptr<AppCacheGroup> group;
578 scoped_refptr<AppCache> cache;
579 if (!storage_->is_disabled()) {
580 if (success_) {
581 storage_->LazilyCommitLastAccessTimes();
582 DCHECK(group_record_.manifest_url == manifest_url_);
583 CreateCacheAndGroupFromRecords(&cache, &group);
584 } else {
585 group = storage_->working_set_.GetGroup(manifest_url_);
586 if (!group.get()) {
587 group =
588 new AppCacheGroup(storage_, manifest_url_, storage_->NewGroupId());
592 FOR_EACH_DELEGATE(delegates_, OnGroupLoaded(group.get(), manifest_url_));
595 // StoreGroupAndCacheTask -------
597 class AppCacheStorageImpl::StoreGroupAndCacheTask : public StoreOrLoadTask {
598 public:
599 StoreGroupAndCacheTask(AppCacheStorageImpl* storage, AppCacheGroup* group,
600 AppCache* newest_cache);
602 void GetQuotaThenSchedule();
603 void OnQuotaCallback(storage::QuotaStatusCode status,
604 int64 usage,
605 int64 quota);
607 // DatabaseTask:
608 void Run() override;
609 void RunCompleted() override;
610 void CancelCompletion() override;
612 protected:
613 ~StoreGroupAndCacheTask() override {}
615 private:
616 scoped_refptr<AppCacheGroup> group_;
617 scoped_refptr<AppCache> cache_;
618 bool success_;
619 bool would_exceed_quota_;
620 int64 space_available_;
621 int64 new_origin_usage_;
622 std::vector<int64> newly_deletable_response_ids_;
625 AppCacheStorageImpl::StoreGroupAndCacheTask::StoreGroupAndCacheTask(
626 AppCacheStorageImpl* storage, AppCacheGroup* group, AppCache* newest_cache)
627 : StoreOrLoadTask(storage), group_(group), cache_(newest_cache),
628 success_(false), would_exceed_quota_(false),
629 space_available_(-1), new_origin_usage_(-1) {
630 group_record_.group_id = group->group_id();
631 group_record_.manifest_url = group->manifest_url();
632 group_record_.origin = group_record_.manifest_url.GetOrigin();
633 newest_cache->ToDatabaseRecords(
634 group,
635 &cache_record_, &entry_records_,
636 &intercept_namespace_records_,
637 &fallback_namespace_records_,
638 &online_whitelist_records_);
641 void AppCacheStorageImpl::StoreGroupAndCacheTask::GetQuotaThenSchedule() {
642 storage::QuotaManager* quota_manager = NULL;
643 if (storage_->service()->quota_manager_proxy()) {
644 quota_manager =
645 storage_->service()->quota_manager_proxy()->quota_manager();
648 if (!quota_manager) {
649 if (storage_->service()->special_storage_policy() &&
650 storage_->service()->special_storage_policy()->IsStorageUnlimited(
651 group_record_.origin))
652 space_available_ = kint64max;
653 Schedule();
654 return;
657 // We have to ask the quota manager for the value.
658 storage_->pending_quota_queries_.insert(this);
659 quota_manager->GetUsageAndQuota(
660 group_record_.origin,
661 storage::kStorageTypeTemporary,
662 base::Bind(&StoreGroupAndCacheTask::OnQuotaCallback, this));
665 void AppCacheStorageImpl::StoreGroupAndCacheTask::OnQuotaCallback(
666 storage::QuotaStatusCode status,
667 int64 usage,
668 int64 quota) {
669 if (storage_) {
670 if (status == storage::kQuotaStatusOk)
671 space_available_ = std::max(static_cast<int64>(0), quota - usage);
672 else
673 space_available_ = 0;
674 storage_->pending_quota_queries_.erase(this);
675 Schedule();
679 void AppCacheStorageImpl::StoreGroupAndCacheTask::Run() {
680 DCHECK(!success_);
681 sql::Connection* connection = database_->db_connection();
682 if (!connection)
683 return;
685 sql::Transaction transaction(connection);
686 if (!transaction.Begin())
687 return;
689 int64 old_origin_usage = database_->GetOriginUsage(group_record_.origin);
691 AppCacheDatabase::GroupRecord existing_group;
692 success_ = database_->FindGroup(group_record_.group_id, &existing_group);
693 if (!success_) {
694 group_record_.creation_time = base::Time::Now();
695 group_record_.last_access_time = base::Time::Now();
696 success_ = database_->InsertGroup(&group_record_);
697 } else {
698 DCHECK(group_record_.group_id == existing_group.group_id);
699 DCHECK(group_record_.manifest_url == existing_group.manifest_url);
700 DCHECK(group_record_.origin == existing_group.origin);
702 database_->UpdateLastAccessTime(group_record_.group_id,
703 base::Time::Now());
705 AppCacheDatabase::CacheRecord cache;
706 if (database_->FindCacheForGroup(group_record_.group_id, &cache)) {
707 // Get the set of response ids in the old cache.
708 std::set<int64> existing_response_ids;
709 database_->FindResponseIdsForCacheAsSet(cache.cache_id,
710 &existing_response_ids);
712 // Remove those that remain in the new cache.
713 std::vector<AppCacheDatabase::EntryRecord>::const_iterator entry_iter =
714 entry_records_.begin();
715 while (entry_iter != entry_records_.end()) {
716 existing_response_ids.erase(entry_iter->response_id);
717 ++entry_iter;
720 // The rest are deletable.
721 std::set<int64>::const_iterator id_iter = existing_response_ids.begin();
722 while (id_iter != existing_response_ids.end()) {
723 newly_deletable_response_ids_.push_back(*id_iter);
724 ++id_iter;
727 success_ =
728 database_->DeleteCache(cache.cache_id) &&
729 database_->DeleteEntriesForCache(cache.cache_id) &&
730 database_->DeleteNamespacesForCache(cache.cache_id) &&
731 database_->DeleteOnlineWhiteListForCache(cache.cache_id) &&
732 database_->InsertDeletableResponseIds(newly_deletable_response_ids_);
733 // TODO(michaeln): store group_id too with deletable ids
734 } else {
735 NOTREACHED() << "A existing group without a cache is unexpected";
739 success_ =
740 success_ &&
741 database_->InsertCache(&cache_record_) &&
742 database_->InsertEntryRecords(entry_records_) &&
743 database_->InsertNamespaceRecords(intercept_namespace_records_) &&
744 database_->InsertNamespaceRecords(fallback_namespace_records_) &&
745 database_->InsertOnlineWhiteListRecords(online_whitelist_records_);
747 if (!success_)
748 return;
750 new_origin_usage_ = database_->GetOriginUsage(group_record_.origin);
752 // Only check quota when the new usage exceeds the old usage.
753 if (new_origin_usage_ <= old_origin_usage) {
754 success_ = transaction.Commit();
755 return;
758 // Use a simple hard-coded value when not using quota management.
759 if (space_available_ == -1) {
760 if (new_origin_usage_ > kDefaultQuota) {
761 would_exceed_quota_ = true;
762 success_ = false;
763 return;
765 success_ = transaction.Commit();
766 return;
769 // Check limits based on the space availbable given to us via the
770 // quota system.
771 int64 delta = new_origin_usage_ - old_origin_usage;
772 if (delta > space_available_) {
773 would_exceed_quota_ = true;
774 success_ = false;
775 return;
778 success_ = transaction.Commit();
781 void AppCacheStorageImpl::StoreGroupAndCacheTask::RunCompleted() {
782 if (success_) {
783 storage_->UpdateUsageMapAndNotify(
784 group_->manifest_url().GetOrigin(), new_origin_usage_);
785 if (cache_.get() != group_->newest_complete_cache()) {
786 cache_->set_complete(true);
787 group_->AddCache(cache_.get());
789 if (group_->creation_time().is_null())
790 group_->set_creation_time(group_record_.creation_time);
791 group_->AddNewlyDeletableResponseIds(&newly_deletable_response_ids_);
793 FOR_EACH_DELEGATE(
794 delegates_,
795 OnGroupAndNewestCacheStored(
796 group_.get(), cache_.get(), success_, would_exceed_quota_));
797 group_ = NULL;
798 cache_ = NULL;
800 // TODO(michaeln): if (would_exceed_quota_) what if the current usage
801 // also exceeds the quota? http://crbug.com/83968
804 void AppCacheStorageImpl::StoreGroupAndCacheTask::CancelCompletion() {
805 // Overriden to safely drop our reference to the group and cache
806 // which are not thread safe refcounted.
807 DatabaseTask::CancelCompletion();
808 group_ = NULL;
809 cache_ = NULL;
812 // FindMainResponseTask -------
814 // Helpers for FindMainResponseTask::Run()
815 namespace {
816 class SortByCachePreference
817 : public std::binary_function<
818 AppCacheDatabase::EntryRecord,
819 AppCacheDatabase::EntryRecord,
820 bool> {
821 public:
822 SortByCachePreference(int64 preferred_id, const std::set<int64>& in_use_ids)
823 : preferred_id_(preferred_id), in_use_ids_(in_use_ids) {
825 bool operator()(
826 const AppCacheDatabase::EntryRecord& lhs,
827 const AppCacheDatabase::EntryRecord& rhs) {
828 return compute_value(lhs) > compute_value(rhs);
830 private:
831 int compute_value(const AppCacheDatabase::EntryRecord& entry) {
832 if (entry.cache_id == preferred_id_)
833 return 100;
834 else if (in_use_ids_.find(entry.cache_id) != in_use_ids_.end())
835 return 50;
836 return 0;
838 int64 preferred_id_;
839 const std::set<int64>& in_use_ids_;
842 bool SortByLength(
843 const AppCacheDatabase::NamespaceRecord& lhs,
844 const AppCacheDatabase::NamespaceRecord& rhs) {
845 return lhs.namespace_.namespace_url.spec().length() >
846 rhs.namespace_.namespace_url.spec().length();
849 class NetworkNamespaceHelper {
850 public:
851 explicit NetworkNamespaceHelper(AppCacheDatabase* database)
852 : database_(database) {
855 bool IsInNetworkNamespace(const GURL& url, int64 cache_id) {
856 typedef std::pair<WhiteListMap::iterator, bool> InsertResult;
857 InsertResult result = namespaces_map_.insert(
858 WhiteListMap::value_type(cache_id, AppCacheNamespaceVector()));
859 if (result.second)
860 GetOnlineWhiteListForCache(cache_id, &result.first->second);
861 return AppCache::FindNamespace(result.first->second, url) != NULL;
864 private:
865 void GetOnlineWhiteListForCache(
866 int64 cache_id, AppCacheNamespaceVector* namespaces) {
867 DCHECK(namespaces && namespaces->empty());
868 typedef std::vector<AppCacheDatabase::OnlineWhiteListRecord>
869 WhiteListVector;
870 WhiteListVector records;
871 if (!database_->FindOnlineWhiteListForCache(cache_id, &records))
872 return;
873 WhiteListVector::const_iterator iter = records.begin();
874 while (iter != records.end()) {
875 namespaces->push_back(
876 AppCacheNamespace(APPCACHE_NETWORK_NAMESPACE, iter->namespace_url,
877 GURL(), iter->is_pattern));
878 ++iter;
882 // Key is cache id
883 typedef std::map<int64, AppCacheNamespaceVector> WhiteListMap;
884 WhiteListMap namespaces_map_;
885 AppCacheDatabase* database_;
888 } // namespace
890 class AppCacheStorageImpl::FindMainResponseTask : public DatabaseTask {
891 public:
892 FindMainResponseTask(AppCacheStorageImpl* storage,
893 const GURL& url,
894 const GURL& preferred_manifest_url,
895 const AppCacheWorkingSet::GroupMap* groups_in_use)
896 : DatabaseTask(storage), url_(url),
897 preferred_manifest_url_(preferred_manifest_url),
898 cache_id_(kAppCacheNoCacheId), group_id_(0) {
899 if (groups_in_use) {
900 for (AppCacheWorkingSet::GroupMap::const_iterator it =
901 groups_in_use->begin();
902 it != groups_in_use->end(); ++it) {
903 AppCacheGroup* group = it->second;
904 AppCache* cache = group->newest_complete_cache();
905 if (group->is_obsolete() || !cache)
906 continue;
907 cache_ids_in_use_.insert(cache->cache_id());
912 // DatabaseTask:
913 void Run() override;
914 void RunCompleted() override;
916 protected:
917 ~FindMainResponseTask() override {}
919 private:
920 typedef std::vector<AppCacheDatabase::NamespaceRecord*>
921 NamespaceRecordPtrVector;
923 bool FindExactMatch(int64 preferred_id);
924 bool FindNamespaceMatch(int64 preferred_id);
925 bool FindNamespaceHelper(
926 int64 preferred_cache_id,
927 AppCacheDatabase::NamespaceRecordVector* namespaces,
928 NetworkNamespaceHelper* network_namespace_helper);
929 bool FindFirstValidNamespace(const NamespaceRecordPtrVector& namespaces);
931 GURL url_;
932 GURL preferred_manifest_url_;
933 std::set<int64> cache_ids_in_use_;
934 AppCacheEntry entry_;
935 AppCacheEntry fallback_entry_;
936 GURL namespace_entry_url_;
937 int64 cache_id_;
938 int64 group_id_;
939 GURL manifest_url_;
942 void AppCacheStorageImpl::FindMainResponseTask::Run() {
943 tracked_objects::ScopedTracker tracking_profile(
944 FROM_HERE_WITH_EXPLICIT_FUNCTION(
945 "AppCacheStorageImpl::FindMainResponseTask"));
946 // NOTE: The heuristics around choosing amoungst multiple candidates
947 // is underspecified, and just plain not fully understood. This needs
948 // to be refined.
950 // The 'preferred_manifest_url' is the url of the manifest associated
951 // with the page that opened or embedded the page being loaded now.
952 // We have a strong preference to use resources from that cache.
953 // We also have a lesser bias to use resources from caches that are currently
954 // being used by other unrelated pages.
955 // TODO(michaeln): come up with a 'preferred_manifest_url' in more cases
956 // - when navigating a frame whose current contents are from an appcache
957 // - when clicking an href in a frame that is appcached
958 int64 preferred_cache_id = kAppCacheNoCacheId;
959 if (!preferred_manifest_url_.is_empty()) {
960 AppCacheDatabase::GroupRecord preferred_group;
961 AppCacheDatabase::CacheRecord preferred_cache;
962 if (database_->FindGroupForManifestUrl(
963 preferred_manifest_url_, &preferred_group) &&
964 database_->FindCacheForGroup(
965 preferred_group.group_id, &preferred_cache)) {
966 preferred_cache_id = preferred_cache.cache_id;
970 if (FindExactMatch(preferred_cache_id) ||
971 FindNamespaceMatch(preferred_cache_id)) {
972 // We found something.
973 DCHECK(cache_id_ != kAppCacheNoCacheId && !manifest_url_.is_empty() &&
974 group_id_ != 0);
975 return;
978 // We didn't find anything.
979 DCHECK(cache_id_ == kAppCacheNoCacheId && manifest_url_.is_empty() &&
980 group_id_ == 0);
983 bool AppCacheStorageImpl::
984 FindMainResponseTask::FindExactMatch(int64 preferred_cache_id) {
985 std::vector<AppCacheDatabase::EntryRecord> entries;
986 if (database_->FindEntriesForUrl(url_, &entries) && !entries.empty()) {
987 // Sort them in order of preference, from the preferred_cache first,
988 // followed by hits from caches that are 'in use', then the rest.
989 std::sort(entries.begin(), entries.end(),
990 SortByCachePreference(preferred_cache_id, cache_ids_in_use_));
992 // Take the first with a valid, non-foreign entry.
993 std::vector<AppCacheDatabase::EntryRecord>::iterator iter;
994 for (iter = entries.begin(); iter < entries.end(); ++iter) {
995 AppCacheDatabase::GroupRecord group_record;
996 if ((iter->flags & AppCacheEntry::FOREIGN) ||
997 !database_->FindGroupForCache(iter->cache_id, &group_record)) {
998 continue;
1000 manifest_url_ = group_record.manifest_url;
1001 group_id_ = group_record.group_id;
1002 entry_ = AppCacheEntry(iter->flags, iter->response_id);
1003 cache_id_ = iter->cache_id;
1004 return true; // We found an exact match.
1007 return false;
1010 bool AppCacheStorageImpl::
1011 FindMainResponseTask::FindNamespaceMatch(int64 preferred_cache_id) {
1012 AppCacheDatabase::NamespaceRecordVector all_intercepts;
1013 AppCacheDatabase::NamespaceRecordVector all_fallbacks;
1014 if (!database_->FindNamespacesForOrigin(
1015 url_.GetOrigin(), &all_intercepts, &all_fallbacks)
1016 || (all_intercepts.empty() && all_fallbacks.empty())) {
1017 return false;
1020 NetworkNamespaceHelper network_namespace_helper(database_);
1021 if (FindNamespaceHelper(preferred_cache_id,
1022 &all_intercepts,
1023 &network_namespace_helper) ||
1024 FindNamespaceHelper(preferred_cache_id,
1025 &all_fallbacks,
1026 &network_namespace_helper)) {
1027 return true;
1029 return false;
1032 bool AppCacheStorageImpl::
1033 FindMainResponseTask::FindNamespaceHelper(
1034 int64 preferred_cache_id,
1035 AppCacheDatabase::NamespaceRecordVector* namespaces,
1036 NetworkNamespaceHelper* network_namespace_helper) {
1037 // Sort them by length, longer matches within the same cache/bucket take
1038 // precedence.
1039 std::sort(namespaces->begin(), namespaces->end(), SortByLength);
1041 NamespaceRecordPtrVector preferred_namespaces;
1042 NamespaceRecordPtrVector inuse_namespaces;
1043 NamespaceRecordPtrVector other_namespaces;
1044 std::vector<AppCacheDatabase::NamespaceRecord>::iterator iter;
1045 for (iter = namespaces->begin(); iter < namespaces->end(); ++iter) {
1046 // Skip those that aren't a match.
1047 if (!iter->namespace_.IsMatch(url_))
1048 continue;
1050 // Skip namespaces where the requested url falls into a network
1051 // namespace of its containing appcache.
1052 if (network_namespace_helper->IsInNetworkNamespace(url_, iter->cache_id))
1053 continue;
1055 // Bin them into one of our three buckets.
1056 if (iter->cache_id == preferred_cache_id)
1057 preferred_namespaces.push_back(&(*iter));
1058 else if (cache_ids_in_use_.find(iter->cache_id) != cache_ids_in_use_.end())
1059 inuse_namespaces.push_back(&(*iter));
1060 else
1061 other_namespaces.push_back(&(*iter));
1064 if (FindFirstValidNamespace(preferred_namespaces) ||
1065 FindFirstValidNamespace(inuse_namespaces) ||
1066 FindFirstValidNamespace(other_namespaces))
1067 return true; // We found one.
1069 // We didn't find anything.
1070 return false;
1073 bool AppCacheStorageImpl::
1074 FindMainResponseTask::FindFirstValidNamespace(
1075 const NamespaceRecordPtrVector& namespaces) {
1076 // Take the first with a valid, non-foreign entry.
1077 NamespaceRecordPtrVector::const_iterator iter;
1078 for (iter = namespaces.begin(); iter < namespaces.end(); ++iter) {
1079 AppCacheDatabase::EntryRecord entry_record;
1080 if (database_->FindEntry((*iter)->cache_id, (*iter)->namespace_.target_url,
1081 &entry_record)) {
1082 AppCacheDatabase::GroupRecord group_record;
1083 if ((entry_record.flags & AppCacheEntry::FOREIGN) ||
1084 !database_->FindGroupForCache(entry_record.cache_id, &group_record)) {
1085 continue;
1087 manifest_url_ = group_record.manifest_url;
1088 group_id_ = group_record.group_id;
1089 cache_id_ = (*iter)->cache_id;
1090 namespace_entry_url_ = (*iter)->namespace_.target_url;
1091 if ((*iter)->namespace_.type == APPCACHE_FALLBACK_NAMESPACE)
1092 fallback_entry_ = AppCacheEntry(entry_record.flags,
1093 entry_record.response_id);
1094 else
1095 entry_ = AppCacheEntry(entry_record.flags, entry_record.response_id);
1096 return true; // We found one.
1099 return false; // We didn't find a match.
1102 void AppCacheStorageImpl::FindMainResponseTask::RunCompleted() {
1103 storage_->CallOnMainResponseFound(
1104 &delegates_, url_, entry_, namespace_entry_url_, fallback_entry_,
1105 cache_id_, group_id_, manifest_url_);
1108 // MarkEntryAsForeignTask -------
1110 class AppCacheStorageImpl::MarkEntryAsForeignTask : public DatabaseTask {
1111 public:
1112 MarkEntryAsForeignTask(
1113 AppCacheStorageImpl* storage, const GURL& url, int64 cache_id)
1114 : DatabaseTask(storage), cache_id_(cache_id), entry_url_(url) {}
1116 // DatabaseTask:
1117 void Run() override;
1118 void RunCompleted() override;
1120 protected:
1121 ~MarkEntryAsForeignTask() override {}
1123 private:
1124 int64 cache_id_;
1125 GURL entry_url_;
1128 void AppCacheStorageImpl::MarkEntryAsForeignTask::Run() {
1129 database_->AddEntryFlags(entry_url_, cache_id_, AppCacheEntry::FOREIGN);
1132 void AppCacheStorageImpl::MarkEntryAsForeignTask::RunCompleted() {
1133 DCHECK(storage_->pending_foreign_markings_.front().first == entry_url_ &&
1134 storage_->pending_foreign_markings_.front().second == cache_id_);
1135 storage_->pending_foreign_markings_.pop_front();
1138 // MakeGroupObsoleteTask -------
1140 class AppCacheStorageImpl::MakeGroupObsoleteTask : public DatabaseTask {
1141 public:
1142 MakeGroupObsoleteTask(AppCacheStorageImpl* storage,
1143 AppCacheGroup* group,
1144 int response_code);
1146 // DatabaseTask:
1147 void Run() override;
1148 void RunCompleted() override;
1149 void CancelCompletion() override;
1151 protected:
1152 ~MakeGroupObsoleteTask() override {}
1154 private:
1155 scoped_refptr<AppCacheGroup> group_;
1156 int64 group_id_;
1157 GURL origin_;
1158 bool success_;
1159 int response_code_;
1160 int64 new_origin_usage_;
1161 std::vector<int64> newly_deletable_response_ids_;
1164 AppCacheStorageImpl::MakeGroupObsoleteTask::MakeGroupObsoleteTask(
1165 AppCacheStorageImpl* storage,
1166 AppCacheGroup* group,
1167 int response_code)
1168 : DatabaseTask(storage),
1169 group_(group),
1170 group_id_(group->group_id()),
1171 origin_(group->manifest_url().GetOrigin()),
1172 success_(false),
1173 response_code_(response_code),
1174 new_origin_usage_(-1) {}
1176 void AppCacheStorageImpl::MakeGroupObsoleteTask::Run() {
1177 DCHECK(!success_);
1178 sql::Connection* connection = database_->db_connection();
1179 if (!connection)
1180 return;
1182 sql::Transaction transaction(connection);
1183 if (!transaction.Begin())
1184 return;
1186 AppCacheDatabase::GroupRecord group_record;
1187 if (!database_->FindGroup(group_id_, &group_record)) {
1188 // This group doesn't exists in the database, nothing todo here.
1189 new_origin_usage_ = database_->GetOriginUsage(origin_);
1190 success_ = true;
1191 return;
1194 DCHECK_EQ(group_record.origin, origin_);
1195 success_ = DeleteGroupAndRelatedRecords(database_,
1196 group_id_,
1197 &newly_deletable_response_ids_);
1199 new_origin_usage_ = database_->GetOriginUsage(origin_);
1200 success_ = success_ && transaction.Commit();
1203 void AppCacheStorageImpl::MakeGroupObsoleteTask::RunCompleted() {
1204 if (success_) {
1205 group_->set_obsolete(true);
1206 if (!storage_->is_disabled()) {
1207 storage_->UpdateUsageMapAndNotify(origin_, new_origin_usage_);
1208 group_->AddNewlyDeletableResponseIds(&newly_deletable_response_ids_);
1210 // Also remove from the working set, caches for an 'obsolete' group
1211 // may linger in use, but the group itself cannot be looked up by
1212 // 'manifest_url' in the working set any longer.
1213 storage_->working_set()->RemoveGroup(group_.get());
1216 FOR_EACH_DELEGATE(
1217 delegates_, OnGroupMadeObsolete(group_.get(), success_, response_code_));
1218 group_ = NULL;
1221 void AppCacheStorageImpl::MakeGroupObsoleteTask::CancelCompletion() {
1222 // Overriden to safely drop our reference to the group
1223 // which is not thread safe refcounted.
1224 DatabaseTask::CancelCompletion();
1225 group_ = NULL;
1228 // GetDeletableResponseIdsTask -------
1230 class AppCacheStorageImpl::GetDeletableResponseIdsTask : public DatabaseTask {
1231 public:
1232 GetDeletableResponseIdsTask(AppCacheStorageImpl* storage, int64 max_rowid)
1233 : DatabaseTask(storage), max_rowid_(max_rowid) {}
1235 // DatabaseTask:
1236 void Run() override;
1237 void RunCompleted() override;
1239 protected:
1240 ~GetDeletableResponseIdsTask() override {}
1242 private:
1243 int64 max_rowid_;
1244 std::vector<int64> response_ids_;
1247 void AppCacheStorageImpl::GetDeletableResponseIdsTask::Run() {
1248 const int kSqlLimit = 1000;
1249 database_->GetDeletableResponseIds(&response_ids_, max_rowid_, kSqlLimit);
1250 // TODO(michaeln): retrieve group_ids too
1253 void AppCacheStorageImpl::GetDeletableResponseIdsTask::RunCompleted() {
1254 if (!response_ids_.empty())
1255 storage_->StartDeletingResponses(response_ids_);
1258 // InsertDeletableResponseIdsTask -------
1260 class AppCacheStorageImpl::InsertDeletableResponseIdsTask
1261 : public DatabaseTask {
1262 public:
1263 explicit InsertDeletableResponseIdsTask(AppCacheStorageImpl* storage)
1264 : DatabaseTask(storage) {}
1266 // DatabaseTask:
1267 void Run() override;
1269 std::vector<int64> response_ids_;
1271 protected:
1272 ~InsertDeletableResponseIdsTask() override {}
1275 void AppCacheStorageImpl::InsertDeletableResponseIdsTask::Run() {
1276 database_->InsertDeletableResponseIds(response_ids_);
1277 // TODO(michaeln): store group_ids too
1280 // DeleteDeletableResponseIdsTask -------
1282 class AppCacheStorageImpl::DeleteDeletableResponseIdsTask
1283 : public DatabaseTask {
1284 public:
1285 explicit DeleteDeletableResponseIdsTask(AppCacheStorageImpl* storage)
1286 : DatabaseTask(storage) {}
1288 // DatabaseTask:
1289 void Run() override;
1291 std::vector<int64> response_ids_;
1293 protected:
1294 ~DeleteDeletableResponseIdsTask() override {}
1297 void AppCacheStorageImpl::DeleteDeletableResponseIdsTask::Run() {
1298 database_->DeleteDeletableResponseIds(response_ids_);
1301 // LazyUpdateLastAccessTimeTask -------
1303 class AppCacheStorageImpl::LazyUpdateLastAccessTimeTask
1304 : public DatabaseTask {
1305 public:
1306 LazyUpdateLastAccessTimeTask(
1307 AppCacheStorageImpl* storage, AppCacheGroup* group, base::Time time)
1308 : DatabaseTask(storage), group_id_(group->group_id()),
1309 last_access_time_(time) {
1310 storage->NotifyStorageAccessed(group->manifest_url().GetOrigin());
1313 // DatabaseTask:
1314 void Run() override;
1315 void RunCompleted() override;
1317 protected:
1318 ~LazyUpdateLastAccessTimeTask() override {}
1320 private:
1321 int64 group_id_;
1322 base::Time last_access_time_;
1325 void AppCacheStorageImpl::LazyUpdateLastAccessTimeTask::Run() {
1326 tracked_objects::ScopedTracker tracking_profile(
1327 FROM_HERE_WITH_EXPLICIT_FUNCTION(
1328 "AppCacheStorageImpl::LazyUpdateLastAccessTimeTask"));
1329 database_->LazyUpdateLastAccessTime(group_id_, last_access_time_);
1332 void AppCacheStorageImpl::LazyUpdateLastAccessTimeTask::RunCompleted() {
1333 storage_->LazilyCommitLastAccessTimes();
1336 // CommitLastAccessTimesTask -------
1338 class AppCacheStorageImpl::CommitLastAccessTimesTask
1339 : public DatabaseTask {
1340 public:
1341 CommitLastAccessTimesTask(AppCacheStorageImpl* storage)
1342 : DatabaseTask(storage) {}
1344 // DatabaseTask:
1345 void Run() override {
1346 tracked_objects::ScopedTracker tracking_profile(
1347 FROM_HERE_WITH_EXPLICIT_FUNCTION(
1348 "AppCacheStorageImpl::CommitLastAccessTimesTask"));
1349 database_->CommitLazyLastAccessTimes();
1352 protected:
1353 ~CommitLastAccessTimesTask() override {}
1356 // AppCacheStorageImpl ---------------------------------------------------
1358 AppCacheStorageImpl::AppCacheStorageImpl(AppCacheServiceImpl* service)
1359 : AppCacheStorage(service),
1360 is_incognito_(false),
1361 is_response_deletion_scheduled_(false),
1362 did_start_deleting_responses_(false),
1363 last_deletable_response_rowid_(0),
1364 database_(NULL),
1365 is_disabled_(false),
1366 weak_factory_(this) {
1369 AppCacheStorageImpl::~AppCacheStorageImpl() {
1370 std::for_each(pending_quota_queries_.begin(),
1371 pending_quota_queries_.end(),
1372 std::mem_fun(&DatabaseTask::CancelCompletion));
1373 std::for_each(scheduled_database_tasks_.begin(),
1374 scheduled_database_tasks_.end(),
1375 std::mem_fun(&DatabaseTask::CancelCompletion));
1377 if (database_ &&
1378 !db_thread_->PostTask(
1379 FROM_HERE,
1380 base::Bind(&ClearSessionOnlyOrigins,
1381 database_,
1382 make_scoped_refptr(service_->special_storage_policy()),
1383 service()->force_keep_session_state()))) {
1384 delete database_;
1386 database_ = NULL; // So no further database tasks can be scheduled.
1389 void AppCacheStorageImpl::Initialize(
1390 const base::FilePath& cache_directory,
1391 const scoped_refptr<base::SingleThreadTaskRunner>& db_thread,
1392 const scoped_refptr<base::SingleThreadTaskRunner>& cache_thread) {
1393 DCHECK(db_thread.get());
1395 cache_directory_ = cache_directory;
1396 is_incognito_ = cache_directory_.empty();
1398 base::FilePath db_file_path;
1399 if (!is_incognito_)
1400 db_file_path = cache_directory_.Append(kAppCacheDatabaseName);
1401 database_ = new AppCacheDatabase(db_file_path);
1403 db_thread_ = db_thread;
1404 cache_thread_ = cache_thread;
1406 scoped_refptr<InitTask> task(new InitTask(this));
1407 task->Schedule();
1410 void AppCacheStorageImpl::Disable() {
1411 if (is_disabled_)
1412 return;
1413 VLOG(1) << "Disabling appcache storage.";
1414 is_disabled_ = true;
1415 ClearUsageMapAndNotify();
1416 working_set()->Disable();
1417 if (disk_cache_)
1418 disk_cache_->Disable();
1419 scoped_refptr<DisableDatabaseTask> task(new DisableDatabaseTask(this));
1420 task->Schedule();
1423 void AppCacheStorageImpl::GetAllInfo(Delegate* delegate) {
1424 DCHECK(delegate);
1425 scoped_refptr<GetAllInfoTask> task(new GetAllInfoTask(this));
1426 task->AddDelegate(GetOrCreateDelegateReference(delegate));
1427 task->Schedule();
1430 void AppCacheStorageImpl::LoadCache(int64 id, Delegate* delegate) {
1431 DCHECK(delegate);
1432 if (is_disabled_) {
1433 delegate->OnCacheLoaded(NULL, id);
1434 return;
1437 AppCache* cache = working_set_.GetCache(id);
1438 if (cache) {
1439 delegate->OnCacheLoaded(cache, id);
1440 if (cache->owning_group()) {
1441 scoped_refptr<DatabaseTask> update_task(
1442 new LazyUpdateLastAccessTimeTask(
1443 this, cache->owning_group(), base::Time::Now()));
1444 update_task->Schedule();
1446 return;
1448 scoped_refptr<CacheLoadTask> task(GetPendingCacheLoadTask(id));
1449 if (task.get()) {
1450 task->AddDelegate(GetOrCreateDelegateReference(delegate));
1451 return;
1453 task = new CacheLoadTask(id, this);
1454 task->AddDelegate(GetOrCreateDelegateReference(delegate));
1455 task->Schedule();
1456 pending_cache_loads_[id] = task.get();
1459 void AppCacheStorageImpl::LoadOrCreateGroup(
1460 const GURL& manifest_url, Delegate* delegate) {
1461 DCHECK(delegate);
1462 if (is_disabled_) {
1463 delegate->OnGroupLoaded(NULL, manifest_url);
1464 return;
1467 AppCacheGroup* group = working_set_.GetGroup(manifest_url);
1468 if (group) {
1469 delegate->OnGroupLoaded(group, manifest_url);
1470 scoped_refptr<DatabaseTask> update_task(
1471 new LazyUpdateLastAccessTimeTask(
1472 this, group, base::Time::Now()));
1473 update_task->Schedule();
1474 return;
1477 scoped_refptr<GroupLoadTask> task(GetPendingGroupLoadTask(manifest_url));
1478 if (task.get()) {
1479 task->AddDelegate(GetOrCreateDelegateReference(delegate));
1480 return;
1483 if (usage_map_.find(manifest_url.GetOrigin()) == usage_map_.end()) {
1484 // No need to query the database, return a new group immediately.
1485 scoped_refptr<AppCacheGroup> group(new AppCacheGroup(
1486 this, manifest_url, NewGroupId()));
1487 delegate->OnGroupLoaded(group.get(), manifest_url);
1488 return;
1491 task = new GroupLoadTask(manifest_url, this);
1492 task->AddDelegate(GetOrCreateDelegateReference(delegate));
1493 task->Schedule();
1494 pending_group_loads_[manifest_url] = task.get();
1497 void AppCacheStorageImpl::StoreGroupAndNewestCache(
1498 AppCacheGroup* group, AppCache* newest_cache, Delegate* delegate) {
1499 // TODO(michaeln): distinguish between a simple update of an existing
1500 // cache that just adds new master entry(s), and the insertion of a
1501 // whole new cache. The StoreGroupAndCacheTask as written will handle
1502 // the simple update case in a very heavy weight way (delete all and
1503 // the reinsert all over again).
1504 DCHECK(group && delegate && newest_cache);
1505 scoped_refptr<StoreGroupAndCacheTask> task(
1506 new StoreGroupAndCacheTask(this, group, newest_cache));
1507 task->AddDelegate(GetOrCreateDelegateReference(delegate));
1508 task->GetQuotaThenSchedule();
1510 // TODO(michaeln): histogram is fishing for clues to crbug/95101
1511 if (!newest_cache->GetEntry(group->manifest_url())) {
1512 AppCacheHistograms::AddMissingManifestDetectedAtCallsite(
1513 AppCacheHistograms::CALLSITE_3);
1517 void AppCacheStorageImpl::FindResponseForMainRequest(
1518 const GURL& url, const GURL& preferred_manifest_url,
1519 Delegate* delegate) {
1520 DCHECK(delegate);
1522 const GURL* url_ptr = &url;
1523 GURL url_no_ref;
1524 if (url.has_ref()) {
1525 GURL::Replacements replacements;
1526 replacements.ClearRef();
1527 url_no_ref = url.ReplaceComponents(replacements);
1528 url_ptr = &url_no_ref;
1531 const GURL origin = url.GetOrigin();
1533 // First look in our working set for a direct hit without having to query
1534 // the database.
1535 const AppCacheWorkingSet::GroupMap* groups_in_use =
1536 working_set()->GetGroupsInOrigin(origin);
1537 if (groups_in_use) {
1538 if (!preferred_manifest_url.is_empty()) {
1539 AppCacheWorkingSet::GroupMap::const_iterator found =
1540 groups_in_use->find(preferred_manifest_url);
1541 if (found != groups_in_use->end() &&
1542 FindResponseForMainRequestInGroup(
1543 found->second, *url_ptr, delegate)) {
1544 return;
1546 } else {
1547 for (AppCacheWorkingSet::GroupMap::const_iterator it =
1548 groups_in_use->begin();
1549 it != groups_in_use->end(); ++it) {
1550 if (FindResponseForMainRequestInGroup(
1551 it->second, *url_ptr, delegate)) {
1552 return;
1558 if (IsInitTaskComplete() && usage_map_.find(origin) == usage_map_.end()) {
1559 // No need to query the database, return async'ly but without going thru
1560 // the DB thread.
1561 scoped_refptr<AppCacheGroup> no_group;
1562 scoped_refptr<AppCache> no_cache;
1563 ScheduleSimpleTask(
1564 base::Bind(&AppCacheStorageImpl::DeliverShortCircuitedFindMainResponse,
1565 weak_factory_.GetWeakPtr(), url, AppCacheEntry(), no_group,
1566 no_cache,
1567 make_scoped_refptr(GetOrCreateDelegateReference(delegate))));
1568 return;
1571 // We have to query the database, schedule a database task to do so.
1572 scoped_refptr<FindMainResponseTask> task(
1573 new FindMainResponseTask(this, *url_ptr, preferred_manifest_url,
1574 groups_in_use));
1575 task->AddDelegate(GetOrCreateDelegateReference(delegate));
1576 task->Schedule();
1579 bool AppCacheStorageImpl::FindResponseForMainRequestInGroup(
1580 AppCacheGroup* group, const GURL& url, Delegate* delegate) {
1581 AppCache* cache = group->newest_complete_cache();
1582 if (group->is_obsolete() || !cache)
1583 return false;
1585 AppCacheEntry* entry = cache->GetEntry(url);
1586 if (!entry || entry->IsForeign())
1587 return false;
1589 ScheduleSimpleTask(
1590 base::Bind(&AppCacheStorageImpl::DeliverShortCircuitedFindMainResponse,
1591 weak_factory_.GetWeakPtr(), url, *entry,
1592 make_scoped_refptr(group), make_scoped_refptr(cache),
1593 make_scoped_refptr(GetOrCreateDelegateReference(delegate))));
1594 return true;
1597 void AppCacheStorageImpl::DeliverShortCircuitedFindMainResponse(
1598 const GURL& url,
1599 const AppCacheEntry& found_entry,
1600 scoped_refptr<AppCacheGroup> group,
1601 scoped_refptr<AppCache> cache,
1602 scoped_refptr<DelegateReference> delegate_ref) {
1603 if (delegate_ref->delegate) {
1604 DelegateReferenceVector delegates(1, delegate_ref);
1605 CallOnMainResponseFound(
1606 &delegates, url, found_entry,
1607 GURL(), AppCacheEntry(),
1608 cache.get() ? cache->cache_id() : kAppCacheNoCacheId,
1609 group.get() ? group->group_id() : kAppCacheNoCacheId,
1610 group.get() ? group->manifest_url() : GURL());
1614 void AppCacheStorageImpl::CallOnMainResponseFound(
1615 DelegateReferenceVector* delegates,
1616 const GURL& url, const AppCacheEntry& entry,
1617 const GURL& namespace_entry_url, const AppCacheEntry& fallback_entry,
1618 int64 cache_id, int64 group_id, const GURL& manifest_url) {
1619 FOR_EACH_DELEGATE(
1620 (*delegates),
1621 OnMainResponseFound(url, entry,
1622 namespace_entry_url, fallback_entry,
1623 cache_id, group_id, manifest_url));
1626 void AppCacheStorageImpl::FindResponseForSubRequest(
1627 AppCache* cache, const GURL& url,
1628 AppCacheEntry* found_entry, AppCacheEntry* found_fallback_entry,
1629 bool* found_network_namespace) {
1630 DCHECK(cache && cache->is_complete());
1632 // When a group is forcibly deleted, all subresource loads for pages
1633 // using caches in the group will result in a synthesized network errors.
1634 // Forcible deletion is not a function that is covered by the HTML5 spec.
1635 if (cache->owning_group()->is_being_deleted()) {
1636 *found_entry = AppCacheEntry();
1637 *found_fallback_entry = AppCacheEntry();
1638 *found_network_namespace = false;
1639 return;
1642 GURL fallback_namespace_not_used;
1643 GURL intercept_namespace_not_used;
1644 cache->FindResponseForRequest(
1645 url, found_entry, &intercept_namespace_not_used,
1646 found_fallback_entry, &fallback_namespace_not_used,
1647 found_network_namespace);
1650 void AppCacheStorageImpl::MarkEntryAsForeign(
1651 const GURL& entry_url, int64 cache_id) {
1652 AppCache* cache = working_set_.GetCache(cache_id);
1653 if (cache) {
1654 AppCacheEntry* entry = cache->GetEntry(entry_url);
1655 DCHECK(entry);
1656 if (entry)
1657 entry->add_types(AppCacheEntry::FOREIGN);
1659 scoped_refptr<MarkEntryAsForeignTask> task(
1660 new MarkEntryAsForeignTask(this, entry_url, cache_id));
1661 task->Schedule();
1662 pending_foreign_markings_.push_back(std::make_pair(entry_url, cache_id));
1665 void AppCacheStorageImpl::MakeGroupObsolete(AppCacheGroup* group,
1666 Delegate* delegate,
1667 int response_code) {
1668 DCHECK(group && delegate);
1669 scoped_refptr<MakeGroupObsoleteTask> task(
1670 new MakeGroupObsoleteTask(this, group, response_code));
1671 task->AddDelegate(GetOrCreateDelegateReference(delegate));
1672 task->Schedule();
1675 AppCacheResponseReader* AppCacheStorageImpl::CreateResponseReader(
1676 const GURL& manifest_url, int64 group_id, int64 response_id) {
1677 return new AppCacheResponseReader(response_id, group_id, disk_cache());
1680 AppCacheResponseWriter* AppCacheStorageImpl::CreateResponseWriter(
1681 const GURL& manifest_url, int64 group_id) {
1682 return new AppCacheResponseWriter(NewResponseId(), group_id, disk_cache());
1685 AppCacheResponseMetadataWriter*
1686 AppCacheStorageImpl::CreateResponseMetadataWriter(int64 group_id,
1687 int64 response_id) {
1688 return new AppCacheResponseMetadataWriter(response_id, group_id,
1689 disk_cache());
1692 void AppCacheStorageImpl::DoomResponses(
1693 const GURL& manifest_url, const std::vector<int64>& response_ids) {
1694 if (response_ids.empty())
1695 return;
1697 // Start deleting them from the disk cache lazily.
1698 StartDeletingResponses(response_ids);
1700 // Also schedule a database task to record these ids in the
1701 // deletable responses table.
1702 // TODO(michaeln): There is a race here. If the browser crashes
1703 // prior to committing these rows to the database and prior to us
1704 // having deleted them from the disk cache, we'll never delete them.
1705 scoped_refptr<InsertDeletableResponseIdsTask> task(
1706 new InsertDeletableResponseIdsTask(this));
1707 task->response_ids_ = response_ids;
1708 task->Schedule();
1711 void AppCacheStorageImpl::DeleteResponses(
1712 const GURL& manifest_url, const std::vector<int64>& response_ids) {
1713 if (response_ids.empty())
1714 return;
1715 StartDeletingResponses(response_ids);
1718 void AppCacheStorageImpl::DelayedStartDeletingUnusedResponses() {
1719 // Only if we haven't already begun.
1720 if (!did_start_deleting_responses_) {
1721 scoped_refptr<GetDeletableResponseIdsTask> task(
1722 new GetDeletableResponseIdsTask(this, last_deletable_response_rowid_));
1723 task->Schedule();
1727 void AppCacheStorageImpl::StartDeletingResponses(
1728 const std::vector<int64>& response_ids) {
1729 DCHECK(!response_ids.empty());
1730 did_start_deleting_responses_ = true;
1731 deletable_response_ids_.insert(
1732 deletable_response_ids_.end(),
1733 response_ids.begin(), response_ids.end());
1734 if (!is_response_deletion_scheduled_)
1735 ScheduleDeleteOneResponse();
1738 void AppCacheStorageImpl::ScheduleDeleteOneResponse() {
1739 DCHECK(!is_response_deletion_scheduled_);
1740 const base::TimeDelta kBriefDelay = base::TimeDelta::FromMilliseconds(10);
1741 base::MessageLoop::current()->PostDelayedTask(
1742 FROM_HERE,
1743 base::Bind(&AppCacheStorageImpl::DeleteOneResponse,
1744 weak_factory_.GetWeakPtr()),
1745 kBriefDelay);
1746 is_response_deletion_scheduled_ = true;
1749 void AppCacheStorageImpl::DeleteOneResponse() {
1750 DCHECK(is_response_deletion_scheduled_);
1751 DCHECK(!deletable_response_ids_.empty());
1753 if (!disk_cache()) {
1754 DCHECK(is_disabled_);
1755 deletable_response_ids_.clear();
1756 deleted_response_ids_.clear();
1757 is_response_deletion_scheduled_ = false;
1758 return;
1761 // TODO(michaeln): add group_id to DoomEntry args
1762 int64 id = deletable_response_ids_.front();
1763 int rv = disk_cache_->DoomEntry(
1764 id, base::Bind(&AppCacheStorageImpl::OnDeletedOneResponse,
1765 base::Unretained(this)));
1766 if (rv != net::ERR_IO_PENDING)
1767 OnDeletedOneResponse(rv);
1770 void AppCacheStorageImpl::OnDeletedOneResponse(int rv) {
1771 is_response_deletion_scheduled_ = false;
1772 if (is_disabled_)
1773 return;
1775 int64 id = deletable_response_ids_.front();
1776 deletable_response_ids_.pop_front();
1777 if (rv != net::ERR_ABORTED)
1778 deleted_response_ids_.push_back(id);
1780 const size_t kBatchSize = 50U;
1781 if (deleted_response_ids_.size() >= kBatchSize ||
1782 deletable_response_ids_.empty()) {
1783 scoped_refptr<DeleteDeletableResponseIdsTask> task(
1784 new DeleteDeletableResponseIdsTask(this));
1785 task->response_ids_.swap(deleted_response_ids_);
1786 task->Schedule();
1789 if (deletable_response_ids_.empty()) {
1790 scoped_refptr<GetDeletableResponseIdsTask> task(
1791 new GetDeletableResponseIdsTask(this, last_deletable_response_rowid_));
1792 task->Schedule();
1793 return;
1796 ScheduleDeleteOneResponse();
1799 AppCacheStorageImpl::CacheLoadTask*
1800 AppCacheStorageImpl::GetPendingCacheLoadTask(int64 cache_id) {
1801 PendingCacheLoads::iterator found = pending_cache_loads_.find(cache_id);
1802 if (found != pending_cache_loads_.end())
1803 return found->second;
1804 return NULL;
1807 AppCacheStorageImpl::GroupLoadTask*
1808 AppCacheStorageImpl::GetPendingGroupLoadTask(const GURL& manifest_url) {
1809 PendingGroupLoads::iterator found = pending_group_loads_.find(manifest_url);
1810 if (found != pending_group_loads_.end())
1811 return found->second;
1812 return NULL;
1815 void AppCacheStorageImpl::GetPendingForeignMarkingsForCache(
1816 int64 cache_id, std::vector<GURL>* urls) {
1817 PendingForeignMarkings::iterator iter = pending_foreign_markings_.begin();
1818 while (iter != pending_foreign_markings_.end()) {
1819 if (iter->second == cache_id)
1820 urls->push_back(iter->first);
1821 ++iter;
1825 void AppCacheStorageImpl::ScheduleSimpleTask(const base::Closure& task) {
1826 pending_simple_tasks_.push_back(task);
1827 base::MessageLoop::current()->PostTask(
1828 FROM_HERE,
1829 base::Bind(&AppCacheStorageImpl::RunOnePendingSimpleTask,
1830 weak_factory_.GetWeakPtr()));
1833 void AppCacheStorageImpl::RunOnePendingSimpleTask() {
1834 DCHECK(!pending_simple_tasks_.empty());
1835 base::Closure task = pending_simple_tasks_.front();
1836 pending_simple_tasks_.pop_front();
1837 task.Run();
1840 AppCacheDiskCache* AppCacheStorageImpl::disk_cache() {
1841 DCHECK(IsInitTaskComplete());
1843 if (is_disabled_)
1844 return NULL;
1846 if (!disk_cache_) {
1847 int rv = net::OK;
1848 disk_cache_.reset(new AppCacheDiskCache);
1849 if (is_incognito_) {
1850 rv = disk_cache_->InitWithMemBackend(
1851 kMaxMemDiskCacheSize,
1852 base::Bind(&AppCacheStorageImpl::OnDiskCacheInitialized,
1853 base::Unretained(this)));
1854 } else {
1855 rv = disk_cache_->InitWithDiskBackend(
1856 cache_directory_.Append(kDiskCacheDirectoryName),
1857 kMaxDiskCacheSize,
1858 false,
1859 cache_thread_.get(),
1860 base::Bind(&AppCacheStorageImpl::OnDiskCacheInitialized,
1861 base::Unretained(this)));
1864 if (rv != net::ERR_IO_PENDING)
1865 OnDiskCacheInitialized(rv);
1867 return disk_cache_.get();
1870 void AppCacheStorageImpl::OnDiskCacheInitialized(int rv) {
1871 if (rv != net::OK) {
1872 LOG(ERROR) << "Failed to open the appcache diskcache.";
1873 AppCacheHistograms::CountInitResult(AppCacheHistograms::DISK_CACHE_ERROR);
1875 // We're unable to open the disk cache, this is a fatal error that we can't
1876 // really recover from. We handle it by temporarily disabling the appcache
1877 // deleting the directory on disk and reinitializing the appcache system.
1878 Disable();
1879 if (rv != net::ERR_ABORTED)
1880 DeleteAndStartOver();
1884 void AppCacheStorageImpl::DeleteAndStartOver() {
1885 DCHECK(is_disabled_);
1886 if (!is_incognito_) {
1887 VLOG(1) << "Deleting existing appcache data and starting over.";
1888 // We can have tasks in flight to close file handles on both the db
1889 // and cache threads, we need to allow those tasks to cycle thru
1890 // prior to deleting the files and calling reinit.
1891 cache_thread_->PostTaskAndReply(
1892 FROM_HERE,
1893 base::Bind(&base::DoNothing),
1894 base::Bind(&AppCacheStorageImpl::DeleteAndStartOverPart2,
1895 weak_factory_.GetWeakPtr()));
1899 void AppCacheStorageImpl::DeleteAndStartOverPart2() {
1900 db_thread_->PostTaskAndReply(
1901 FROM_HERE,
1902 base::Bind(base::IgnoreResult(&base::DeleteFile), cache_directory_, true),
1903 base::Bind(&AppCacheStorageImpl::CallScheduleReinitialize,
1904 weak_factory_.GetWeakPtr()));
1907 void AppCacheStorageImpl::CallScheduleReinitialize() {
1908 service_->ScheduleReinitialize();
1909 // note: 'this' may be deleted at this point.
1912 void AppCacheStorageImpl::LazilyCommitLastAccessTimes() {
1913 if (lazy_commit_timer_.IsRunning())
1914 return;
1915 const base::TimeDelta kDelay = base::TimeDelta::FromMinutes(5);
1916 lazy_commit_timer_.Start(
1917 FROM_HERE, kDelay,
1918 base::Bind(&AppCacheStorageImpl::OnLazyCommitTimer,
1919 weak_factory_.GetWeakPtr()));
1922 void AppCacheStorageImpl::OnLazyCommitTimer() {
1923 lazy_commit_timer_.Stop();
1924 if (is_disabled())
1925 return;
1926 scoped_refptr<DatabaseTask> task(new CommitLastAccessTimesTask(this));
1927 task->Schedule();
1930 } // namespace content