Dismiss autofill popup on screen orientation change.
[chromium-blink-merge.git] / webkit / browser / appcache / appcache_storage_impl.cc
blobdbc6d01cb75fc98e5452a72c49a5673c60917b8f
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 "webkit/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/file_util.h"
15 #include "base/logging.h"
16 #include "base/message_loop/message_loop.h"
17 #include "base/stl_util.h"
18 #include "base/strings/string_util.h"
19 #include "net/base/cache_type.h"
20 #include "net/base/net_errors.h"
21 #include "sql/connection.h"
22 #include "sql/transaction.h"
23 #include "webkit/browser/appcache/appcache.h"
24 #include "webkit/browser/appcache/appcache_database.h"
25 #include "webkit/browser/appcache/appcache_entry.h"
26 #include "webkit/browser/appcache/appcache_group.h"
27 #include "webkit/browser/appcache/appcache_histograms.h"
28 #include "webkit/browser/appcache/appcache_quota_client.h"
29 #include "webkit/browser/appcache/appcache_response.h"
30 #include "webkit/browser/appcache/appcache_service.h"
31 #include "webkit/browser/quota/quota_client.h"
32 #include "webkit/browser/quota/quota_manager.h"
33 #include "webkit/browser/quota/special_storage_policy.h"
35 namespace appcache {
37 // Hard coded default when not using quota management.
38 static const int kDefaultQuota = 5 * 1024 * 1024;
40 static const int kMaxDiskCacheSize = 250 * 1024 * 1024;
41 static const int kMaxMemDiskCacheSize = 10 * 1024 * 1024;
42 static const base::FilePath::CharType kDiskCacheDirectoryName[] =
43 FILE_PATH_LITERAL("Cache");
45 namespace {
47 // Helpers for clearing data from the AppCacheDatabase.
48 bool DeleteGroupAndRelatedRecords(AppCacheDatabase* database,
49 int64 group_id,
50 std::vector<int64>* deletable_response_ids) {
51 AppCacheDatabase::CacheRecord cache_record;
52 bool success = false;
53 if (database->FindCacheForGroup(group_id, &cache_record)) {
54 database->FindResponseIdsForCacheAsVector(cache_record.cache_id,
55 deletable_response_ids);
56 success =
57 database->DeleteGroup(group_id) &&
58 database->DeleteCache(cache_record.cache_id) &&
59 database->DeleteEntriesForCache(cache_record.cache_id) &&
60 database->DeleteNamespacesForCache(cache_record.cache_id) &&
61 database->DeleteOnlineWhiteListForCache(cache_record.cache_id) &&
62 database->InsertDeletableResponseIds(*deletable_response_ids);
63 } else {
64 NOTREACHED() << "A existing group without a cache is unexpected";
65 success = database->DeleteGroup(group_id);
67 return success;
70 // Destroys |database|. If there is appcache data to be deleted
71 // (|force_keep_session_state| is false), deletes session-only appcache data.
72 void ClearSessionOnlyOrigins(
73 AppCacheDatabase* database,
74 scoped_refptr<quota::SpecialStoragePolicy> special_storage_policy,
75 bool force_keep_session_state) {
76 scoped_ptr<AppCacheDatabase> database_to_delete(database);
78 // If saving session state, only delete the database.
79 if (force_keep_session_state)
80 return;
82 bool has_session_only_appcaches =
83 special_storage_policy.get() &&
84 special_storage_policy->HasSessionOnlyOrigins();
86 // Clearning only session-only databases, and there are none.
87 if (!has_session_only_appcaches)
88 return;
90 std::set<GURL> origins;
91 database->FindOriginsWithGroups(&origins);
92 if (origins.empty())
93 return; // nothing to delete
95 sql::Connection* connection = database->db_connection();
96 if (!connection) {
97 NOTREACHED() << "Missing database connection.";
98 return;
101 std::set<GURL>::const_iterator origin;
102 for (origin = origins.begin(); origin != origins.end(); ++origin) {
103 if (!special_storage_policy->IsStorageSessionOnly(*origin))
104 continue;
105 if (special_storage_policy.get() &&
106 special_storage_policy->IsStorageProtected(*origin))
107 continue;
109 std::vector<AppCacheDatabase::GroupRecord> groups;
110 database->FindGroupsForOrigin(*origin, &groups);
111 std::vector<AppCacheDatabase::GroupRecord>::const_iterator group;
112 for (group = groups.begin(); group != groups.end(); ++group) {
113 sql::Transaction transaction(connection);
114 if (!transaction.Begin()) {
115 NOTREACHED() << "Failed to start transaction";
116 return;
118 std::vector<int64> deletable_response_ids;
119 bool success = DeleteGroupAndRelatedRecords(database,
120 group->group_id,
121 &deletable_response_ids);
122 success = success && transaction.Commit();
123 DCHECK(success);
124 } // for each group
125 } // for each origin
128 } // namespace
130 // DatabaseTask -----------------------------------------
132 class AppCacheStorageImpl::DatabaseTask
133 : public base::RefCountedThreadSafe<DatabaseTask> {
134 public:
135 explicit DatabaseTask(AppCacheStorageImpl* storage)
136 : storage_(storage), database_(storage->database_),
137 io_thread_(base::MessageLoopProxy::current()) {
138 DCHECK(io_thread_.get());
141 void AddDelegate(DelegateReference* delegate_reference) {
142 delegates_.push_back(make_scoped_refptr(delegate_reference));
145 // Schedules a task to be Run() on the DB thread. Tasks
146 // are run in the order in which they are scheduled.
147 void Schedule();
149 // Called on the DB thread.
150 virtual void Run() = 0;
152 // Called on the IO thread after Run() has completed.
153 virtual void RunCompleted() {}
155 // Once scheduled a task cannot be cancelled, but the
156 // call to RunCompleted may be. This method should only be
157 // called on the IO thread. This is used by AppCacheStorageImpl
158 // to cancel the completion calls when AppCacheStorageImpl is
159 // destructed. This method may be overriden to release or delete
160 // additional data associated with the task that is not DB thread
161 // safe. If overriden, this base class method must be called from
162 // within the override.
163 virtual void CancelCompletion();
165 protected:
166 friend class base::RefCountedThreadSafe<DatabaseTask>;
167 virtual ~DatabaseTask() {}
169 AppCacheStorageImpl* storage_;
170 AppCacheDatabase* database_;
171 DelegateReferenceVector delegates_;
173 private:
174 void CallRun(base::TimeTicks schedule_time);
175 void CallRunCompleted(base::TimeTicks schedule_time);
176 void CallDisableStorage();
178 scoped_refptr<base::MessageLoopProxy> io_thread_;
181 void AppCacheStorageImpl::DatabaseTask::Schedule() {
182 DCHECK(storage_);
183 DCHECK(io_thread_->BelongsToCurrentThread());
184 if (storage_->db_thread_->PostTask(
185 FROM_HERE,
186 base::Bind(&DatabaseTask::CallRun, this, base::TimeTicks::Now()))) {
187 storage_->scheduled_database_tasks_.push_back(this);
188 } else {
189 NOTREACHED() << "Thread for database tasks is not running. "
190 << "This is not always the DB thread.";
194 void AppCacheStorageImpl::DatabaseTask::CancelCompletion() {
195 DCHECK(io_thread_->BelongsToCurrentThread());
196 delegates_.clear();
197 storage_ = NULL;
200 void AppCacheStorageImpl::DatabaseTask::CallRun(
201 base::TimeTicks schedule_time) {
202 AppCacheHistograms::AddTaskQueueTimeSample(
203 base::TimeTicks::Now() - schedule_time);
204 if (!database_->is_disabled()) {
205 base::TimeTicks run_time = base::TimeTicks::Now();
206 Run();
207 AppCacheHistograms::AddTaskRunTimeSample(
208 base::TimeTicks::Now() - run_time);
209 if (database_->is_disabled()) {
210 io_thread_->PostTask(
211 FROM_HERE,
212 base::Bind(&DatabaseTask::CallDisableStorage, this));
215 io_thread_->PostTask(
216 FROM_HERE,
217 base::Bind(&DatabaseTask::CallRunCompleted, this,
218 base::TimeTicks::Now()));
221 void AppCacheStorageImpl::DatabaseTask::CallRunCompleted(
222 base::TimeTicks schedule_time) {
223 AppCacheHistograms::AddCompletionQueueTimeSample(
224 base::TimeTicks::Now() - schedule_time);
225 if (storage_) {
226 DCHECK(io_thread_->BelongsToCurrentThread());
227 DCHECK(storage_->scheduled_database_tasks_.front() == this);
228 storage_->scheduled_database_tasks_.pop_front();
229 base::TimeTicks run_time = base::TimeTicks::Now();
230 RunCompleted();
231 AppCacheHistograms::AddCompletionRunTimeSample(
232 base::TimeTicks::Now() - run_time);
233 delegates_.clear();
237 void AppCacheStorageImpl::DatabaseTask::CallDisableStorage() {
238 if (storage_) {
239 DCHECK(io_thread_->BelongsToCurrentThread());
240 storage_->Disable();
244 // InitTask -------
246 class AppCacheStorageImpl::InitTask : public DatabaseTask {
247 public:
248 explicit InitTask(AppCacheStorageImpl* storage)
249 : DatabaseTask(storage), last_group_id_(0),
250 last_cache_id_(0), last_response_id_(0),
251 last_deletable_response_rowid_(0) {}
253 // DatabaseTask:
254 virtual void Run() OVERRIDE;
255 virtual void RunCompleted() OVERRIDE;
257 protected:
258 virtual ~InitTask() {}
260 private:
261 int64 last_group_id_;
262 int64 last_cache_id_;
263 int64 last_response_id_;
264 int64 last_deletable_response_rowid_;
265 std::map<GURL, int64> usage_map_;
268 void AppCacheStorageImpl::InitTask::Run() {
269 database_->FindLastStorageIds(
270 &last_group_id_, &last_cache_id_, &last_response_id_,
271 &last_deletable_response_rowid_);
272 database_->GetAllOriginUsage(&usage_map_);
275 void AppCacheStorageImpl::InitTask::RunCompleted() {
276 storage_->last_group_id_ = last_group_id_;
277 storage_->last_cache_id_ = last_cache_id_;
278 storage_->last_response_id_ = last_response_id_;
279 storage_->last_deletable_response_rowid_ = last_deletable_response_rowid_;
281 if (!storage_->is_disabled()) {
282 storage_->usage_map_.swap(usage_map_);
283 const base::TimeDelta kDelay = base::TimeDelta::FromMinutes(5);
284 base::MessageLoop::current()->PostDelayedTask(
285 FROM_HERE,
286 base::Bind(&AppCacheStorageImpl::DelayedStartDeletingUnusedResponses,
287 storage_->weak_factory_.GetWeakPtr()),
288 kDelay);
291 if (storage_->service()->quota_client())
292 storage_->service()->quota_client()->NotifyAppCacheReady();
295 // CloseConnectionTask -------
297 class AppCacheStorageImpl::CloseConnectionTask : public DatabaseTask {
298 public:
299 explicit CloseConnectionTask(AppCacheStorageImpl* storage)
300 : DatabaseTask(storage) {}
302 // DatabaseTask:
303 virtual void Run() OVERRIDE { database_->CloseConnection(); }
305 protected:
306 virtual ~CloseConnectionTask() {}
309 // DisableDatabaseTask -------
311 class AppCacheStorageImpl::DisableDatabaseTask : public DatabaseTask {
312 public:
313 explicit DisableDatabaseTask(AppCacheStorageImpl* storage)
314 : DatabaseTask(storage) {}
316 // DatabaseTask:
317 virtual void Run() OVERRIDE { database_->Disable(); }
319 protected:
320 virtual ~DisableDatabaseTask() {}
323 // GetAllInfoTask -------
325 class AppCacheStorageImpl::GetAllInfoTask : public DatabaseTask {
326 public:
327 explicit GetAllInfoTask(AppCacheStorageImpl* storage)
328 : DatabaseTask(storage),
329 info_collection_(new AppCacheInfoCollection()) {
332 // DatabaseTask:
333 virtual void Run() OVERRIDE;
334 virtual void RunCompleted() OVERRIDE;
336 protected:
337 virtual ~GetAllInfoTask() {}
339 private:
340 scoped_refptr<AppCacheInfoCollection> info_collection_;
343 void AppCacheStorageImpl::GetAllInfoTask::Run() {
344 std::set<GURL> origins;
345 database_->FindOriginsWithGroups(&origins);
346 for (std::set<GURL>::const_iterator origin = origins.begin();
347 origin != origins.end(); ++origin) {
348 AppCacheInfoVector& infos =
349 info_collection_->infos_by_origin[*origin];
350 std::vector<AppCacheDatabase::GroupRecord> groups;
351 database_->FindGroupsForOrigin(*origin, &groups);
352 for (std::vector<AppCacheDatabase::GroupRecord>::const_iterator
353 group = groups.begin();
354 group != groups.end(); ++group) {
355 AppCacheDatabase::CacheRecord cache_record;
356 database_->FindCacheForGroup(group->group_id, &cache_record);
357 AppCacheInfo info;
358 info.manifest_url = group->manifest_url;
359 info.creation_time = group->creation_time;
360 info.size = cache_record.cache_size;
361 info.last_access_time = group->last_access_time;
362 info.last_update_time = cache_record.update_time;
363 info.cache_id = cache_record.cache_id;
364 info.group_id = group->group_id;
365 info.is_complete = true;
366 infos.push_back(info);
371 void AppCacheStorageImpl::GetAllInfoTask::RunCompleted() {
372 DCHECK(delegates_.size() == 1);
373 FOR_EACH_DELEGATE(delegates_, OnAllInfo(info_collection_.get()));
376 // StoreOrLoadTask -------
378 class AppCacheStorageImpl::StoreOrLoadTask : public DatabaseTask {
379 protected:
380 explicit StoreOrLoadTask(AppCacheStorageImpl* storage)
381 : DatabaseTask(storage) {}
382 virtual ~StoreOrLoadTask() {}
384 bool FindRelatedCacheRecords(int64 cache_id);
385 void CreateCacheAndGroupFromRecords(
386 scoped_refptr<AppCache>* cache, scoped_refptr<AppCacheGroup>* group);
388 AppCacheDatabase::GroupRecord group_record_;
389 AppCacheDatabase::CacheRecord cache_record_;
390 std::vector<AppCacheDatabase::EntryRecord> entry_records_;
391 std::vector<AppCacheDatabase::NamespaceRecord>
392 intercept_namespace_records_;
393 std::vector<AppCacheDatabase::NamespaceRecord>
394 fallback_namespace_records_;
395 std::vector<AppCacheDatabase::OnlineWhiteListRecord>
396 online_whitelist_records_;
399 bool AppCacheStorageImpl::StoreOrLoadTask::FindRelatedCacheRecords(
400 int64 cache_id) {
401 return database_->FindEntriesForCache(cache_id, &entry_records_) &&
402 database_->FindNamespacesForCache(
403 cache_id, &intercept_namespace_records_,
404 &fallback_namespace_records_) &&
405 database_->FindOnlineWhiteListForCache(
406 cache_id, &online_whitelist_records_);
409 void AppCacheStorageImpl::StoreOrLoadTask::CreateCacheAndGroupFromRecords(
410 scoped_refptr<AppCache>* cache, scoped_refptr<AppCacheGroup>* group) {
411 DCHECK(storage_ && cache && group);
413 (*cache) = storage_->working_set_.GetCache(cache_record_.cache_id);
414 if (cache->get()) {
415 (*group) = cache->get()->owning_group();
416 DCHECK(group->get());
417 DCHECK_EQ(group_record_.group_id, group->get()->group_id());
419 // TODO(michaeln): histogram is fishing for clues to crbug/95101
420 if (!cache->get()->GetEntry(group_record_.manifest_url)) {
421 AppCacheHistograms::AddMissingManifestDetectedAtCallsite(
422 AppCacheHistograms::CALLSITE_0);
425 storage_->NotifyStorageAccessed(group_record_.origin);
426 return;
429 (*cache) = new AppCache(storage_, cache_record_.cache_id);
430 cache->get()->InitializeWithDatabaseRecords(
431 cache_record_, entry_records_,
432 intercept_namespace_records_,
433 fallback_namespace_records_,
434 online_whitelist_records_);
435 cache->get()->set_complete(true);
437 (*group) = storage_->working_set_.GetGroup(group_record_.manifest_url);
438 if (group->get()) {
439 DCHECK(group_record_.group_id == group->get()->group_id());
440 group->get()->AddCache(cache->get());
442 // TODO(michaeln): histogram is fishing for clues to crbug/95101
443 if (!cache->get()->GetEntry(group_record_.manifest_url)) {
444 AppCacheHistograms::AddMissingManifestDetectedAtCallsite(
445 AppCacheHistograms::CALLSITE_1);
447 } else {
448 (*group) = new AppCacheGroup(
449 storage_, group_record_.manifest_url,
450 group_record_.group_id);
451 group->get()->set_creation_time(group_record_.creation_time);
452 group->get()->AddCache(cache->get());
454 // TODO(michaeln): histogram is fishing for clues to crbug/95101
455 if (!cache->get()->GetEntry(group_record_.manifest_url)) {
456 AppCacheHistograms::AddMissingManifestDetectedAtCallsite(
457 AppCacheHistograms::CALLSITE_2);
460 DCHECK(group->get()->newest_complete_cache() == cache->get());
462 // We have to update foriegn entries if MarkEntryAsForeignTasks
463 // are in flight.
464 std::vector<GURL> urls;
465 storage_->GetPendingForeignMarkingsForCache(cache->get()->cache_id(), &urls);
466 for (std::vector<GURL>::iterator iter = urls.begin();
467 iter != urls.end(); ++iter) {
468 DCHECK(cache->get()->GetEntry(*iter));
469 cache->get()->GetEntry(*iter)->add_types(AppCacheEntry::FOREIGN);
472 storage_->NotifyStorageAccessed(group_record_.origin);
474 // TODO(michaeln): Maybe verify that the responses we expect to exist
475 // do actually exist in the disk_cache (and if not then what?)
478 // CacheLoadTask -------
480 class AppCacheStorageImpl::CacheLoadTask : public StoreOrLoadTask {
481 public:
482 CacheLoadTask(int64 cache_id, AppCacheStorageImpl* storage)
483 : StoreOrLoadTask(storage), cache_id_(cache_id),
484 success_(false) {}
486 // DatabaseTask:
487 virtual void Run() OVERRIDE;
488 virtual void RunCompleted() OVERRIDE;
490 protected:
491 virtual ~CacheLoadTask() {}
493 private:
494 int64 cache_id_;
495 bool success_;
498 void AppCacheStorageImpl::CacheLoadTask::Run() {
499 success_ =
500 database_->FindCache(cache_id_, &cache_record_) &&
501 database_->FindGroup(cache_record_.group_id, &group_record_) &&
502 FindRelatedCacheRecords(cache_id_);
504 if (success_)
505 database_->UpdateGroupLastAccessTime(group_record_.group_id,
506 base::Time::Now());
509 void AppCacheStorageImpl::CacheLoadTask::RunCompleted() {
510 storage_->pending_cache_loads_.erase(cache_id_);
511 scoped_refptr<AppCache> cache;
512 scoped_refptr<AppCacheGroup> group;
513 if (success_ && !storage_->is_disabled()) {
514 DCHECK(cache_record_.cache_id == cache_id_);
515 CreateCacheAndGroupFromRecords(&cache, &group);
517 FOR_EACH_DELEGATE(delegates_, OnCacheLoaded(cache.get(), cache_id_));
520 // GroupLoadTask -------
522 class AppCacheStorageImpl::GroupLoadTask : public StoreOrLoadTask {
523 public:
524 GroupLoadTask(GURL manifest_url, AppCacheStorageImpl* storage)
525 : StoreOrLoadTask(storage), manifest_url_(manifest_url),
526 success_(false) {}
528 // DatabaseTask:
529 virtual void Run() OVERRIDE;
530 virtual void RunCompleted() OVERRIDE;
532 protected:
533 virtual ~GroupLoadTask() {}
535 private:
536 GURL manifest_url_;
537 bool success_;
540 void AppCacheStorageImpl::GroupLoadTask::Run() {
541 success_ =
542 database_->FindGroupForManifestUrl(manifest_url_, &group_record_) &&
543 database_->FindCacheForGroup(group_record_.group_id, &cache_record_) &&
544 FindRelatedCacheRecords(cache_record_.cache_id);
546 if (success_)
547 database_->UpdateGroupLastAccessTime(group_record_.group_id,
548 base::Time::Now());
551 void AppCacheStorageImpl::GroupLoadTask::RunCompleted() {
552 storage_->pending_group_loads_.erase(manifest_url_);
553 scoped_refptr<AppCacheGroup> group;
554 scoped_refptr<AppCache> cache;
555 if (!storage_->is_disabled()) {
556 if (success_) {
557 DCHECK(group_record_.manifest_url == manifest_url_);
558 CreateCacheAndGroupFromRecords(&cache, &group);
559 } else {
560 group = storage_->working_set_.GetGroup(manifest_url_);
561 if (!group.get()) {
562 group =
563 new AppCacheGroup(storage_, manifest_url_, storage_->NewGroupId());
567 FOR_EACH_DELEGATE(delegates_, OnGroupLoaded(group.get(), manifest_url_));
570 // StoreGroupAndCacheTask -------
572 class AppCacheStorageImpl::StoreGroupAndCacheTask : public StoreOrLoadTask {
573 public:
574 StoreGroupAndCacheTask(AppCacheStorageImpl* storage, AppCacheGroup* group,
575 AppCache* newest_cache);
577 void GetQuotaThenSchedule();
578 void OnQuotaCallback(
579 quota::QuotaStatusCode status, int64 usage, int64 quota);
581 // DatabaseTask:
582 virtual void Run() OVERRIDE;
583 virtual void RunCompleted() OVERRIDE;
584 virtual void CancelCompletion() OVERRIDE;
586 protected:
587 virtual ~StoreGroupAndCacheTask() {}
589 private:
590 scoped_refptr<AppCacheGroup> group_;
591 scoped_refptr<AppCache> cache_;
592 bool success_;
593 bool would_exceed_quota_;
594 int64 space_available_;
595 int64 new_origin_usage_;
596 std::vector<int64> newly_deletable_response_ids_;
599 AppCacheStorageImpl::StoreGroupAndCacheTask::StoreGroupAndCacheTask(
600 AppCacheStorageImpl* storage, AppCacheGroup* group, AppCache* newest_cache)
601 : StoreOrLoadTask(storage), group_(group), cache_(newest_cache),
602 success_(false), would_exceed_quota_(false),
603 space_available_(-1), new_origin_usage_(-1) {
604 group_record_.group_id = group->group_id();
605 group_record_.manifest_url = group->manifest_url();
606 group_record_.origin = group_record_.manifest_url.GetOrigin();
607 newest_cache->ToDatabaseRecords(
608 group,
609 &cache_record_, &entry_records_,
610 &intercept_namespace_records_,
611 &fallback_namespace_records_,
612 &online_whitelist_records_);
615 void AppCacheStorageImpl::StoreGroupAndCacheTask::GetQuotaThenSchedule() {
616 quota::QuotaManager* quota_manager = NULL;
617 if (storage_->service()->quota_manager_proxy()) {
618 quota_manager =
619 storage_->service()->quota_manager_proxy()->quota_manager();
622 if (!quota_manager) {
623 if (storage_->service()->special_storage_policy() &&
624 storage_->service()->special_storage_policy()->IsStorageUnlimited(
625 group_record_.origin))
626 space_available_ = kint64max;
627 Schedule();
628 return;
631 // We have to ask the quota manager for the value.
632 storage_->pending_quota_queries_.insert(this);
633 quota_manager->GetUsageAndQuota(
634 group_record_.origin, quota::kStorageTypeTemporary,
635 base::Bind(&StoreGroupAndCacheTask::OnQuotaCallback, this));
638 void AppCacheStorageImpl::StoreGroupAndCacheTask::OnQuotaCallback(
639 quota::QuotaStatusCode status, int64 usage, int64 quota) {
640 if (storage_) {
641 if (status == quota::kQuotaStatusOk)
642 space_available_ = std::max(static_cast<int64>(0), quota - usage);
643 else
644 space_available_ = 0;
645 storage_->pending_quota_queries_.erase(this);
646 Schedule();
650 void AppCacheStorageImpl::StoreGroupAndCacheTask::Run() {
651 DCHECK(!success_);
652 sql::Connection* connection = database_->db_connection();
653 if (!connection)
654 return;
656 sql::Transaction transaction(connection);
657 if (!transaction.Begin())
658 return;
660 int64 old_origin_usage = database_->GetOriginUsage(group_record_.origin);
662 AppCacheDatabase::GroupRecord existing_group;
663 success_ = database_->FindGroup(group_record_.group_id, &existing_group);
664 if (!success_) {
665 group_record_.creation_time = base::Time::Now();
666 group_record_.last_access_time = base::Time::Now();
667 success_ = database_->InsertGroup(&group_record_);
668 } else {
669 DCHECK(group_record_.group_id == existing_group.group_id);
670 DCHECK(group_record_.manifest_url == existing_group.manifest_url);
671 DCHECK(group_record_.origin == existing_group.origin);
673 database_->UpdateGroupLastAccessTime(group_record_.group_id,
674 base::Time::Now());
676 AppCacheDatabase::CacheRecord cache;
677 if (database_->FindCacheForGroup(group_record_.group_id, &cache)) {
678 // Get the set of response ids in the old cache.
679 std::set<int64> existing_response_ids;
680 database_->FindResponseIdsForCacheAsSet(cache.cache_id,
681 &existing_response_ids);
683 // Remove those that remain in the new cache.
684 std::vector<AppCacheDatabase::EntryRecord>::const_iterator entry_iter =
685 entry_records_.begin();
686 while (entry_iter != entry_records_.end()) {
687 existing_response_ids.erase(entry_iter->response_id);
688 ++entry_iter;
691 // The rest are deletable.
692 std::set<int64>::const_iterator id_iter = existing_response_ids.begin();
693 while (id_iter != existing_response_ids.end()) {
694 newly_deletable_response_ids_.push_back(*id_iter);
695 ++id_iter;
698 success_ =
699 database_->DeleteCache(cache.cache_id) &&
700 database_->DeleteEntriesForCache(cache.cache_id) &&
701 database_->DeleteNamespacesForCache(cache.cache_id) &&
702 database_->DeleteOnlineWhiteListForCache(cache.cache_id) &&
703 database_->InsertDeletableResponseIds(newly_deletable_response_ids_);
704 // TODO(michaeln): store group_id too with deletable ids
705 } else {
706 NOTREACHED() << "A existing group without a cache is unexpected";
710 success_ =
711 success_ &&
712 database_->InsertCache(&cache_record_) &&
713 database_->InsertEntryRecords(entry_records_) &&
714 database_->InsertNamespaceRecords(intercept_namespace_records_) &&
715 database_->InsertNamespaceRecords(fallback_namespace_records_) &&
716 database_->InsertOnlineWhiteListRecords(online_whitelist_records_);
718 if (!success_)
719 return;
721 new_origin_usage_ = database_->GetOriginUsage(group_record_.origin);
723 // Only check quota when the new usage exceeds the old usage.
724 if (new_origin_usage_ <= old_origin_usage) {
725 success_ = transaction.Commit();
726 return;
729 // Use a simple hard-coded value when not using quota management.
730 if (space_available_ == -1) {
731 if (new_origin_usage_ > kDefaultQuota) {
732 would_exceed_quota_ = true;
733 success_ = false;
734 return;
736 success_ = transaction.Commit();
737 return;
740 // Check limits based on the space availbable given to us via the
741 // quota system.
742 int64 delta = new_origin_usage_ - old_origin_usage;
743 if (delta > space_available_) {
744 would_exceed_quota_ = true;
745 success_ = false;
746 return;
749 success_ = transaction.Commit();
752 void AppCacheStorageImpl::StoreGroupAndCacheTask::RunCompleted() {
753 if (success_) {
754 storage_->UpdateUsageMapAndNotify(
755 group_->manifest_url().GetOrigin(), new_origin_usage_);
756 if (cache_.get() != group_->newest_complete_cache()) {
757 cache_->set_complete(true);
758 group_->AddCache(cache_.get());
760 if (group_->creation_time().is_null())
761 group_->set_creation_time(group_record_.creation_time);
762 group_->AddNewlyDeletableResponseIds(&newly_deletable_response_ids_);
764 FOR_EACH_DELEGATE(
765 delegates_,
766 OnGroupAndNewestCacheStored(
767 group_.get(), cache_.get(), success_, would_exceed_quota_));
768 group_ = NULL;
769 cache_ = NULL;
771 // TODO(michaeln): if (would_exceed_quota_) what if the current usage
772 // also exceeds the quota? http://crbug.com/83968
775 void AppCacheStorageImpl::StoreGroupAndCacheTask::CancelCompletion() {
776 // Overriden to safely drop our reference to the group and cache
777 // which are not thread safe refcounted.
778 DatabaseTask::CancelCompletion();
779 group_ = NULL;
780 cache_ = NULL;
783 // FindMainResponseTask -------
785 // Helpers for FindMainResponseTask::Run()
786 namespace {
787 class SortByCachePreference
788 : public std::binary_function<
789 AppCacheDatabase::EntryRecord,
790 AppCacheDatabase::EntryRecord,
791 bool> {
792 public:
793 SortByCachePreference(int64 preferred_id, const std::set<int64>& in_use_ids)
794 : preferred_id_(preferred_id), in_use_ids_(in_use_ids) {
796 bool operator()(
797 const AppCacheDatabase::EntryRecord& lhs,
798 const AppCacheDatabase::EntryRecord& rhs) {
799 return compute_value(lhs) > compute_value(rhs);
801 private:
802 int compute_value(const AppCacheDatabase::EntryRecord& entry) {
803 if (entry.cache_id == preferred_id_)
804 return 100;
805 else if (in_use_ids_.find(entry.cache_id) != in_use_ids_.end())
806 return 50;
807 return 0;
809 int64 preferred_id_;
810 const std::set<int64>& in_use_ids_;
813 bool SortByLength(
814 const AppCacheDatabase::NamespaceRecord& lhs,
815 const AppCacheDatabase::NamespaceRecord& rhs) {
816 return lhs.namespace_.namespace_url.spec().length() >
817 rhs.namespace_.namespace_url.spec().length();
820 class NetworkNamespaceHelper {
821 public:
822 explicit NetworkNamespaceHelper(AppCacheDatabase* database)
823 : database_(database) {
826 bool IsInNetworkNamespace(const GURL& url, int64 cache_id) {
827 typedef std::pair<WhiteListMap::iterator, bool> InsertResult;
828 InsertResult result = namespaces_map_.insert(
829 WhiteListMap::value_type(cache_id, NamespaceVector()));
830 if (result.second)
831 GetOnlineWhiteListForCache(cache_id, &result.first->second);
832 return AppCache::FindNamespace(result.first->second, url) != NULL;
835 private:
836 void GetOnlineWhiteListForCache(
837 int64 cache_id, NamespaceVector* namespaces) {
838 DCHECK(namespaces && namespaces->empty());
839 typedef std::vector<AppCacheDatabase::OnlineWhiteListRecord>
840 WhiteListVector;
841 WhiteListVector records;
842 if (!database_->FindOnlineWhiteListForCache(cache_id, &records))
843 return;
844 WhiteListVector::const_iterator iter = records.begin();
845 while (iter != records.end()) {
846 namespaces->push_back(
847 Namespace(NETWORK_NAMESPACE, iter->namespace_url, GURL(),
848 iter->is_pattern));
849 ++iter;
853 // Key is cache id
854 typedef std::map<int64, NamespaceVector> WhiteListMap;
855 WhiteListMap namespaces_map_;
856 AppCacheDatabase* database_;
859 } // namespace
861 class AppCacheStorageImpl::FindMainResponseTask : public DatabaseTask {
862 public:
863 FindMainResponseTask(AppCacheStorageImpl* storage,
864 const GURL& url,
865 const GURL& preferred_manifest_url,
866 const AppCacheWorkingSet::GroupMap* groups_in_use)
867 : DatabaseTask(storage), url_(url),
868 preferred_manifest_url_(preferred_manifest_url),
869 cache_id_(kNoCacheId), group_id_(0) {
870 if (groups_in_use) {
871 for (AppCacheWorkingSet::GroupMap::const_iterator it =
872 groups_in_use->begin();
873 it != groups_in_use->end(); ++it) {
874 AppCacheGroup* group = it->second;
875 AppCache* cache = group->newest_complete_cache();
876 if (group->is_obsolete() || !cache)
877 continue;
878 cache_ids_in_use_.insert(cache->cache_id());
883 // DatabaseTask:
884 virtual void Run() OVERRIDE;
885 virtual void RunCompleted() OVERRIDE;
887 protected:
888 virtual ~FindMainResponseTask() {}
890 private:
891 typedef std::vector<AppCacheDatabase::NamespaceRecord*>
892 NamespaceRecordPtrVector;
894 bool FindExactMatch(int64 preferred_id);
895 bool FindNamespaceMatch(int64 preferred_id);
896 bool FindNamespaceHelper(
897 int64 preferred_cache_id,
898 AppCacheDatabase::NamespaceRecordVector* namespaces,
899 NetworkNamespaceHelper* network_namespace_helper);
900 bool FindFirstValidNamespace(const NamespaceRecordPtrVector& namespaces);
902 GURL url_;
903 GURL preferred_manifest_url_;
904 std::set<int64> cache_ids_in_use_;
905 AppCacheEntry entry_;
906 AppCacheEntry fallback_entry_;
907 GURL namespace_entry_url_;
908 int64 cache_id_;
909 int64 group_id_;
910 GURL manifest_url_;
915 void AppCacheStorageImpl::FindMainResponseTask::Run() {
916 // NOTE: The heuristics around choosing amoungst multiple candidates
917 // is underspecified, and just plain not fully understood. This needs
918 // to be refined.
920 // The 'preferred_manifest_url' is the url of the manifest associated
921 // with the page that opened or embedded the page being loaded now.
922 // We have a strong preference to use resources from that cache.
923 // We also have a lesser bias to use resources from caches that are currently
924 // being used by other unrelated pages.
925 // TODO(michaeln): come up with a 'preferred_manifest_url' in more cases
926 // - when navigating a frame whose current contents are from an appcache
927 // - when clicking an href in a frame that is appcached
928 int64 preferred_cache_id = kNoCacheId;
929 if (!preferred_manifest_url_.is_empty()) {
930 AppCacheDatabase::GroupRecord preferred_group;
931 AppCacheDatabase::CacheRecord preferred_cache;
932 if (database_->FindGroupForManifestUrl(
933 preferred_manifest_url_, &preferred_group) &&
934 database_->FindCacheForGroup(
935 preferred_group.group_id, &preferred_cache)) {
936 preferred_cache_id = preferred_cache.cache_id;
940 if (FindExactMatch(preferred_cache_id) ||
941 FindNamespaceMatch(preferred_cache_id)) {
942 // We found something.
943 DCHECK(cache_id_ != kNoCacheId && !manifest_url_.is_empty() &&
944 group_id_ != 0);
945 return;
948 // We didn't find anything.
949 DCHECK(cache_id_ == kNoCacheId && manifest_url_.is_empty() &&
950 group_id_ == 0);
953 bool AppCacheStorageImpl::
954 FindMainResponseTask::FindExactMatch(int64 preferred_cache_id) {
955 std::vector<AppCacheDatabase::EntryRecord> entries;
956 if (database_->FindEntriesForUrl(url_, &entries) && !entries.empty()) {
957 // Sort them in order of preference, from the preferred_cache first,
958 // followed by hits from caches that are 'in use', then the rest.
959 std::sort(entries.begin(), entries.end(),
960 SortByCachePreference(preferred_cache_id, cache_ids_in_use_));
962 // Take the first with a valid, non-foreign entry.
963 std::vector<AppCacheDatabase::EntryRecord>::iterator iter;
964 for (iter = entries.begin(); iter < entries.end(); ++iter) {
965 AppCacheDatabase::GroupRecord group_record;
966 if ((iter->flags & AppCacheEntry::FOREIGN) ||
967 !database_->FindGroupForCache(iter->cache_id, &group_record)) {
968 continue;
970 manifest_url_ = group_record.manifest_url;
971 group_id_ = group_record.group_id;
972 entry_ = AppCacheEntry(iter->flags, iter->response_id);
973 cache_id_ = iter->cache_id;
974 return true; // We found an exact match.
977 return false;
980 bool AppCacheStorageImpl::
981 FindMainResponseTask::FindNamespaceMatch(int64 preferred_cache_id) {
982 AppCacheDatabase::NamespaceRecordVector all_intercepts;
983 AppCacheDatabase::NamespaceRecordVector all_fallbacks;
984 if (!database_->FindNamespacesForOrigin(
985 url_.GetOrigin(), &all_intercepts, &all_fallbacks)
986 || (all_intercepts.empty() && all_fallbacks.empty())) {
987 return false;
990 NetworkNamespaceHelper network_namespace_helper(database_);
991 if (FindNamespaceHelper(preferred_cache_id,
992 &all_intercepts,
993 &network_namespace_helper) ||
994 FindNamespaceHelper(preferred_cache_id,
995 &all_fallbacks,
996 &network_namespace_helper)) {
997 return true;
999 return false;
1002 bool AppCacheStorageImpl::
1003 FindMainResponseTask::FindNamespaceHelper(
1004 int64 preferred_cache_id,
1005 AppCacheDatabase::NamespaceRecordVector* namespaces,
1006 NetworkNamespaceHelper* network_namespace_helper) {
1007 // Sort them by length, longer matches within the same cache/bucket take
1008 // precedence.
1009 std::sort(namespaces->begin(), namespaces->end(), SortByLength);
1011 NamespaceRecordPtrVector preferred_namespaces;
1012 NamespaceRecordPtrVector inuse_namespaces;
1013 NamespaceRecordPtrVector other_namespaces;
1014 std::vector<AppCacheDatabase::NamespaceRecord>::iterator iter;
1015 for (iter = namespaces->begin(); iter < namespaces->end(); ++iter) {
1016 // Skip those that aren't a match.
1017 if (!iter->namespace_.IsMatch(url_))
1018 continue;
1020 // Skip namespaces where the requested url falls into a network
1021 // namespace of its containing appcache.
1022 if (network_namespace_helper->IsInNetworkNamespace(url_, iter->cache_id))
1023 continue;
1025 // Bin them into one of our three buckets.
1026 if (iter->cache_id == preferred_cache_id)
1027 preferred_namespaces.push_back(&(*iter));
1028 else if (cache_ids_in_use_.find(iter->cache_id) != cache_ids_in_use_.end())
1029 inuse_namespaces.push_back(&(*iter));
1030 else
1031 other_namespaces.push_back(&(*iter));
1034 if (FindFirstValidNamespace(preferred_namespaces) ||
1035 FindFirstValidNamespace(inuse_namespaces) ||
1036 FindFirstValidNamespace(other_namespaces))
1037 return true; // We found one.
1039 // We didn't find anything.
1040 return false;
1043 bool AppCacheStorageImpl::
1044 FindMainResponseTask::FindFirstValidNamespace(
1045 const NamespaceRecordPtrVector& namespaces) {
1046 // Take the first with a valid, non-foreign entry.
1047 NamespaceRecordPtrVector::const_iterator iter;
1048 for (iter = namespaces.begin(); iter < namespaces.end(); ++iter) {
1049 AppCacheDatabase::EntryRecord entry_record;
1050 if (database_->FindEntry((*iter)->cache_id, (*iter)->namespace_.target_url,
1051 &entry_record)) {
1052 AppCacheDatabase::GroupRecord group_record;
1053 if ((entry_record.flags & AppCacheEntry::FOREIGN) ||
1054 !database_->FindGroupForCache(entry_record.cache_id, &group_record)) {
1055 continue;
1057 manifest_url_ = group_record.manifest_url;
1058 group_id_ = group_record.group_id;
1059 cache_id_ = (*iter)->cache_id;
1060 namespace_entry_url_ = (*iter)->namespace_.target_url;
1061 if ((*iter)->namespace_.type == FALLBACK_NAMESPACE)
1062 fallback_entry_ = AppCacheEntry(entry_record.flags,
1063 entry_record.response_id);
1064 else
1065 entry_ = AppCacheEntry(entry_record.flags, entry_record.response_id);
1066 return true; // We found one.
1069 return false; // We didn't find a match.
1072 void AppCacheStorageImpl::FindMainResponseTask::RunCompleted() {
1073 storage_->CallOnMainResponseFound(
1074 &delegates_, url_, entry_, namespace_entry_url_, fallback_entry_,
1075 cache_id_, group_id_, manifest_url_);
1078 // MarkEntryAsForeignTask -------
1080 class AppCacheStorageImpl::MarkEntryAsForeignTask : public DatabaseTask {
1081 public:
1082 MarkEntryAsForeignTask(
1083 AppCacheStorageImpl* storage, const GURL& url, int64 cache_id)
1084 : DatabaseTask(storage), cache_id_(cache_id), entry_url_(url) {}
1086 // DatabaseTask:
1087 virtual void Run() OVERRIDE;
1088 virtual void RunCompleted() OVERRIDE;
1090 protected:
1091 virtual ~MarkEntryAsForeignTask() {}
1093 private:
1094 int64 cache_id_;
1095 GURL entry_url_;
1098 void AppCacheStorageImpl::MarkEntryAsForeignTask::Run() {
1099 database_->AddEntryFlags(entry_url_, cache_id_, AppCacheEntry::FOREIGN);
1102 void AppCacheStorageImpl::MarkEntryAsForeignTask::RunCompleted() {
1103 DCHECK(storage_->pending_foreign_markings_.front().first == entry_url_ &&
1104 storage_->pending_foreign_markings_.front().second == cache_id_);
1105 storage_->pending_foreign_markings_.pop_front();
1108 // MakeGroupObsoleteTask -------
1110 class AppCacheStorageImpl::MakeGroupObsoleteTask : public DatabaseTask {
1111 public:
1112 MakeGroupObsoleteTask(AppCacheStorageImpl* storage, AppCacheGroup* group);
1114 // DatabaseTask:
1115 virtual void Run() OVERRIDE;
1116 virtual void RunCompleted() OVERRIDE;
1117 virtual void CancelCompletion() OVERRIDE;
1119 protected:
1120 virtual ~MakeGroupObsoleteTask() {}
1122 private:
1123 scoped_refptr<AppCacheGroup> group_;
1124 int64 group_id_;
1125 GURL origin_;
1126 bool success_;
1127 int64 new_origin_usage_;
1128 std::vector<int64> newly_deletable_response_ids_;
1131 AppCacheStorageImpl::MakeGroupObsoleteTask::MakeGroupObsoleteTask(
1132 AppCacheStorageImpl* storage, AppCacheGroup* group)
1133 : DatabaseTask(storage), group_(group), group_id_(group->group_id()),
1134 origin_(group->manifest_url().GetOrigin()),
1135 success_(false), new_origin_usage_(-1) {
1138 void AppCacheStorageImpl::MakeGroupObsoleteTask::Run() {
1139 DCHECK(!success_);
1140 sql::Connection* connection = database_->db_connection();
1141 if (!connection)
1142 return;
1144 sql::Transaction transaction(connection);
1145 if (!transaction.Begin())
1146 return;
1148 AppCacheDatabase::GroupRecord group_record;
1149 if (!database_->FindGroup(group_id_, &group_record)) {
1150 // This group doesn't exists in the database, nothing todo here.
1151 new_origin_usage_ = database_->GetOriginUsage(origin_);
1152 success_ = true;
1153 return;
1156 DCHECK_EQ(group_record.origin, origin_);
1157 success_ = DeleteGroupAndRelatedRecords(database_,
1158 group_id_,
1159 &newly_deletable_response_ids_);
1161 new_origin_usage_ = database_->GetOriginUsage(origin_);
1162 success_ = success_ && transaction.Commit();
1165 void AppCacheStorageImpl::MakeGroupObsoleteTask::RunCompleted() {
1166 if (success_) {
1167 group_->set_obsolete(true);
1168 if (!storage_->is_disabled()) {
1169 storage_->UpdateUsageMapAndNotify(origin_, new_origin_usage_);
1170 group_->AddNewlyDeletableResponseIds(&newly_deletable_response_ids_);
1172 // Also remove from the working set, caches for an 'obsolete' group
1173 // may linger in use, but the group itself cannot be looked up by
1174 // 'manifest_url' in the working set any longer.
1175 storage_->working_set()->RemoveGroup(group_.get());
1178 FOR_EACH_DELEGATE(delegates_, OnGroupMadeObsolete(group_.get(), success_));
1179 group_ = NULL;
1182 void AppCacheStorageImpl::MakeGroupObsoleteTask::CancelCompletion() {
1183 // Overriden to safely drop our reference to the group
1184 // which is not thread safe refcounted.
1185 DatabaseTask::CancelCompletion();
1186 group_ = NULL;
1189 // GetDeletableResponseIdsTask -------
1191 class AppCacheStorageImpl::GetDeletableResponseIdsTask : public DatabaseTask {
1192 public:
1193 GetDeletableResponseIdsTask(AppCacheStorageImpl* storage, int64 max_rowid)
1194 : DatabaseTask(storage), max_rowid_(max_rowid) {}
1196 // DatabaseTask:
1197 virtual void Run() OVERRIDE;
1198 virtual void RunCompleted() OVERRIDE;
1200 protected:
1201 virtual ~GetDeletableResponseIdsTask() {}
1203 private:
1204 int64 max_rowid_;
1205 std::vector<int64> response_ids_;
1208 void AppCacheStorageImpl::GetDeletableResponseIdsTask::Run() {
1209 const int kSqlLimit = 1000;
1210 database_->GetDeletableResponseIds(&response_ids_, max_rowid_, kSqlLimit);
1211 // TODO(michaeln): retrieve group_ids too
1214 void AppCacheStorageImpl::GetDeletableResponseIdsTask::RunCompleted() {
1215 if (!response_ids_.empty())
1216 storage_->StartDeletingResponses(response_ids_);
1219 // InsertDeletableResponseIdsTask -------
1221 class AppCacheStorageImpl::InsertDeletableResponseIdsTask
1222 : public DatabaseTask {
1223 public:
1224 explicit InsertDeletableResponseIdsTask(AppCacheStorageImpl* storage)
1225 : DatabaseTask(storage) {}
1227 // DatabaseTask:
1228 virtual void Run() OVERRIDE;
1230 std::vector<int64> response_ids_;
1232 protected:
1233 virtual ~InsertDeletableResponseIdsTask() {}
1236 void AppCacheStorageImpl::InsertDeletableResponseIdsTask::Run() {
1237 database_->InsertDeletableResponseIds(response_ids_);
1238 // TODO(michaeln): store group_ids too
1241 // DeleteDeletableResponseIdsTask -------
1243 class AppCacheStorageImpl::DeleteDeletableResponseIdsTask
1244 : public DatabaseTask {
1245 public:
1246 explicit DeleteDeletableResponseIdsTask(AppCacheStorageImpl* storage)
1247 : DatabaseTask(storage) {}
1249 // DatabaseTask:
1250 virtual void Run() OVERRIDE;
1252 std::vector<int64> response_ids_;
1254 protected:
1255 virtual ~DeleteDeletableResponseIdsTask() {}
1258 void AppCacheStorageImpl::DeleteDeletableResponseIdsTask::Run() {
1259 database_->DeleteDeletableResponseIds(response_ids_);
1262 // UpdateGroupLastAccessTimeTask -------
1264 class AppCacheStorageImpl::UpdateGroupLastAccessTimeTask
1265 : public DatabaseTask {
1266 public:
1267 UpdateGroupLastAccessTimeTask(
1268 AppCacheStorageImpl* storage, AppCacheGroup* group, base::Time time)
1269 : DatabaseTask(storage), group_id_(group->group_id()),
1270 last_access_time_(time) {
1271 storage->NotifyStorageAccessed(group->manifest_url().GetOrigin());
1274 // DatabaseTask:
1275 virtual void Run() OVERRIDE;
1277 protected:
1278 virtual ~UpdateGroupLastAccessTimeTask() {}
1280 private:
1281 int64 group_id_;
1282 base::Time last_access_time_;
1285 void AppCacheStorageImpl::UpdateGroupLastAccessTimeTask::Run() {
1286 database_->UpdateGroupLastAccessTime(group_id_, last_access_time_);
1290 // AppCacheStorageImpl ---------------------------------------------------
1292 AppCacheStorageImpl::AppCacheStorageImpl(AppCacheService* service)
1293 : AppCacheStorage(service),
1294 is_incognito_(false),
1295 is_response_deletion_scheduled_(false),
1296 did_start_deleting_responses_(false),
1297 last_deletable_response_rowid_(0),
1298 database_(NULL),
1299 is_disabled_(false),
1300 weak_factory_(this) {
1303 AppCacheStorageImpl::~AppCacheStorageImpl() {
1304 std::for_each(pending_quota_queries_.begin(),
1305 pending_quota_queries_.end(),
1306 std::mem_fun(&DatabaseTask::CancelCompletion));
1307 std::for_each(scheduled_database_tasks_.begin(),
1308 scheduled_database_tasks_.end(),
1309 std::mem_fun(&DatabaseTask::CancelCompletion));
1311 if (database_ &&
1312 !db_thread_->PostTask(
1313 FROM_HERE,
1314 base::Bind(&ClearSessionOnlyOrigins, database_,
1315 make_scoped_refptr(service_->special_storage_policy()),
1316 service()->force_keep_session_state()))) {
1317 delete database_;
1321 void AppCacheStorageImpl::Initialize(const base::FilePath& cache_directory,
1322 base::MessageLoopProxy* db_thread,
1323 base::MessageLoopProxy* cache_thread) {
1324 DCHECK(db_thread);
1326 cache_directory_ = cache_directory;
1327 is_incognito_ = cache_directory_.empty();
1329 base::FilePath db_file_path;
1330 if (!is_incognito_)
1331 db_file_path = cache_directory_.Append(kAppCacheDatabaseName);
1332 database_ = new AppCacheDatabase(db_file_path);
1334 db_thread_ = db_thread;
1335 cache_thread_ = cache_thread;
1337 scoped_refptr<InitTask> task(new InitTask(this));
1338 task->Schedule();
1341 void AppCacheStorageImpl::Disable() {
1342 if (is_disabled_)
1343 return;
1344 VLOG(1) << "Disabling appcache storage.";
1345 is_disabled_ = true;
1346 ClearUsageMapAndNotify();
1347 working_set()->Disable();
1348 if (disk_cache_)
1349 disk_cache_->Disable();
1350 scoped_refptr<DisableDatabaseTask> task(new DisableDatabaseTask(this));
1351 task->Schedule();
1354 void AppCacheStorageImpl::GetAllInfo(Delegate* delegate) {
1355 DCHECK(delegate);
1356 scoped_refptr<GetAllInfoTask> task(new GetAllInfoTask(this));
1357 task->AddDelegate(GetOrCreateDelegateReference(delegate));
1358 task->Schedule();
1361 void AppCacheStorageImpl::LoadCache(int64 id, Delegate* delegate) {
1362 DCHECK(delegate);
1363 if (is_disabled_) {
1364 delegate->OnCacheLoaded(NULL, id);
1365 return;
1368 AppCache* cache = working_set_.GetCache(id);
1369 if (cache) {
1370 delegate->OnCacheLoaded(cache, id);
1371 if (cache->owning_group()) {
1372 scoped_refptr<DatabaseTask> update_task(
1373 new UpdateGroupLastAccessTimeTask(
1374 this, cache->owning_group(), base::Time::Now()));
1375 update_task->Schedule();
1377 return;
1379 scoped_refptr<CacheLoadTask> task(GetPendingCacheLoadTask(id));
1380 if (task.get()) {
1381 task->AddDelegate(GetOrCreateDelegateReference(delegate));
1382 return;
1384 task = new CacheLoadTask(id, this);
1385 task->AddDelegate(GetOrCreateDelegateReference(delegate));
1386 task->Schedule();
1387 pending_cache_loads_[id] = task.get();
1390 void AppCacheStorageImpl::LoadOrCreateGroup(
1391 const GURL& manifest_url, Delegate* delegate) {
1392 DCHECK(delegate);
1393 if (is_disabled_) {
1394 delegate->OnGroupLoaded(NULL, manifest_url);
1395 return;
1398 AppCacheGroup* group = working_set_.GetGroup(manifest_url);
1399 if (group) {
1400 delegate->OnGroupLoaded(group, manifest_url);
1401 scoped_refptr<DatabaseTask> update_task(
1402 new UpdateGroupLastAccessTimeTask(
1403 this, group, base::Time::Now()));
1404 update_task->Schedule();
1405 return;
1408 scoped_refptr<GroupLoadTask> task(GetPendingGroupLoadTask(manifest_url));
1409 if (task.get()) {
1410 task->AddDelegate(GetOrCreateDelegateReference(delegate));
1411 return;
1414 if (usage_map_.find(manifest_url.GetOrigin()) == usage_map_.end()) {
1415 // No need to query the database, return a new group immediately.
1416 scoped_refptr<AppCacheGroup> group(new AppCacheGroup(
1417 service_->storage(), manifest_url, NewGroupId()));
1418 delegate->OnGroupLoaded(group.get(), manifest_url);
1419 return;
1422 task = new GroupLoadTask(manifest_url, this);
1423 task->AddDelegate(GetOrCreateDelegateReference(delegate));
1424 task->Schedule();
1425 pending_group_loads_[manifest_url] = task.get();
1428 void AppCacheStorageImpl::StoreGroupAndNewestCache(
1429 AppCacheGroup* group, AppCache* newest_cache, Delegate* delegate) {
1430 // TODO(michaeln): distinguish between a simple update of an existing
1431 // cache that just adds new master entry(s), and the insertion of a
1432 // whole new cache. The StoreGroupAndCacheTask as written will handle
1433 // the simple update case in a very heavy weight way (delete all and
1434 // the reinsert all over again).
1435 DCHECK(group && delegate && newest_cache);
1436 scoped_refptr<StoreGroupAndCacheTask> task(
1437 new StoreGroupAndCacheTask(this, group, newest_cache));
1438 task->AddDelegate(GetOrCreateDelegateReference(delegate));
1439 task->GetQuotaThenSchedule();
1441 // TODO(michaeln): histogram is fishing for clues to crbug/95101
1442 if (!newest_cache->GetEntry(group->manifest_url())) {
1443 AppCacheHistograms::AddMissingManifestDetectedAtCallsite(
1444 AppCacheHistograms::CALLSITE_3);
1448 void AppCacheStorageImpl::FindResponseForMainRequest(
1449 const GURL& url, const GURL& preferred_manifest_url,
1450 Delegate* delegate) {
1451 DCHECK(delegate);
1453 const GURL* url_ptr = &url;
1454 GURL url_no_ref;
1455 if (url.has_ref()) {
1456 GURL::Replacements replacements;
1457 replacements.ClearRef();
1458 url_no_ref = url.ReplaceComponents(replacements);
1459 url_ptr = &url_no_ref;
1462 const GURL origin = url.GetOrigin();
1464 // First look in our working set for a direct hit without having to query
1465 // the database.
1466 const AppCacheWorkingSet::GroupMap* groups_in_use =
1467 working_set()->GetGroupsInOrigin(origin);
1468 if (groups_in_use) {
1469 if (!preferred_manifest_url.is_empty()) {
1470 AppCacheWorkingSet::GroupMap::const_iterator found =
1471 groups_in_use->find(preferred_manifest_url);
1472 if (found != groups_in_use->end() &&
1473 FindResponseForMainRequestInGroup(
1474 found->second, *url_ptr, delegate)) {
1475 return;
1477 } else {
1478 for (AppCacheWorkingSet::GroupMap::const_iterator it =
1479 groups_in_use->begin();
1480 it != groups_in_use->end(); ++it) {
1481 if (FindResponseForMainRequestInGroup(
1482 it->second, *url_ptr, delegate)) {
1483 return;
1489 if (IsInitTaskComplete() && usage_map_.find(origin) == usage_map_.end()) {
1490 // No need to query the database, return async'ly but without going thru
1491 // the DB thread.
1492 scoped_refptr<AppCacheGroup> no_group;
1493 scoped_refptr<AppCache> no_cache;
1494 ScheduleSimpleTask(
1495 base::Bind(&AppCacheStorageImpl::DeliverShortCircuitedFindMainResponse,
1496 weak_factory_.GetWeakPtr(), url, AppCacheEntry(), no_group,
1497 no_cache,
1498 make_scoped_refptr(GetOrCreateDelegateReference(delegate))));
1499 return;
1502 // We have to query the database, schedule a database task to do so.
1503 scoped_refptr<FindMainResponseTask> task(
1504 new FindMainResponseTask(this, *url_ptr, preferred_manifest_url,
1505 groups_in_use));
1506 task->AddDelegate(GetOrCreateDelegateReference(delegate));
1507 task->Schedule();
1510 bool AppCacheStorageImpl::FindResponseForMainRequestInGroup(
1511 AppCacheGroup* group, const GURL& url, Delegate* delegate) {
1512 AppCache* cache = group->newest_complete_cache();
1513 if (group->is_obsolete() || !cache)
1514 return false;
1516 AppCacheEntry* entry = cache->GetEntry(url);
1517 if (!entry || entry->IsForeign())
1518 return false;
1520 ScheduleSimpleTask(
1521 base::Bind(&AppCacheStorageImpl::DeliverShortCircuitedFindMainResponse,
1522 weak_factory_.GetWeakPtr(), url, *entry,
1523 make_scoped_refptr(group), make_scoped_refptr(cache),
1524 make_scoped_refptr(GetOrCreateDelegateReference(delegate))));
1525 return true;
1528 void AppCacheStorageImpl::DeliverShortCircuitedFindMainResponse(
1529 const GURL& url,
1530 const AppCacheEntry& found_entry,
1531 scoped_refptr<AppCacheGroup> group,
1532 scoped_refptr<AppCache> cache,
1533 scoped_refptr<DelegateReference> delegate_ref) {
1534 if (delegate_ref->delegate) {
1535 DelegateReferenceVector delegates(1, delegate_ref);
1536 CallOnMainResponseFound(
1537 &delegates, url, found_entry,
1538 GURL(), AppCacheEntry(),
1539 cache.get() ? cache->cache_id() : kNoCacheId,
1540 group.get() ? group->group_id() : kNoCacheId,
1541 group.get() ? group->manifest_url() : GURL());
1545 void AppCacheStorageImpl::CallOnMainResponseFound(
1546 DelegateReferenceVector* delegates,
1547 const GURL& url, const AppCacheEntry& entry,
1548 const GURL& namespace_entry_url, const AppCacheEntry& fallback_entry,
1549 int64 cache_id, int64 group_id, const GURL& manifest_url) {
1550 FOR_EACH_DELEGATE(
1551 (*delegates),
1552 OnMainResponseFound(url, entry,
1553 namespace_entry_url, fallback_entry,
1554 cache_id, group_id, manifest_url));
1557 void AppCacheStorageImpl::FindResponseForSubRequest(
1558 AppCache* cache, const GURL& url,
1559 AppCacheEntry* found_entry, AppCacheEntry* found_fallback_entry,
1560 bool* found_network_namespace) {
1561 DCHECK(cache && cache->is_complete());
1563 // When a group is forcibly deleted, all subresource loads for pages
1564 // using caches in the group will result in a synthesized network errors.
1565 // Forcible deletion is not a function that is covered by the HTML5 spec.
1566 if (cache->owning_group()->is_being_deleted()) {
1567 *found_entry = AppCacheEntry();
1568 *found_fallback_entry = AppCacheEntry();
1569 *found_network_namespace = false;
1570 return;
1573 GURL fallback_namespace_not_used;
1574 GURL intercept_namespace_not_used;
1575 cache->FindResponseForRequest(
1576 url, found_entry, &intercept_namespace_not_used,
1577 found_fallback_entry, &fallback_namespace_not_used,
1578 found_network_namespace);
1581 void AppCacheStorageImpl::MarkEntryAsForeign(
1582 const GURL& entry_url, int64 cache_id) {
1583 AppCache* cache = working_set_.GetCache(cache_id);
1584 if (cache) {
1585 AppCacheEntry* entry = cache->GetEntry(entry_url);
1586 DCHECK(entry);
1587 if (entry)
1588 entry->add_types(AppCacheEntry::FOREIGN);
1590 scoped_refptr<MarkEntryAsForeignTask> task(
1591 new MarkEntryAsForeignTask(this, entry_url, cache_id));
1592 task->Schedule();
1593 pending_foreign_markings_.push_back(std::make_pair(entry_url, cache_id));
1596 void AppCacheStorageImpl::MakeGroupObsolete(
1597 AppCacheGroup* group, Delegate* delegate) {
1598 DCHECK(group && delegate);
1599 scoped_refptr<MakeGroupObsoleteTask> task(
1600 new MakeGroupObsoleteTask(this, group));
1601 task->AddDelegate(GetOrCreateDelegateReference(delegate));
1602 task->Schedule();
1605 AppCacheResponseReader* AppCacheStorageImpl::CreateResponseReader(
1606 const GURL& manifest_url, int64 group_id, int64 response_id) {
1607 return new AppCacheResponseReader(response_id, group_id, disk_cache());
1610 AppCacheResponseWriter* AppCacheStorageImpl::CreateResponseWriter(
1611 const GURL& manifest_url, int64 group_id) {
1612 return new AppCacheResponseWriter(NewResponseId(), group_id, disk_cache());
1615 void AppCacheStorageImpl::DoomResponses(
1616 const GURL& manifest_url, const std::vector<int64>& response_ids) {
1617 if (response_ids.empty())
1618 return;
1620 // Start deleting them from the disk cache lazily.
1621 StartDeletingResponses(response_ids);
1623 // Also schedule a database task to record these ids in the
1624 // deletable responses table.
1625 // TODO(michaeln): There is a race here. If the browser crashes
1626 // prior to committing these rows to the database and prior to us
1627 // having deleted them from the disk cache, we'll never delete them.
1628 scoped_refptr<InsertDeletableResponseIdsTask> task(
1629 new InsertDeletableResponseIdsTask(this));
1630 task->response_ids_ = response_ids;
1631 task->Schedule();
1634 void AppCacheStorageImpl::DeleteResponses(
1635 const GURL& manifest_url, const std::vector<int64>& response_ids) {
1636 if (response_ids.empty())
1637 return;
1638 StartDeletingResponses(response_ids);
1641 void AppCacheStorageImpl::PurgeMemory() {
1642 scoped_refptr<CloseConnectionTask> task(new CloseConnectionTask(this));
1643 task->Schedule();
1646 void AppCacheStorageImpl::DelayedStartDeletingUnusedResponses() {
1647 // Only if we haven't already begun.
1648 if (!did_start_deleting_responses_) {
1649 scoped_refptr<GetDeletableResponseIdsTask> task(
1650 new GetDeletableResponseIdsTask(this, last_deletable_response_rowid_));
1651 task->Schedule();
1655 void AppCacheStorageImpl::StartDeletingResponses(
1656 const std::vector<int64>& response_ids) {
1657 DCHECK(!response_ids.empty());
1658 did_start_deleting_responses_ = true;
1659 deletable_response_ids_.insert(
1660 deletable_response_ids_.end(),
1661 response_ids.begin(), response_ids.end());
1662 if (!is_response_deletion_scheduled_)
1663 ScheduleDeleteOneResponse();
1666 void AppCacheStorageImpl::ScheduleDeleteOneResponse() {
1667 DCHECK(!is_response_deletion_scheduled_);
1668 const base::TimeDelta kDelay = base::TimeDelta::FromMilliseconds(10);
1669 base::MessageLoop::current()->PostDelayedTask(
1670 FROM_HERE,
1671 base::Bind(&AppCacheStorageImpl::DeleteOneResponse,
1672 weak_factory_.GetWeakPtr()),
1673 kDelay);
1674 is_response_deletion_scheduled_ = true;
1677 void AppCacheStorageImpl::DeleteOneResponse() {
1678 DCHECK(is_response_deletion_scheduled_);
1679 DCHECK(!deletable_response_ids_.empty());
1681 if (!disk_cache()) {
1682 DCHECK(is_disabled_);
1683 deletable_response_ids_.clear();
1684 deleted_response_ids_.clear();
1685 is_response_deletion_scheduled_ = false;
1686 return;
1689 // TODO(michaeln): add group_id to DoomEntry args
1690 int64 id = deletable_response_ids_.front();
1691 int rv = disk_cache_->DoomEntry(
1692 id, base::Bind(&AppCacheStorageImpl::OnDeletedOneResponse,
1693 base::Unretained(this)));
1694 if (rv != net::ERR_IO_PENDING)
1695 OnDeletedOneResponse(rv);
1698 void AppCacheStorageImpl::OnDeletedOneResponse(int rv) {
1699 is_response_deletion_scheduled_ = false;
1700 if (is_disabled_)
1701 return;
1703 int64 id = deletable_response_ids_.front();
1704 deletable_response_ids_.pop_front();
1705 if (rv != net::ERR_ABORTED)
1706 deleted_response_ids_.push_back(id);
1708 const size_t kBatchSize = 50U;
1709 if (deleted_response_ids_.size() >= kBatchSize ||
1710 deletable_response_ids_.empty()) {
1711 scoped_refptr<DeleteDeletableResponseIdsTask> task(
1712 new DeleteDeletableResponseIdsTask(this));
1713 task->response_ids_.swap(deleted_response_ids_);
1714 task->Schedule();
1717 if (deletable_response_ids_.empty()) {
1718 scoped_refptr<GetDeletableResponseIdsTask> task(
1719 new GetDeletableResponseIdsTask(this, last_deletable_response_rowid_));
1720 task->Schedule();
1721 return;
1724 ScheduleDeleteOneResponse();
1727 AppCacheStorageImpl::CacheLoadTask*
1728 AppCacheStorageImpl::GetPendingCacheLoadTask(int64 cache_id) {
1729 PendingCacheLoads::iterator found = pending_cache_loads_.find(cache_id);
1730 if (found != pending_cache_loads_.end())
1731 return found->second;
1732 return NULL;
1735 AppCacheStorageImpl::GroupLoadTask*
1736 AppCacheStorageImpl::GetPendingGroupLoadTask(const GURL& manifest_url) {
1737 PendingGroupLoads::iterator found = pending_group_loads_.find(manifest_url);
1738 if (found != pending_group_loads_.end())
1739 return found->second;
1740 return NULL;
1743 void AppCacheStorageImpl::GetPendingForeignMarkingsForCache(
1744 int64 cache_id, std::vector<GURL>* urls) {
1745 PendingForeignMarkings::iterator iter = pending_foreign_markings_.begin();
1746 while (iter != pending_foreign_markings_.end()) {
1747 if (iter->second == cache_id)
1748 urls->push_back(iter->first);
1749 ++iter;
1753 void AppCacheStorageImpl::ScheduleSimpleTask(const base::Closure& task) {
1754 pending_simple_tasks_.push_back(task);
1755 base::MessageLoop::current()->PostTask(
1756 FROM_HERE,
1757 base::Bind(&AppCacheStorageImpl::RunOnePendingSimpleTask,
1758 weak_factory_.GetWeakPtr()));
1761 void AppCacheStorageImpl::RunOnePendingSimpleTask() {
1762 DCHECK(!pending_simple_tasks_.empty());
1763 base::Closure task = pending_simple_tasks_.front();
1764 pending_simple_tasks_.pop_front();
1765 task.Run();
1768 AppCacheDiskCache* AppCacheStorageImpl::disk_cache() {
1769 DCHECK(IsInitTaskComplete());
1771 if (is_disabled_)
1772 return NULL;
1774 if (!disk_cache_) {
1775 int rv = net::OK;
1776 disk_cache_.reset(new AppCacheDiskCache);
1777 if (is_incognito_) {
1778 rv = disk_cache_->InitWithMemBackend(
1779 kMaxMemDiskCacheSize,
1780 base::Bind(&AppCacheStorageImpl::OnDiskCacheInitialized,
1781 base::Unretained(this)));
1782 } else {
1783 rv = disk_cache_->InitWithDiskBackend(
1784 cache_directory_.Append(kDiskCacheDirectoryName),
1785 kMaxDiskCacheSize,
1786 false,
1787 cache_thread_.get(),
1788 base::Bind(&AppCacheStorageImpl::OnDiskCacheInitialized,
1789 base::Unretained(this)));
1792 // We should not keep this reference around.
1793 cache_thread_ = NULL;
1795 if (rv != net::ERR_IO_PENDING)
1796 OnDiskCacheInitialized(rv);
1798 return disk_cache_.get();
1801 void AppCacheStorageImpl::OnDiskCacheInitialized(int rv) {
1802 if (rv != net::OK) {
1803 LOG(ERROR) << "Failed to open the appcache diskcache.";
1804 AppCacheHistograms::CountInitResult(AppCacheHistograms::DISK_CACHE_ERROR);
1806 // We're unable to open the disk cache, this is a fatal error that we can't
1807 // really recover from. We handle it by disabling the appcache for this
1808 // browser session and deleting the directory on disk. The next browser
1809 // session should start with a clean slate.
1810 Disable();
1811 if (!is_incognito_) {
1812 VLOG(1) << "Deleting existing appcache data and starting over.";
1813 db_thread_->PostTask(
1814 FROM_HERE, base::Bind(base::IgnoreResult(&base::DeleteFile),
1815 cache_directory_, true));
1820 } // namespace appcache