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.
4
5 #include "content/browser/appcache/appcache_storage_impl.h"
6
7 #include <stddef.h>
8
9 #include <algorithm>
10 #include <functional>
11 #include <limits>
12 #include <set>
13 #include <vector>
14
15 #include "base/bind.h"
16 #include "base/bind_helpers.h"
17 #include "base/files/file_util.h"
18 #include "base/location.h"
19 #include "base/logging.h"
20 #include "base/single_thread_task_runner.h"
21 #include "base/stl_util.h"
22 #include "base/strings/string_util.h"
23 #include "base/task/post_task.h"
24 #include "base/threading/sequenced_task_runner_handle.h"
25 #include "content/browser/appcache/appcache.h"
26 #include "content/browser/appcache/appcache_database.h"
27 #include "content/browser/appcache/appcache_disk_cache_ops.h"
28 #include "content/browser/appcache/appcache_entry.h"
29 #include "content/browser/appcache/appcache_group.h"
30 #include "content/browser/appcache/appcache_histograms.h"
31 #include "content/browser/appcache/appcache_quota_client.h"
32 #include "content/browser/appcache/appcache_response_info.h"
33 #include "content/browser/appcache/appcache_service_impl.h"
34 #include "content/public/browser/browser_task_traits.h"
35 #include "net/base/cache_type.h"
36 #include "net/base/net_errors.h"
37 #include "sql/database.h"
38 #include "sql/transaction.h"
39 #include "storage/browser/quota/quota_client.h"
40 #include "storage/browser/quota/quota_manager.h"
41 #include "storage/browser/quota/quota_manager_proxy.h"
42 #include "third_party/blink/public/mojom/appcache/appcache_info.mojom.h"
43 #include "third_party/blink/public/mojom/quota/quota_types.mojom.h"
44
45 namespace content {
46
47 namespace {
48
49 constexpr const int kMB = 1024 * 1024;
50
51 // Hard coded default when not using quota management.
52 constexpr const int kDefaultQuota = 5 * kMB;
53
54 constexpr base::FilePath::CharType kDiskCacheDirectoryName[] =
55 FILE_PATH_LITERAL("Cache");
56 constexpr base::FilePath::CharType kAppCacheDatabaseName[] =
57 FILE_PATH_LITERAL("Index");
58
59 // Helpers for clearing data from the AppCacheDatabase.
DeleteGroupAndRelatedRecords(AppCacheDatabase * database,int64_t group_id,std::vector<int64_t> * deletable_response_ids)60 bool DeleteGroupAndRelatedRecords(
61 AppCacheDatabase* database,
62 int64_t group_id,
63 std::vector<int64_t>* deletable_response_ids) {
64 AppCacheDatabase::CacheRecord cache_record;
65 bool success = false;
66 if (database->FindCacheForGroup(group_id, &cache_record)) {
67 database->FindResponseIdsForCacheAsVector(cache_record.cache_id,
68 deletable_response_ids);
69 success =
70 database->DeleteGroup(group_id) &&
71 database->DeleteCache(cache_record.cache_id) &&
72 database->DeleteEntriesForCache(cache_record.cache_id) &&
73 database->DeleteNamespacesForCache(cache_record.cache_id) &&
74 database->DeleteOnlineWhiteListForCache(cache_record.cache_id) &&
75 database->InsertDeletableResponseIds(*deletable_response_ids);
76 } else {
77 NOTREACHED() << "A existing group without a cache is unexpected";
78 success = database->DeleteGroup(group_id);
79 }
80 return success;
81 }
82
83 } // namespace
84
85 // static
ClearSessionOnlyOrigins(std::unique_ptr<AppCacheDatabase> database,scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy,bool force_keep_session_state)86 void AppCacheStorageImpl::ClearSessionOnlyOrigins(
87 std::unique_ptr<AppCacheDatabase> database,
88 scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy,
89 bool force_keep_session_state) {
90 // If saving session state, only delete the database.
91 if (force_keep_session_state)
92 return;
93
94 bool has_session_only_appcaches =
95 special_storage_policy.get() &&
96 special_storage_policy->HasSessionOnlyOrigins();
97
98 // Clearning only session-only databases, and there are none.
99 if (!has_session_only_appcaches)
100 return;
101
102 std::set<url::Origin> origins;
103 database->FindOriginsWithGroups(&origins);
104 if (origins.empty())
105 return; // nothing to delete
106
107 sql::Database* connection = database->db_connection();
108 if (!connection) {
109 NOTREACHED() << "Missing database connection.";
110 return;
111 }
112
113 for (const url::Origin& origin : origins) {
114 if (!special_storage_policy->IsStorageSessionOnly(origin.GetURL()))
115 continue;
116 if (special_storage_policy->IsStorageProtected(origin.GetURL()))
117 continue;
118
119 std::vector<AppCacheDatabase::GroupRecord> groups;
120 database->FindGroupsForOrigin(origin, &groups);
121 for (const auto& group : groups) {
122 sql::Transaction transaction(connection);
123 if (!transaction.Begin()) {
124 NOTREACHED() << "Failed to start transaction";
125 return;
126 }
127 std::vector<int64_t> deletable_response_ids;
128 bool success = DeleteGroupAndRelatedRecords(
129 database.get(), group.group_id, &deletable_response_ids);
130 success = success && transaction.Commit();
131 DCHECK(success);
132 } // for each group
133 } // for each origin
134 }
135
136 // DatabaseTask -----------------------------------------
137
138 class AppCacheStorageImpl::DatabaseTask
139 : public base::RefCountedThreadSafe<DatabaseTask> {
140 public:
DatabaseTask(AppCacheStorageImpl * storage)141 explicit DatabaseTask(AppCacheStorageImpl* storage)
142 : storage_(storage),
143 database_(storage->database_.get()),
144 io_thread_(base::SequencedTaskRunnerHandle::Get()) {
145 DCHECK(io_thread_.get());
146 }
147
AddDelegate(DelegateReference * delegate_reference)148 void AddDelegate(DelegateReference* delegate_reference) {
149 delegates_.push_back(base::WrapRefCounted(delegate_reference));
150 }
151
152 // Schedules a task to be Run() on the DB thread. Tasks
153 // are run in the order in which they are scheduled.
154 void Schedule();
155
156 // Called on the DB thread.
157 virtual void Run() = 0;
158
159 // Called on the IO thread after Run() has completed.
RunCompleted()160 virtual void RunCompleted() {}
161
162 // Once scheduled a task cannot be cancelled, but the
163 // call to RunCompleted may be. This method should only be
164 // called on the IO thread. This is used by AppCacheStorageImpl
165 // to cancel the completion calls when AppCacheStorageImpl is
166 // destructed. This method may be overriden to release or delete
167 // additional data associated with the task that is not DB thread
168 // safe. If overriden, this base class method must be called from
169 // within the override.
170 virtual void CancelCompletion();
171
172 protected:
173 friend class base::RefCountedThreadSafe<DatabaseTask>;
174 virtual ~DatabaseTask() = default;
175
176 AppCacheStorageImpl* storage_;
177 AppCacheDatabase* const database_;
178 std::vector<scoped_refptr<DelegateReference>> delegates_;
179
180 private:
181 void CallRun();
182 void CallRunCompleted();
183 void OnFatalError();
184
185 const scoped_refptr<base::SequencedTaskRunner> io_thread_;
186 };
187
Schedule()188 void AppCacheStorageImpl::DatabaseTask::Schedule() {
189 DCHECK(storage_);
190 DCHECK(io_thread_->RunsTasksInCurrentSequence());
191 if (!storage_->database_)
192 return;
193
194 if (storage_->db_task_runner_->PostTask(
195 FROM_HERE, base::BindOnce(&DatabaseTask::CallRun, this))) {
196 storage_->scheduled_database_tasks_.push_back(this);
197 } else {
198 NOTREACHED() << "Thread for database tasks is not running.";
199 }
200 }
201
CancelCompletion()202 void AppCacheStorageImpl::DatabaseTask::CancelCompletion() {
203 DCHECK(io_thread_->RunsTasksInCurrentSequence());
204 delegates_.clear();
205 storage_ = nullptr;
206 }
207
CallRun()208 void AppCacheStorageImpl::DatabaseTask::CallRun() {
209 if (!database_->is_disabled()) {
210 Run();
211 if (database_->was_corruption_detected()) {
212 database_->Disable();
213 }
214 if (database_->is_disabled()) {
215 io_thread_->PostTask(FROM_HERE,
216 base::BindOnce(&DatabaseTask::OnFatalError, this));
217 }
218 }
219 io_thread_->PostTask(FROM_HERE,
220 base::BindOnce(&DatabaseTask::CallRunCompleted, this));
221 }
222
CallRunCompleted()223 void AppCacheStorageImpl::DatabaseTask::CallRunCompleted() {
224 if (storage_) {
225 DCHECK(io_thread_->RunsTasksInCurrentSequence());
226 DCHECK(storage_->scheduled_database_tasks_.front() == this);
227 storage_->scheduled_database_tasks_.pop_front();
228 RunCompleted();
229 delegates_.clear();
230 }
231 }
232
OnFatalError()233 void AppCacheStorageImpl::DatabaseTask::OnFatalError() {
234 if (storage_) {
235 DCHECK(io_thread_->RunsTasksInCurrentSequence());
236 storage_->Disable();
237 storage_->DeleteAndStartOver();
238 }
239 }
240
241 // InitTask -------
242
243 class AppCacheStorageImpl::InitTask : public DatabaseTask {
244 public:
InitTask(AppCacheStorageImpl * storage)245 explicit InitTask(AppCacheStorageImpl* storage)
246 : DatabaseTask(storage), last_group_id_(0),
247 last_cache_id_(0), last_response_id_(0),
248 last_deletable_response_rowid_(0) {
249 if (!storage->is_incognito_) {
250 db_file_path_ =
251 storage->cache_directory_.Append(kAppCacheDatabaseName);
252 disk_cache_directory_ =
253 storage->cache_directory_.Append(kDiskCacheDirectoryName);
254 }
255 }
256
257 // DatabaseTask:
258 void Run() override;
259 void RunCompleted() override;
260
261 protected:
262 ~InitTask() override = default;
263
264 private:
265 base::FilePath db_file_path_;
266 base::FilePath disk_cache_directory_;
267 int64_t last_group_id_;
268 int64_t last_cache_id_;
269 int64_t last_response_id_;
270 int64_t last_deletable_response_rowid_;
271 std::map<url::Origin, int64_t> usage_map_;
272 };
273
Run()274 void AppCacheStorageImpl::InitTask::Run() {
275 // If there is no sql database, ensure there is no disk cache either.
276 if (!db_file_path_.empty() &&
277 !base::PathExists(db_file_path_) &&
278 base::DirectoryExists(disk_cache_directory_)) {
279 base::DeleteFileRecursively(disk_cache_directory_);
280 if (base::DirectoryExists(disk_cache_directory_)) {
281 database_->Disable(); // This triggers OnFatalError handling.
282 return;
283 }
284 }
285
286 database_->FindLastStorageIds(
287 &last_group_id_, &last_cache_id_, &last_response_id_,
288 &last_deletable_response_rowid_);
289 database_->GetAllOriginUsage(&usage_map_);
290 }
291
RunCompleted()292 void AppCacheStorageImpl::InitTask::RunCompleted() {
293 storage_->last_group_id_ = last_group_id_;
294 storage_->last_cache_id_ = last_cache_id_;
295 storage_->last_response_id_ = last_response_id_;
296 storage_->last_deletable_response_rowid_ = last_deletable_response_rowid_;
297
298 if (!storage_->is_disabled()) {
299 storage_->usage_map_.swap(usage_map_);
300 const base::TimeDelta kDelay = base::TimeDelta::FromMinutes(5);
301 base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
302 FROM_HERE,
303 base::BindOnce(
304 &AppCacheStorageImpl::DelayedStartDeletingUnusedResponses,
305 storage_->weak_factory_.GetWeakPtr()),
306 kDelay);
307 }
308
309 if (storage_->service()->quota_client()) {
310 base::PostTask(
311 FROM_HERE, {BrowserThread::IO},
312 base::BindOnce(&AppCacheQuotaClient::NotifyAppCacheReady,
313 base::RetainedRef(storage_->service()->quota_client())));
314 }
315 }
316
317 // DisableDatabaseTask -------
318
319 class AppCacheStorageImpl::DisableDatabaseTask : public DatabaseTask {
320 public:
DisableDatabaseTask(AppCacheStorageImpl * storage)321 explicit DisableDatabaseTask(AppCacheStorageImpl* storage)
322 : DatabaseTask(storage) {}
323
324 // DatabaseTask:
Run()325 void Run() override { database_->Disable(); }
326
327 protected:
328 ~DisableDatabaseTask() override = default;
329 };
330
331 // GetAllInfoTask -------
332
333 class AppCacheStorageImpl::GetAllInfoTask : public DatabaseTask {
334 public:
GetAllInfoTask(AppCacheStorageImpl * storage)335 explicit GetAllInfoTask(AppCacheStorageImpl* storage)
336 : DatabaseTask(storage),
337 info_collection_(base::MakeRefCounted<AppCacheInfoCollection>()) {}
338
339 // DatabaseTask:
340 void Run() override;
341 void RunCompleted() override;
342
343 protected:
344 ~GetAllInfoTask() override = default;
345
346 private:
347 scoped_refptr<AppCacheInfoCollection> info_collection_;
348 };
349
Run()350 void AppCacheStorageImpl::GetAllInfoTask::Run() {
351 std::set<url::Origin> origins;
352 database_->FindOriginsWithGroups(&origins);
353 for (const url::Origin& origin : origins) {
354 std::vector<blink::mojom::AppCacheInfo>& infos =
355 info_collection_->infos_by_origin[origin];
356 std::vector<AppCacheDatabase::GroupRecord> groups;
357 database_->FindGroupsForOrigin(origin, &groups);
358 for (const auto& group : groups) {
359 AppCacheDatabase::CacheRecord cache_record;
360 database_->FindCacheForGroup(group.group_id, &cache_record);
361 blink::mojom::AppCacheInfo info;
362 info.manifest_url = group.manifest_url;
363 info.creation_time = group.creation_time;
364 info.response_sizes = cache_record.cache_size;
365 info.padding_sizes = cache_record.padding_size;
366 info.last_access_time = group.last_access_time;
367 info.last_update_time = cache_record.update_time;
368 // TODO(enne): should this be cache? group? both??
369 info.token_expires = group.token_expires;
370 info.cache_id = cache_record.cache_id;
371 info.group_id = group.group_id;
372 info.is_complete = true;
373 info.manifest_parser_version = cache_record.manifest_parser_version;
374 info.manifest_scope = cache_record.manifest_scope;
375 infos.push_back(info);
376 }
377 }
378 }
379
RunCompleted()380 void AppCacheStorageImpl::GetAllInfoTask::RunCompleted() {
381 DCHECK_EQ(1U, delegates_.size());
382 AppCacheStorage::ForEachDelegate(
383 delegates_, [&](AppCacheStorage::Delegate* delegate) {
384 delegate->OnAllInfo(info_collection_.get());
385 });
386 }
387
388 // StoreOrLoadTask -------
389
390 class AppCacheStorageImpl::StoreOrLoadTask : public DatabaseTask {
391 protected:
StoreOrLoadTask(AppCacheStorageImpl * storage)392 explicit StoreOrLoadTask(AppCacheStorageImpl* storage)
393 : DatabaseTask(storage) {}
394 ~StoreOrLoadTask() override = default;
395
396 bool FindRelatedCacheRecords(int64_t cache_id);
397 void CreateCacheAndGroupFromRecords(
398 scoped_refptr<AppCache>* cache, scoped_refptr<AppCacheGroup>* group);
399
400 AppCacheDatabase::GroupRecord group_record_;
401 AppCacheDatabase::CacheRecord cache_record_;
402 std::vector<AppCacheDatabase::EntryRecord> entry_records_;
403 std::vector<AppCacheDatabase::NamespaceRecord>
404 intercept_namespace_records_;
405 std::vector<AppCacheDatabase::NamespaceRecord>
406 fallback_namespace_records_;
407 std::vector<AppCacheDatabase::OnlineWhiteListRecord>
408 online_whitelist_records_;
409 };
410
FindRelatedCacheRecords(int64_t cache_id)411 bool AppCacheStorageImpl::StoreOrLoadTask::FindRelatedCacheRecords(
412 int64_t cache_id) {
413 return database_->FindEntriesForCache(cache_id, &entry_records_) &&
414 database_->FindNamespacesForCache(
415 cache_id, &intercept_namespace_records_,
416 &fallback_namespace_records_) &&
417 database_->FindOnlineWhiteListForCache(
418 cache_id, &online_whitelist_records_);
419 }
420
CreateCacheAndGroupFromRecords(scoped_refptr<AppCache> * cache,scoped_refptr<AppCacheGroup> * group)421 void AppCacheStorageImpl::StoreOrLoadTask::CreateCacheAndGroupFromRecords(
422 scoped_refptr<AppCache>* cache, scoped_refptr<AppCacheGroup>* group) {
423 DCHECK(storage_ && cache && group);
424
425 (*cache) = storage_->working_set_.GetCache(cache_record_.cache_id);
426 if (cache->get()) {
427 (*group) = cache->get()->owning_group();
428 DCHECK(group->get());
429 DCHECK_EQ(group_record_.group_id, group->get()->group_id());
430
431 // TODO(pwnall): A removed histogram shows that, very rarely,
432 // cache->get()->GetEntry(group_record_.manifest_url))
433 // return null here. This was supposed to help investigate
434 // https://crbug.com/95101
435 storage_->NotifyStorageAccessed(group_record_.origin);
436 return;
437 }
438
439 *cache = base::MakeRefCounted<AppCache>(storage_, cache_record_.cache_id);
440 cache->get()->InitializeWithDatabaseRecords(
441 cache_record_, entry_records_,
442 intercept_namespace_records_,
443 fallback_namespace_records_,
444 online_whitelist_records_);
445 cache->get()->set_complete(true);
446
447 *group = storage_->working_set_.GetGroup(group_record_.manifest_url);
448 if (group->get()) {
449 DCHECK(group_record_.group_id == group->get()->group_id());
450 group->get()->AddCache(cache->get());
451 } else {
452 *group = base::MakeRefCounted<AppCacheGroup>(
453 storage_, group_record_.manifest_url, group_record_.group_id);
454 group->get()->set_creation_time(group_record_.creation_time);
455 group->get()->set_last_full_update_check_time(
456 group_record_.last_full_update_check_time);
457 group->get()->set_first_evictable_error_time(
458 group_record_.first_evictable_error_time);
459 group->get()->AddCache(cache->get());
460
461 // TODO(pwnall): A removed histogram shows that, very rarely,
462 // cache->get()->GetEntry(group_record_.manifest_url))
463 // return null here. This was supposed to help investigate
464 // https://crbug.com/95101
465 }
466 DCHECK(group->get()->newest_complete_cache() == cache->get());
467
468 // We have to update foriegn entries if MarkEntryAsForeignTasks
469 // are in flight.
470 std::vector<GURL> urls =
471 storage_->GetPendingForeignMarkingsForCache(cache->get()->cache_id());
472 for (const auto& url : urls) {
473 // Skip any entries that were marked as foreign but that don't actually
474 // exist. This shouldn't happen other than with misbehaving renderers, but
475 // we've always just ignored these when the cache already exists when
476 // MarkEntryAsForeign is called, so also ignore them here when the cache
477 // still had to be created.
478 // If AppCache wouldn't be in maintenance mode only, we might want to
479 // (async) ReportBadMessage here and in MarkEntryAsForeign, and deal
480 // with any resulting crashes, but for now just keep the existing behavior.
481 if (!cache->get()->GetEntry(url))
482 continue;
483 cache->get()->GetEntry(url)->add_types(AppCacheEntry::FOREIGN);
484 }
485
486 storage_->NotifyStorageAccessed(group_record_.origin);
487
488 // TODO(michaeln): Maybe verify that the responses we expect to exist
489 // do actually exist in the disk_cache (and if not then what?)
490 }
491
492 // CacheLoadTask -------
493
494 class AppCacheStorageImpl::CacheLoadTask : public StoreOrLoadTask {
495 public:
CacheLoadTask(int64_t cache_id,AppCacheStorageImpl * storage)496 CacheLoadTask(int64_t cache_id, AppCacheStorageImpl* storage)
497 : StoreOrLoadTask(storage), cache_id_(cache_id), success_(false) {}
498
499 // DatabaseTask:
500 void Run() override;
501 void RunCompleted() override;
502
503 protected:
504 ~CacheLoadTask() override = default;
505
506 private:
507 int64_t cache_id_;
508 bool success_;
509 };
510
Run()511 void AppCacheStorageImpl::CacheLoadTask::Run() {
512 success_ =
513 database_->FindCache(cache_id_, &cache_record_) &&
514 database_->FindGroup(cache_record_.group_id, &group_record_) &&
515 FindRelatedCacheRecords(cache_id_);
516
517 if (success_)
518 database_->LazyUpdateLastAccessTime(group_record_.group_id,
519 base::Time::Now());
520 }
521
RunCompleted()522 void AppCacheStorageImpl::CacheLoadTask::RunCompleted() {
523 storage_->pending_cache_loads_.erase(cache_id_);
524 scoped_refptr<AppCache> cache;
525 scoped_refptr<AppCacheGroup> group;
526 if (success_ && !storage_->is_disabled()) {
527 storage_->LazilyCommitLastAccessTimes();
528 DCHECK(cache_record_.cache_id == cache_id_);
529 CreateCacheAndGroupFromRecords(&cache, &group);
530 }
531 AppCacheStorage::ForEachDelegate(
532 delegates_, [&](AppCacheStorage::Delegate* delegate) {
533 delegate->OnCacheLoaded(cache.get(), cache_id_);
534 });
535 }
536
537 // GroupLoadTask -------
538
539 class AppCacheStorageImpl::GroupLoadTask : public StoreOrLoadTask {
540 public:
GroupLoadTask(GURL manifest_url,AppCacheStorageImpl * storage)541 GroupLoadTask(GURL manifest_url, AppCacheStorageImpl* storage)
542 : StoreOrLoadTask(storage), manifest_url_(manifest_url),
543 success_(false) {}
544
545 // DatabaseTask:
546 void Run() override;
547 void RunCompleted() override;
548
549 protected:
550 ~GroupLoadTask() override = default;
551
552 private:
553 GURL manifest_url_;
554 bool success_;
555 };
556
Run()557 void AppCacheStorageImpl::GroupLoadTask::Run() {
558 success_ =
559 database_->FindGroupForManifestUrl(manifest_url_, &group_record_) &&
560 database_->FindCacheForGroup(group_record_.group_id, &cache_record_) &&
561 FindRelatedCacheRecords(cache_record_.cache_id);
562
563 if (success_) {
564 database_->LazyUpdateLastAccessTime(group_record_.group_id,
565 base::Time::Now());
566 }
567 }
568
RunCompleted()569 void AppCacheStorageImpl::GroupLoadTask::RunCompleted() {
570 storage_->pending_group_loads_.erase(manifest_url_);
571 scoped_refptr<AppCacheGroup> group;
572 scoped_refptr<AppCache> cache;
573 if (!storage_->is_disabled()) {
574 if (success_) {
575 storage_->LazilyCommitLastAccessTimes();
576 DCHECK(group_record_.manifest_url == manifest_url_);
577 CreateCacheAndGroupFromRecords(&cache, &group);
578 } else {
579 group = storage_->working_set_.GetGroup(manifest_url_);
580 if (!group.get()) {
581 group = base::MakeRefCounted<AppCacheGroup>(storage_, manifest_url_,
582 storage_->NewGroupId());
583 }
584 }
585 }
586 AppCacheStorage::ForEachDelegate(
587 delegates_, [&](AppCacheStorage::Delegate* delegate) {
588 delegate->OnGroupLoaded(group.get(), manifest_url_);
589 });
590 }
591
592 // StoreGroupAndCacheTask -------
593
594 class AppCacheStorageImpl::StoreGroupAndCacheTask : public StoreOrLoadTask {
595 public:
596 StoreGroupAndCacheTask(AppCacheStorageImpl* storage, AppCacheGroup* group,
597 AppCache* newest_cache);
598
599 void GetQuotaThenSchedule();
600 void OnQuotaCallback(blink::mojom::QuotaStatusCode status,
601 int64_t usage,
602 int64_t quota);
603
604 // DatabaseTask:
605 void Run() override;
606 void RunCompleted() override;
607 void CancelCompletion() override;
608
609 protected:
610 ~StoreGroupAndCacheTask() override = default;
611
612 private:
613 scoped_refptr<AppCacheGroup> group_;
614 scoped_refptr<AppCache> cache_;
615 bool success_;
616 bool would_exceed_quota_;
617 int64_t space_available_;
618 int64_t new_origin_usage_;
619 std::vector<int64_t> newly_deletable_response_ids_;
620 };
621
StoreGroupAndCacheTask(AppCacheStorageImpl * storage,AppCacheGroup * group,AppCache * newest_cache)622 AppCacheStorageImpl::StoreGroupAndCacheTask::StoreGroupAndCacheTask(
623 AppCacheStorageImpl* storage,
624 AppCacheGroup* group,
625 AppCache* newest_cache)
626 : StoreOrLoadTask(storage),
627 group_(group),
628 cache_(newest_cache),
629 success_(false),
630 would_exceed_quota_(false),
631 space_available_(-1),
632 new_origin_usage_(-1) {
633 group_record_.group_id = group->group_id();
634 group_record_.manifest_url = group->manifest_url();
635 group_record_.origin = url::Origin::Create(group_record_.manifest_url);
636 group_record_.last_full_update_check_time =
637 group->last_full_update_check_time();
638 group_record_.first_evictable_error_time =
639 group->first_evictable_error_time();
640 group_record_.token_expires = group->token_expires();
641 newest_cache->ToDatabaseRecords(
642 group,
643 &cache_record_, &entry_records_,
644 &intercept_namespace_records_,
645 &fallback_namespace_records_,
646 &online_whitelist_records_);
647 }
648
GetQuotaThenSchedule()649 void AppCacheStorageImpl::StoreGroupAndCacheTask::GetQuotaThenSchedule() {
650 if (!storage_->service()->quota_manager_proxy()) {
651 if (storage_->service()->special_storage_policy() &&
652 storage_->service()->special_storage_policy()->IsStorageUnlimited(
653 group_record_.origin.GetURL()))
654 space_available_ = std::numeric_limits<int64_t>::max();
655 Schedule();
656 return;
657 }
658
659 // We have to ask the quota manager for the value.
660 storage_->pending_quota_queries_.insert(this);
661 storage_->service()->quota_manager_proxy()->GetUsageAndQuota(
662 base::ThreadTaskRunnerHandle::Get().get(), group_record_.origin,
663 blink::mojom::StorageType::kTemporary,
664 base::BindOnce(&StoreGroupAndCacheTask::OnQuotaCallback, this));
665 }
666
OnQuotaCallback(blink::mojom::QuotaStatusCode status,int64_t usage,int64_t quota)667 void AppCacheStorageImpl::StoreGroupAndCacheTask::OnQuotaCallback(
668 blink::mojom::QuotaStatusCode status,
669 int64_t usage,
670 int64_t quota) {
671 if (storage_) {
672 if (status == blink::mojom::QuotaStatusCode::kOk)
673 space_available_ = std::max(static_cast<int64_t>(0), quota - usage);
674 else
675 space_available_ = 0;
676 storage_->pending_quota_queries_.erase(this);
677 Schedule();
678 }
679 }
680
Run()681 void AppCacheStorageImpl::StoreGroupAndCacheTask::Run() {
682 DCHECK(!success_);
683 sql::Database* const connection = database_->db_connection();
684 if (!connection)
685 return;
686
687 sql::Transaction transaction(connection);
688 if (!transaction.Begin())
689 return;
690
691 int64_t old_origin_usage = database_->GetOriginUsage(group_record_.origin);
692
693 AppCacheDatabase::GroupRecord existing_group;
694 success_ = database_->FindGroup(group_record_.group_id, &existing_group);
695 if (!success_) {
696 group_record_.creation_time = base::Time::Now();
697 group_record_.last_access_time = base::Time::Now();
698 success_ = database_->InsertGroup(&group_record_);
699 } else {
700 DCHECK(group_record_.group_id == existing_group.group_id);
701 DCHECK(group_record_.manifest_url == existing_group.manifest_url);
702 DCHECK(group_record_.origin == existing_group.origin);
703
704 database_->UpdateLastAccessTime(group_record_.group_id,
705 base::Time::Now());
706
707 database_->UpdateEvictionTimesAndTokenExpires(
708 group_record_.group_id, group_record_.last_full_update_check_time,
709 group_record_.first_evictable_error_time, group_record_.token_expires);
710
711 AppCacheDatabase::CacheRecord cache;
712 if (database_->FindCacheForGroup(group_record_.group_id, &cache)) {
713 // Get the set of response ids in the old cache.
714 std::set<int64_t> existing_response_ids;
715 database_->FindResponseIdsForCacheAsSet(cache.cache_id,
716 &existing_response_ids);
717
718 // Remove those that remain in the new cache.
719 for (const auto& entry : entry_records_)
720 existing_response_ids.erase(entry.response_id);
721
722 // The rest are deletable.
723 for (const auto& id : existing_response_ids)
724 newly_deletable_response_ids_.push_back(id);
725
726 success_ =
727 database_->DeleteCache(cache.cache_id) &&
728 database_->DeleteEntriesForCache(cache.cache_id) &&
729 database_->DeleteNamespacesForCache(cache.cache_id) &&
730 database_->DeleteOnlineWhiteListForCache(cache.cache_id) &&
731 database_->InsertDeletableResponseIds(newly_deletable_response_ids_);
732 // TODO(michaeln): store group_id too with deletable ids
733 } else {
734 NOTREACHED() << "A existing group without a cache is unexpected";
735 }
736 }
737
738 cache_record_.token_expires = group_record_.token_expires;
739
740 success_ =
741 success_ &&
742 database_->InsertCache(&cache_record_) &&
743 database_->InsertEntryRecords(entry_records_) &&
744 database_->InsertNamespaceRecords(intercept_namespace_records_) &&
745 database_->InsertNamespaceRecords(fallback_namespace_records_) &&
746 database_->InsertOnlineWhiteListRecords(online_whitelist_records_);
747
748 if (!success_)
749 return;
750
751 new_origin_usage_ = database_->GetOriginUsage(group_record_.origin);
752
753 // Only check quota when the new usage exceeds the old usage.
754 if (new_origin_usage_ <= old_origin_usage) {
755 success_ = transaction.Commit();
756 return;
757 }
758
759 // Use a simple hard-coded value when not using quota management.
760 if (space_available_ == -1) {
761 if (new_origin_usage_ > kDefaultQuota) {
762 would_exceed_quota_ = true;
763 success_ = false;
764 return;
765 }
766 success_ = transaction.Commit();
767 return;
768 }
769
770 // Check limits based on the space availbable given to us via the
771 // quota system.
772 int64_t delta = new_origin_usage_ - old_origin_usage;
773 if (delta > space_available_) {
774 would_exceed_quota_ = true;
775 success_ = false;
776 return;
777 }
778
779 success_ = transaction.Commit();
780 }
781
RunCompleted()782 void AppCacheStorageImpl::StoreGroupAndCacheTask::RunCompleted() {
783 if (success_) {
784 storage_->UpdateUsageMapAndNotify(
785 url::Origin::Create(group_->manifest_url()), new_origin_usage_);
786 if (cache_.get() != group_->newest_complete_cache()) {
787 cache_->set_complete(true);
788 group_->AddCache(cache_.get());
789 }
790 if (group_->creation_time().is_null())
791 group_->set_creation_time(group_record_.creation_time);
792 group_->AddNewlyDeletableResponseIds(&newly_deletable_response_ids_);
793 }
794 AppCacheStorage::ForEachDelegate(
795 delegates_, [&](AppCacheStorage::Delegate* delegate) {
796 delegate->OnGroupAndNewestCacheStored(group_.get(), cache_.get(),
797 success_, would_exceed_quota_);
798 });
799 group_ = nullptr;
800 cache_ = nullptr;
801
802 // TODO(michaeln): if (would_exceed_quota_) what if the current usage
803 // also exceeds the quota? http://crbug.com/83968
804 }
805
CancelCompletion()806 void AppCacheStorageImpl::StoreGroupAndCacheTask::CancelCompletion() {
807 // Overriden to safely drop our reference to the group and cache
808 // which are not thread safe refcounted.
809 DatabaseTask::CancelCompletion();
810 group_ = nullptr;
811 cache_ = nullptr;
812 }
813
814 // FindMainResponseTask -------
815
816 // Helpers for FindMainResponseTask::Run()
817 namespace {
818 class SortByCachePreference {
819 public:
SortByCachePreference(int64_t preferred_id,const std::set<int64_t> & in_use_ids)820 SortByCachePreference(int64_t preferred_id,
821 const std::set<int64_t>& in_use_ids)
822 : preferred_id_(preferred_id), in_use_ids_(in_use_ids) {}
operator ()(const AppCacheDatabase::EntryRecord & lhs,const AppCacheDatabase::EntryRecord & rhs)823 bool operator()(
824 const AppCacheDatabase::EntryRecord& lhs,
825 const AppCacheDatabase::EntryRecord& rhs) {
826 return compute_value(lhs) > compute_value(rhs);
827 }
828 private:
compute_value(const AppCacheDatabase::EntryRecord & entry)829 int compute_value(const AppCacheDatabase::EntryRecord& entry) {
830 if (entry.cache_id == preferred_id_)
831 return 100;
832 else if (in_use_ids_.find(entry.cache_id) != in_use_ids_.end())
833 return 50;
834 return 0;
835 }
836 int64_t preferred_id_;
837 const std::set<int64_t>& in_use_ids_;
838 };
839
SortByLength(const AppCacheDatabase::NamespaceRecord & lhs,const AppCacheDatabase::NamespaceRecord & rhs)840 bool SortByLength(
841 const AppCacheDatabase::NamespaceRecord& lhs,
842 const AppCacheDatabase::NamespaceRecord& rhs) {
843 return lhs.namespace_.namespace_url.spec().length() >
844 rhs.namespace_.namespace_url.spec().length();
845 }
846
847 class NetworkNamespaceHelper {
848 public:
NetworkNamespaceHelper(AppCacheDatabase * database)849 explicit NetworkNamespaceHelper(AppCacheDatabase* database)
850 : database_(database) {
851 }
852
IsInNetworkNamespace(const GURL & url,int64_t cache_id)853 bool IsInNetworkNamespace(const GURL& url, int64_t cache_id) {
854 std::pair<WhiteListMap::iterator, bool> result = namespaces_map_.insert(
855 WhiteListMap::value_type(cache_id, std::vector<AppCacheNamespace>()));
856 if (result.second)
857 GetOnlineWhiteListForCache(cache_id, &result.first->second);
858 return AppCache::FindNamespace(result.first->second, url) != nullptr;
859 }
860
861 private:
GetOnlineWhiteListForCache(int64_t cache_id,std::vector<AppCacheNamespace> * namespaces)862 void GetOnlineWhiteListForCache(int64_t cache_id,
863 std::vector<AppCacheNamespace>* namespaces) {
864 DCHECK(namespaces && namespaces->empty());
865 using WhiteListVector =
866 std::vector<AppCacheDatabase::OnlineWhiteListRecord>;
867 WhiteListVector records;
868 if (!database_->FindOnlineWhiteListForCache(cache_id, &records))
869 return;
870
871 for (const auto& record : records) {
872 namespaces->push_back(AppCacheNamespace(APPCACHE_NETWORK_NAMESPACE,
873 record.namespace_url, GURL()));
874 }
875 }
876
877 // Key is cache id
878 using WhiteListMap = std::map<int64_t, std::vector<AppCacheNamespace>>;
879 WhiteListMap namespaces_map_;
880 AppCacheDatabase* const database_;
881 };
882
883 } // namespace
884
885 class AppCacheStorageImpl::FindMainResponseTask : public DatabaseTask {
886 public:
FindMainResponseTask(AppCacheStorageImpl * storage,const GURL & url,const GURL & preferred_manifest_url,const AppCacheWorkingSet::GroupMap * groups_in_use)887 FindMainResponseTask(AppCacheStorageImpl* storage,
888 const GURL& url,
889 const GURL& preferred_manifest_url,
890 const AppCacheWorkingSet::GroupMap* groups_in_use)
891 : DatabaseTask(storage),
892 url_(url),
893 preferred_manifest_url_(preferred_manifest_url),
894 cache_id_(blink::mojom::kAppCacheNoCacheId),
895 group_id_(0) {
896 if (groups_in_use) {
897 for (const auto& pair : *groups_in_use) {
898 AppCacheGroup* group = pair.second;
899 AppCache* cache = group->newest_complete_cache();
900 if (group->is_obsolete() || !cache)
901 continue;
902 cache_ids_in_use_.insert(cache->cache_id());
903 }
904 }
905 }
906
907 // DatabaseTask:
908 void Run() override;
909 void RunCompleted() override;
910
911 protected:
912 ~FindMainResponseTask() override = default;
913
914 private:
915 using NamespaceRecordPtrVector =
916 std::vector<AppCacheDatabase::NamespaceRecord*>;
917
918 bool FindExactMatch(int64_t preferred_id);
919 bool FindNamespaceMatch(int64_t preferred_id);
920 bool FindNamespaceHelper(int64_t preferred_cache_id,
921 AppCacheDatabase::NamespaceRecordVector* namespaces,
922 NetworkNamespaceHelper* network_namespace_helper);
923 bool FindFirstValidNamespace(const NamespaceRecordPtrVector& namespaces);
924
925 GURL url_;
926 GURL preferred_manifest_url_;
927 std::set<int64_t> cache_ids_in_use_;
928 AppCacheEntry entry_;
929 AppCacheEntry fallback_entry_;
930 GURL namespace_entry_url_;
931 int64_t cache_id_;
932 int64_t group_id_;
933 GURL manifest_url_;
934 };
935
Run()936 void AppCacheStorageImpl::FindMainResponseTask::Run() {
937 // NOTE: The heuristics around choosing amoungst multiple candidates
938 // is underspecified, and just plain not fully understood. This needs
939 // to be refined.
940
941 // The 'preferred_manifest_url' is the url of the manifest associated
942 // with the page that opened or embedded the page being loaded now.
943 // We have a strong preference to use resources from that cache.
944 // We also have a lesser bias to use resources from caches that are currently
945 // being used by other unrelated pages.
946 // TODO(michaeln): come up with a 'preferred_manifest_url' in more cases
947 // - when navigating a frame whose current contents are from an appcache
948 // - when clicking an href in a frame that is appcached
949 int64_t preferred_cache_id = blink::mojom::kAppCacheNoCacheId;
950 if (!preferred_manifest_url_.is_empty()) {
951 AppCacheDatabase::GroupRecord preferred_group;
952 AppCacheDatabase::CacheRecord preferred_cache;
953 if (database_->FindGroupForManifestUrl(
954 preferred_manifest_url_, &preferred_group) &&
955 database_->FindCacheForGroup(
956 preferred_group.group_id, &preferred_cache)) {
957 preferred_cache_id = preferred_cache.cache_id;
958 }
959 }
960
961 if (FindExactMatch(preferred_cache_id) ||
962 FindNamespaceMatch(preferred_cache_id)) {
963 // We found something.
964 DCHECK(cache_id_ != blink::mojom::kAppCacheNoCacheId &&
965 !manifest_url_.is_empty() && group_id_ != 0);
966 return;
967 }
968
969 // We didn't find anything.
970 DCHECK(cache_id_ == blink::mojom::kAppCacheNoCacheId &&
971 manifest_url_.is_empty() && group_id_ == 0);
972 }
973
FindExactMatch(int64_t preferred_cache_id)974 bool AppCacheStorageImpl::FindMainResponseTask::FindExactMatch(
975 int64_t preferred_cache_id) {
976 std::vector<AppCacheDatabase::EntryRecord> entries;
977 if (database_->FindEntriesForUrl(url_, &entries) && !entries.empty()) {
978 // Sort them in order of preference, from the preferred_cache first,
979 // followed by hits from caches that are 'in use', then the rest.
980 std::sort(entries.begin(), entries.end(),
981 SortByCachePreference(preferred_cache_id, cache_ids_in_use_));
982
983 // Take the first with a valid, non-foreign entry.
984 for (const auto& entry : entries) {
985 AppCacheDatabase::GroupRecord group_record;
986 if ((entry.flags & AppCacheEntry::FOREIGN) ||
987 !database_->FindGroupForCache(entry.cache_id, &group_record)) {
988 continue;
989 }
990 manifest_url_ = group_record.manifest_url;
991 group_id_ = group_record.group_id;
992 entry_ = AppCacheEntry(entry.flags, entry.response_id);
993 cache_id_ = entry.cache_id;
994 return true; // We found an exact match.
995 }
996 }
997 return false;
998 }
999
FindNamespaceMatch(int64_t preferred_cache_id)1000 bool AppCacheStorageImpl::FindMainResponseTask::FindNamespaceMatch(
1001 int64_t preferred_cache_id) {
1002 AppCacheDatabase::NamespaceRecordVector all_intercepts;
1003 AppCacheDatabase::NamespaceRecordVector all_fallbacks;
1004 if (!database_->FindNamespacesForOrigin(url::Origin::Create(url_),
1005 &all_intercepts, &all_fallbacks) ||
1006 (all_intercepts.empty() && all_fallbacks.empty())) {
1007 return false;
1008 }
1009
1010 NetworkNamespaceHelper network_namespace_helper(database_);
1011 if (FindNamespaceHelper(preferred_cache_id,
1012 &all_intercepts,
1013 &network_namespace_helper) ||
1014 FindNamespaceHelper(preferred_cache_id,
1015 &all_fallbacks,
1016 &network_namespace_helper)) {
1017 return true;
1018 }
1019 return false;
1020 }
1021
FindNamespaceHelper(int64_t preferred_cache_id,AppCacheDatabase::NamespaceRecordVector * namespaces,NetworkNamespaceHelper * network_namespace_helper)1022 bool AppCacheStorageImpl::FindMainResponseTask::FindNamespaceHelper(
1023 int64_t preferred_cache_id,
1024 AppCacheDatabase::NamespaceRecordVector* namespaces,
1025 NetworkNamespaceHelper* network_namespace_helper) {
1026 // Sort them by length, longer matches within the same cache/bucket take
1027 // precedence.
1028 std::sort(namespaces->begin(), namespaces->end(), SortByLength);
1029
1030 NamespaceRecordPtrVector preferred_namespaces;
1031 NamespaceRecordPtrVector inuse_namespaces;
1032 NamespaceRecordPtrVector other_namespaces;
1033 for (auto& namespace_record : *namespaces) {
1034 // Skip those that aren't a match.
1035 if (!namespace_record.namespace_.IsMatch(url_))
1036 continue;
1037
1038 // Skip namespaces where the requested url falls into a network
1039 // namespace of its containing appcache.
1040 if (network_namespace_helper->IsInNetworkNamespace(
1041 url_, namespace_record.cache_id))
1042 continue;
1043
1044 // Bin them into one of our three buckets.
1045 if (namespace_record.cache_id == preferred_cache_id)
1046 preferred_namespaces.push_back(&namespace_record);
1047 else if (cache_ids_in_use_.find(namespace_record.cache_id) !=
1048 cache_ids_in_use_.end())
1049 inuse_namespaces.push_back(&namespace_record);
1050 else
1051 other_namespaces.push_back(&namespace_record);
1052 }
1053
1054 if (FindFirstValidNamespace(preferred_namespaces) ||
1055 FindFirstValidNamespace(inuse_namespaces) ||
1056 FindFirstValidNamespace(other_namespaces))
1057 return true; // We found one.
1058
1059 // We didn't find anything.
1060 return false;
1061 }
1062
1063 bool AppCacheStorageImpl::
FindFirstValidNamespace(const NamespaceRecordPtrVector & namespaces)1064 FindMainResponseTask::FindFirstValidNamespace(
1065 const NamespaceRecordPtrVector& namespaces) {
1066 // Take the first with a valid, non-foreign entry.
1067 for (auto* namespace_record : namespaces) {
1068 AppCacheDatabase::EntryRecord entry_record;
1069 if (database_->FindEntry(namespace_record->cache_id,
1070 namespace_record->namespace_.target_url,
1071 &entry_record)) {
1072 AppCacheDatabase::GroupRecord group_record;
1073 if ((entry_record.flags & AppCacheEntry::FOREIGN) ||
1074 !database_->FindGroupForCache(entry_record.cache_id, &group_record)) {
1075 continue;
1076 }
1077 manifest_url_ = group_record.manifest_url;
1078 group_id_ = group_record.group_id;
1079 cache_id_ = namespace_record->cache_id;
1080 namespace_entry_url_ = namespace_record->namespace_.target_url;
1081 if (namespace_record->namespace_.type == APPCACHE_FALLBACK_NAMESPACE)
1082 fallback_entry_ = AppCacheEntry(entry_record.flags,
1083 entry_record.response_id);
1084 else
1085 entry_ = AppCacheEntry(entry_record.flags, entry_record.response_id);
1086 return true; // We found one.
1087 }
1088 }
1089 return false; // We didn't find a match.
1090 }
1091
RunCompleted()1092 void AppCacheStorageImpl::FindMainResponseTask::RunCompleted() {
1093 storage_->CallOnMainResponseFound(
1094 &delegates_, url_, entry_, namespace_entry_url_, fallback_entry_,
1095 cache_id_, group_id_, manifest_url_);
1096 }
1097
1098 // MarkEntryAsForeignTask -------
1099
1100 class AppCacheStorageImpl::MarkEntryAsForeignTask : public DatabaseTask {
1101 public:
MarkEntryAsForeignTask(AppCacheStorageImpl * storage,const GURL & url,int64_t cache_id)1102 MarkEntryAsForeignTask(AppCacheStorageImpl* storage,
1103 const GURL& url,
1104 int64_t cache_id)
1105 : DatabaseTask(storage), cache_id_(cache_id), entry_url_(url) {}
1106
1107 // DatabaseTask:
1108 void Run() override;
1109 void RunCompleted() override;
1110
1111 protected:
1112 ~MarkEntryAsForeignTask() override = default;
1113
1114 private:
1115 int64_t cache_id_;
1116 GURL entry_url_;
1117 };
1118
Run()1119 void AppCacheStorageImpl::MarkEntryAsForeignTask::Run() {
1120 database_->AddEntryFlags(entry_url_, cache_id_, AppCacheEntry::FOREIGN);
1121 }
1122
RunCompleted()1123 void AppCacheStorageImpl::MarkEntryAsForeignTask::RunCompleted() {
1124 DCHECK(storage_->pending_foreign_markings_.front().first == entry_url_ &&
1125 storage_->pending_foreign_markings_.front().second == cache_id_);
1126 storage_->pending_foreign_markings_.pop_front();
1127 }
1128
1129 // MakeGroupObsoleteTask -------
1130
1131 class AppCacheStorageImpl::MakeGroupObsoleteTask : public DatabaseTask {
1132 public:
1133 MakeGroupObsoleteTask(AppCacheStorageImpl* storage,
1134 AppCacheGroup* group,
1135 int response_code);
1136
1137 // DatabaseTask:
1138 void Run() override;
1139 void RunCompleted() override;
1140 void CancelCompletion() override;
1141
1142 protected:
1143 ~MakeGroupObsoleteTask() override = default;
1144
1145 private:
1146 scoped_refptr<AppCacheGroup> group_;
1147 int64_t group_id_;
1148 url::Origin origin_;
1149 bool success_;
1150 int response_code_;
1151 int64_t new_origin_usage_;
1152 std::vector<int64_t> newly_deletable_response_ids_;
1153 };
1154
MakeGroupObsoleteTask(AppCacheStorageImpl * storage,AppCacheGroup * group,int response_code)1155 AppCacheStorageImpl::MakeGroupObsoleteTask::MakeGroupObsoleteTask(
1156 AppCacheStorageImpl* storage,
1157 AppCacheGroup* group,
1158 int response_code)
1159 : DatabaseTask(storage),
1160 group_(group),
1161 group_id_(group->group_id()),
1162 origin_(url::Origin::Create(group->manifest_url())),
1163 success_(false),
1164 response_code_(response_code),
1165 new_origin_usage_(-1) {}
1166
Run()1167 void AppCacheStorageImpl::MakeGroupObsoleteTask::Run() {
1168 DCHECK(!success_);
1169 sql::Database* connection = database_->db_connection();
1170 if (!connection)
1171 return;
1172
1173 sql::Transaction transaction(connection);
1174 if (!transaction.Begin())
1175 return;
1176
1177 AppCacheDatabase::GroupRecord group_record;
1178 if (!database_->FindGroup(group_id_, &group_record)) {
1179 // This group doesn't exists in the database, nothing todo here.
1180 new_origin_usage_ = database_->GetOriginUsage(origin_);
1181 success_ = true;
1182 return;
1183 }
1184
1185 DCHECK_EQ(group_record.origin, origin_);
1186 success_ = DeleteGroupAndRelatedRecords(database_,
1187 group_id_,
1188 &newly_deletable_response_ids_);
1189
1190 new_origin_usage_ = database_->GetOriginUsage(origin_);
1191 success_ = success_ && transaction.Commit();
1192 }
1193
RunCompleted()1194 void AppCacheStorageImpl::MakeGroupObsoleteTask::RunCompleted() {
1195 if (success_) {
1196 group_->set_obsolete(true);
1197 if (!storage_->is_disabled()) {
1198 storage_->UpdateUsageMapAndNotify(origin_, new_origin_usage_);
1199 group_->AddNewlyDeletableResponseIds(&newly_deletable_response_ids_);
1200
1201 // Also remove from the working set, caches for an 'obsolete' group
1202 // may linger in use, but the group itself cannot be looked up by
1203 // 'manifest_url' in the working set any longer.
1204 storage_->working_set()->RemoveGroup(group_.get());
1205 }
1206 }
1207
1208 AppCacheStorage::ForEachDelegate(
1209 delegates_, [&](AppCacheStorage::Delegate* delegate) {
1210 delegate->OnGroupMadeObsolete(group_.get(), success_, response_code_);
1211 });
1212 group_ = nullptr;
1213 }
1214
CancelCompletion()1215 void AppCacheStorageImpl::MakeGroupObsoleteTask::CancelCompletion() {
1216 // Overriden to safely drop our reference to the group
1217 // which is not thread safe refcounted.
1218 DatabaseTask::CancelCompletion();
1219 group_ = nullptr;
1220 }
1221
1222 // GetDeletableResponseIdsTask -------
1223
1224 class AppCacheStorageImpl::GetDeletableResponseIdsTask : public DatabaseTask {
1225 public:
GetDeletableResponseIdsTask(AppCacheStorageImpl * storage,int64_t max_rowid)1226 GetDeletableResponseIdsTask(AppCacheStorageImpl* storage, int64_t max_rowid)
1227 : DatabaseTask(storage), max_rowid_(max_rowid) {}
1228
1229 // DatabaseTask:
1230 void Run() override;
1231 void RunCompleted() override;
1232
1233 protected:
1234 ~GetDeletableResponseIdsTask() override = default;
1235
1236 private:
1237 int64_t max_rowid_;
1238 std::vector<int64_t> response_ids_;
1239 };
1240
Run()1241 void AppCacheStorageImpl::GetDeletableResponseIdsTask::Run() {
1242 const int kSqlLimit = 1000;
1243 database_->GetDeletableResponseIds(&response_ids_, max_rowid_, kSqlLimit);
1244 // TODO(michaeln): retrieve group_ids too
1245 }
1246
RunCompleted()1247 void AppCacheStorageImpl::GetDeletableResponseIdsTask::RunCompleted() {
1248 if (!response_ids_.empty())
1249 storage_->StartDeletingResponses(response_ids_);
1250 }
1251
1252 // InsertDeletableResponseIdsTask -------
1253
1254 class AppCacheStorageImpl::InsertDeletableResponseIdsTask
1255 : public DatabaseTask {
1256 public:
InsertDeletableResponseIdsTask(AppCacheStorageImpl * storage)1257 explicit InsertDeletableResponseIdsTask(AppCacheStorageImpl* storage)
1258 : DatabaseTask(storage) {}
1259
1260 // DatabaseTask:
1261 void Run() override;
1262
1263 std::vector<int64_t> response_ids_;
1264
1265 protected:
1266 ~InsertDeletableResponseIdsTask() override = default;
1267 };
1268
Run()1269 void AppCacheStorageImpl::InsertDeletableResponseIdsTask::Run() {
1270 database_->InsertDeletableResponseIds(response_ids_);
1271 // TODO(michaeln): store group_ids too
1272 }
1273
1274 // DeleteDeletableResponseIdsTask -------
1275
1276 class AppCacheStorageImpl::DeleteDeletableResponseIdsTask
1277 : public DatabaseTask {
1278 public:
DeleteDeletableResponseIdsTask(AppCacheStorageImpl * storage)1279 explicit DeleteDeletableResponseIdsTask(AppCacheStorageImpl* storage)
1280 : DatabaseTask(storage) {}
1281
1282 // DatabaseTask:
1283 void Run() override;
1284
1285 std::vector<int64_t> response_ids_;
1286
1287 protected:
1288 ~DeleteDeletableResponseIdsTask() override = default;
1289 };
1290
Run()1291 void AppCacheStorageImpl::DeleteDeletableResponseIdsTask::Run() {
1292 database_->DeleteDeletableResponseIds(response_ids_);
1293 }
1294
1295 // LazyUpdateLastAccessTimeTask -------
1296
1297 class AppCacheStorageImpl::LazyUpdateLastAccessTimeTask
1298 : public DatabaseTask {
1299 public:
LazyUpdateLastAccessTimeTask(AppCacheStorageImpl * storage,AppCacheGroup * group,base::Time time)1300 LazyUpdateLastAccessTimeTask(
1301 AppCacheStorageImpl* storage, AppCacheGroup* group, base::Time time)
1302 : DatabaseTask(storage), group_id_(group->group_id()),
1303 last_access_time_(time) {
1304 storage->NotifyStorageAccessed(url::Origin::Create(group->manifest_url()));
1305 }
1306
1307 // DatabaseTask:
1308 void Run() override;
1309 void RunCompleted() override;
1310
1311 protected:
1312 ~LazyUpdateLastAccessTimeTask() override = default;
1313
1314 private:
1315 int64_t group_id_;
1316 base::Time last_access_time_;
1317 };
1318
Run()1319 void AppCacheStorageImpl::LazyUpdateLastAccessTimeTask::Run() {
1320 database_->LazyUpdateLastAccessTime(group_id_, last_access_time_);
1321 }
1322
RunCompleted()1323 void AppCacheStorageImpl::LazyUpdateLastAccessTimeTask::RunCompleted() {
1324 storage_->LazilyCommitLastAccessTimes();
1325 }
1326
1327 // CommitLastAccessTimesTask -------
1328
1329 class AppCacheStorageImpl::CommitLastAccessTimesTask
1330 : public DatabaseTask {
1331 public:
CommitLastAccessTimesTask(AppCacheStorageImpl * storage)1332 explicit CommitLastAccessTimesTask(AppCacheStorageImpl* storage)
1333 : DatabaseTask(storage) {}
1334
1335 // DatabaseTask:
Run()1336 void Run() override {
1337 database_->CommitLazyLastAccessTimes();
1338 }
1339
1340 protected:
1341 ~CommitLastAccessTimesTask() override = default;
1342 };
1343
1344 // UpdateEvictionTimes -------
1345
1346 class AppCacheStorageImpl::UpdateEvictionTimesTask
1347 : public DatabaseTask {
1348 public:
UpdateEvictionTimesTask(AppCacheStorageImpl * storage,AppCacheGroup * group)1349 UpdateEvictionTimesTask(AppCacheStorageImpl* storage, AppCacheGroup* group)
1350 : DatabaseTask(storage),
1351 group_id_(group->group_id()),
1352 last_full_update_check_time_(group->last_full_update_check_time()),
1353 first_evictable_error_time_(group->first_evictable_error_time()),
1354 token_expires_(group->token_expires()) {}
1355
1356 // DatabaseTask:
1357 void Run() override;
1358
1359 protected:
1360 ~UpdateEvictionTimesTask() override = default;
1361
1362 private:
1363 int64_t group_id_;
1364 base::Time last_full_update_check_time_;
1365 base::Time first_evictable_error_time_;
1366 base::Time token_expires_;
1367 };
1368
Run()1369 void AppCacheStorageImpl::UpdateEvictionTimesTask::Run() {
1370 database_->UpdateEvictionTimesAndTokenExpires(
1371 group_id_, last_full_update_check_time_, first_evictable_error_time_,
1372 token_expires_);
1373 }
1374
1375 // AppCacheStorageImpl ---------------------------------------------------
1376
AppCacheStorageImpl(AppCacheServiceImpl * service)1377 AppCacheStorageImpl::AppCacheStorageImpl(AppCacheServiceImpl* service)
1378 : AppCacheStorage(service),
1379 is_incognito_(false),
1380 is_response_deletion_scheduled_(false),
1381 did_start_deleting_responses_(false),
1382 last_deletable_response_rowid_(0),
1383 database_(nullptr),
1384 is_disabled_(false),
1385 delete_and_start_over_pending_(false),
1386 expecting_cleanup_complete_on_disable_(false) {}
1387
~AppCacheStorageImpl()1388 AppCacheStorageImpl::~AppCacheStorageImpl() {
1389 for (StoreGroupAndCacheTask* task : pending_quota_queries_)
1390 task->CancelCompletion();
1391 for (DatabaseTask* task : scheduled_database_tasks_)
1392 task->CancelCompletion();
1393
1394 if (database_ &&
1395 !db_task_runner_->PostTask(
1396 FROM_HERE,
1397 base::BindOnce(
1398 &ClearSessionOnlyOrigins, std::move(database_),
1399 base::WrapRefCounted(service_->special_storage_policy()),
1400 service()->force_keep_session_state()))) {
1401 }
1402 }
1403
Initialize(const base::FilePath & cache_directory,const scoped_refptr<base::SequencedTaskRunner> & db_task_runner)1404 void AppCacheStorageImpl::Initialize(
1405 const base::FilePath& cache_directory,
1406 const scoped_refptr<base::SequencedTaskRunner>& db_task_runner) {
1407 cache_directory_ = cache_directory;
1408 is_incognito_ = cache_directory_.empty();
1409
1410 base::FilePath db_file_path;
1411 if (!is_incognito_)
1412 db_file_path = cache_directory_.Append(kAppCacheDatabaseName);
1413 database_ = std::make_unique<AppCacheDatabase>(db_file_path);
1414
1415 db_task_runner_ = db_task_runner;
1416
1417 auto task = base::MakeRefCounted<InitTask>(this);
1418 task->Schedule();
1419 }
1420
Disable()1421 void AppCacheStorageImpl::Disable() {
1422 if (is_disabled_)
1423 return;
1424 VLOG(1) << "Disabling appcache storage.";
1425 is_disabled_ = true;
1426 ClearUsageMapAndNotify();
1427 working_set()->Disable();
1428 if (disk_cache_)
1429 disk_cache_->Disable();
1430 auto task = base::MakeRefCounted<DisableDatabaseTask>(this);
1431 task->Schedule();
1432 }
1433
GetAllInfo(Delegate * delegate)1434 void AppCacheStorageImpl::GetAllInfo(Delegate* delegate) {
1435 DCHECK(delegate);
1436 auto task = base::MakeRefCounted<GetAllInfoTask>(this);
1437 task->AddDelegate(GetOrCreateDelegateReference(delegate));
1438 task->Schedule();
1439 }
1440
LoadCache(int64_t id,Delegate * delegate)1441 void AppCacheStorageImpl::LoadCache(int64_t id, Delegate* delegate) {
1442 DCHECK(delegate);
1443 if (is_disabled_) {
1444 delegate->OnCacheLoaded(nullptr, id);
1445 return;
1446 }
1447
1448 AppCache* cache = working_set_.GetCache(id);
1449 if (cache) {
1450 delegate->OnCacheLoaded(cache, id);
1451 if (cache->owning_group()) {
1452 auto update_task = base::MakeRefCounted<LazyUpdateLastAccessTimeTask>(
1453 this, cache->owning_group(), base::Time::Now());
1454 update_task->Schedule();
1455 }
1456 return;
1457 }
1458 scoped_refptr<CacheLoadTask> task(GetPendingCacheLoadTask(id));
1459 if (task.get()) {
1460 task->AddDelegate(GetOrCreateDelegateReference(delegate));
1461 return;
1462 }
1463 task = base::MakeRefCounted<CacheLoadTask>(id, this);
1464 task->AddDelegate(GetOrCreateDelegateReference(delegate));
1465 task->Schedule();
1466 pending_cache_loads_[id] = task.get();
1467 }
1468
LoadOrCreateGroup(const GURL & manifest_url,Delegate * delegate)1469 void AppCacheStorageImpl::LoadOrCreateGroup(
1470 const GURL& manifest_url, Delegate* delegate) {
1471 DCHECK(delegate);
1472 if (is_disabled_) {
1473 delegate->OnGroupLoaded(nullptr, manifest_url);
1474 return;
1475 }
1476
1477 AppCacheGroup* group = working_set_.GetGroup(manifest_url);
1478 if (group) {
1479 delegate->OnGroupLoaded(group, manifest_url);
1480 auto update_task = base::MakeRefCounted<LazyUpdateLastAccessTimeTask>(
1481 this, group, base::Time::Now());
1482 update_task->Schedule();
1483 return;
1484 }
1485
1486 scoped_refptr<GroupLoadTask> task(GetPendingGroupLoadTask(manifest_url));
1487 if (task.get()) {
1488 task->AddDelegate(GetOrCreateDelegateReference(delegate));
1489 return;
1490 }
1491
1492 if (usage_map_.find(url::Origin::Create(manifest_url)) == usage_map_.end()) {
1493 // No need to query the database, return a new group immediately.
1494 auto new_group =
1495 base::MakeRefCounted<AppCacheGroup>(this, manifest_url, NewGroupId());
1496 delegate->OnGroupLoaded(new_group.get(), manifest_url);
1497 return;
1498 }
1499
1500 task = base::MakeRefCounted<GroupLoadTask>(manifest_url, this);
1501 task->AddDelegate(GetOrCreateDelegateReference(delegate));
1502 task->Schedule();
1503 pending_group_loads_[manifest_url] = task.get();
1504 }
1505
StoreGroupAndNewestCache(AppCacheGroup * group,AppCache * newest_cache,Delegate * delegate)1506 void AppCacheStorageImpl::StoreGroupAndNewestCache(
1507 AppCacheGroup* group, AppCache* newest_cache, Delegate* delegate) {
1508 // TODO(michaeln): distinguish between a simple update of an existing
1509 // cache that just adds new master entry(s), and the insertion of a
1510 // whole new cache. The StoreGroupAndCacheTask as written will handle
1511 // the simple update case in a very heavy weight way (delete all and
1512 // the reinsert all over again).
1513 DCHECK(group && delegate && newest_cache);
1514 auto task =
1515 base::MakeRefCounted<StoreGroupAndCacheTask>(this, group, newest_cache);
1516 task->AddDelegate(GetOrCreateDelegateReference(delegate));
1517 task->GetQuotaThenSchedule();
1518 }
1519
FindResponseForMainRequest(const GURL & url,const GURL & preferred_manifest_url,Delegate * delegate)1520 void AppCacheStorageImpl::FindResponseForMainRequest(
1521 const GURL& url, const GURL& preferred_manifest_url,
1522 Delegate* delegate) {
1523 DCHECK(delegate);
1524
1525 const GURL* url_ptr = &url;
1526 GURL url_no_ref;
1527 if (url.has_ref()) {
1528 GURL::Replacements replacements;
1529 replacements.ClearRef();
1530 url_no_ref = url.ReplaceComponents(replacements);
1531 url_ptr = &url_no_ref;
1532 }
1533
1534 const url::Origin origin(url::Origin::Create(url));
1535
1536 // First look in our working set for a direct hit without having to query
1537 // the database.
1538 const AppCacheWorkingSet::GroupMap* groups_in_use =
1539 working_set()->GetGroupsInOrigin(origin);
1540 if (groups_in_use) {
1541 if (!preferred_manifest_url.is_empty()) {
1542 auto found = groups_in_use->find(preferred_manifest_url);
1543 if (found != groups_in_use->end() &&
1544 FindResponseForMainRequestInGroup(
1545 found->second, *url_ptr, delegate)) {
1546 return;
1547 }
1548 } else {
1549 for (const auto& pair : *groups_in_use) {
1550 if (FindResponseForMainRequestInGroup(pair.second, *url_ptr,
1551 delegate)) {
1552 return;
1553 }
1554 }
1555 }
1556 }
1557
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(base::BindOnce(
1564 &AppCacheStorageImpl::DeliverShortCircuitedFindMainResponse,
1565 weak_factory_.GetWeakPtr(), url, AppCacheEntry(), no_group, no_cache,
1566 base::WrapRefCounted(GetOrCreateDelegateReference(delegate))));
1567 return;
1568 }
1569
1570 // We have to query the database, schedule a database task to do so.
1571 auto task = base::MakeRefCounted<FindMainResponseTask>(
1572 this, *url_ptr, preferred_manifest_url, groups_in_use);
1573 task->AddDelegate(GetOrCreateDelegateReference(delegate));
1574 task->Schedule();
1575 }
1576
FindResponseForMainRequestInGroup(AppCacheGroup * group,const GURL & url,Delegate * delegate)1577 bool AppCacheStorageImpl::FindResponseForMainRequestInGroup(
1578 AppCacheGroup* group, const GURL& url, Delegate* delegate) {
1579 AppCache* cache = group->newest_complete_cache();
1580 if (group->is_obsolete() || !cache)
1581 return false;
1582
1583 AppCacheEntry* entry = cache->GetEntry(url);
1584 if (!entry || entry->IsForeign())
1585 return false;
1586
1587 ScheduleSimpleTask(base::BindOnce(
1588 &AppCacheStorageImpl::DeliverShortCircuitedFindMainResponse,
1589 weak_factory_.GetWeakPtr(), url, *entry, base::WrapRefCounted(group),
1590 base::WrapRefCounted(cache),
1591 base::WrapRefCounted(GetOrCreateDelegateReference(delegate))));
1592 return true;
1593 }
1594
DeliverShortCircuitedFindMainResponse(const GURL & url,const AppCacheEntry & found_entry,scoped_refptr<AppCacheGroup> group,scoped_refptr<AppCache> cache,scoped_refptr<DelegateReference> delegate_ref)1595 void AppCacheStorageImpl::DeliverShortCircuitedFindMainResponse(
1596 const GURL& url,
1597 const AppCacheEntry& found_entry,
1598 scoped_refptr<AppCacheGroup> group,
1599 scoped_refptr<AppCache> cache,
1600 scoped_refptr<DelegateReference> delegate_ref) {
1601 if (delegate_ref->delegate) {
1602 std::vector<scoped_refptr<DelegateReference>> delegates(1, delegate_ref);
1603 CallOnMainResponseFound(
1604 &delegates, url, found_entry, GURL(), AppCacheEntry(),
1605 cache.get() ? cache->cache_id() : blink::mojom::kAppCacheNoCacheId,
1606 group.get() ? group->group_id() : blink::mojom::kAppCacheNoCacheId,
1607 group.get() ? group->manifest_url() : GURL());
1608 }
1609 }
1610
CallOnMainResponseFound(std::vector<scoped_refptr<DelegateReference>> * delegates,const GURL & url,const AppCacheEntry & entry,const GURL & namespace_entry_url,const AppCacheEntry & fallback_entry,int64_t cache_id,int64_t group_id,const GURL & manifest_url)1611 void AppCacheStorageImpl::CallOnMainResponseFound(
1612 std::vector<scoped_refptr<DelegateReference>>* delegates,
1613 const GURL& url,
1614 const AppCacheEntry& entry,
1615 const GURL& namespace_entry_url,
1616 const AppCacheEntry& fallback_entry,
1617 int64_t cache_id,
1618 int64_t group_id,
1619 const GURL& manifest_url) {
1620 AppCacheStorage::ForEachDelegate(
1621 *delegates, [&](AppCacheStorage::Delegate* delegate) {
1622 delegate->OnMainResponseFound(url, entry, namespace_entry_url,
1623 fallback_entry, cache_id, group_id,
1624 manifest_url);
1625 });
1626 }
1627
FindResponseForSubRequest(AppCache * cache,const GURL & url,AppCacheEntry * found_entry,AppCacheEntry * found_fallback_entry,bool * found_network_namespace)1628 void AppCacheStorageImpl::FindResponseForSubRequest(
1629 AppCache* cache, const GURL& url,
1630 AppCacheEntry* found_entry, AppCacheEntry* found_fallback_entry,
1631 bool* found_network_namespace) {
1632 DCHECK(cache && cache->is_complete());
1633
1634 // When a group is forcibly deleted, all subresource loads for pages
1635 // using caches in the group will result in a synthesized network errors.
1636 // Forcible deletion is not a function that is covered by the HTML5 spec.
1637 if (cache->owning_group()->is_being_deleted()) {
1638 *found_entry = AppCacheEntry();
1639 *found_fallback_entry = AppCacheEntry();
1640 *found_network_namespace = false;
1641 return;
1642 }
1643
1644 GURL fallback_namespace_not_used;
1645 GURL intercept_namespace_not_used;
1646 cache->FindResponseForRequest(
1647 url, found_entry, &intercept_namespace_not_used,
1648 found_fallback_entry, &fallback_namespace_not_used,
1649 found_network_namespace);
1650 }
1651
MarkEntryAsForeign(const GURL & entry_url,int64_t cache_id)1652 void AppCacheStorageImpl::MarkEntryAsForeign(const GURL& entry_url,
1653 int64_t cache_id) {
1654 AppCache* cache = working_set_.GetCache(cache_id);
1655 if (cache) {
1656 AppCacheEntry* entry = cache->GetEntry(entry_url);
1657 if (entry)
1658 entry->add_types(AppCacheEntry::FOREIGN);
1659 }
1660 auto task =
1661 base::MakeRefCounted<MarkEntryAsForeignTask>(this, entry_url, cache_id);
1662 task->Schedule();
1663 pending_foreign_markings_.push_back(std::make_pair(entry_url, cache_id));
1664 }
1665
MakeGroupObsolete(AppCacheGroup * group,Delegate * delegate,int response_code)1666 void AppCacheStorageImpl::MakeGroupObsolete(AppCacheGroup* group,
1667 Delegate* delegate,
1668 int response_code) {
1669 DCHECK(group && delegate);
1670 auto task =
1671 base::MakeRefCounted<MakeGroupObsoleteTask>(this, group, response_code);
1672 task->AddDelegate(GetOrCreateDelegateReference(delegate));
1673 task->Schedule();
1674 }
1675
StoreEvictionTimes(AppCacheGroup * group)1676 void AppCacheStorageImpl::StoreEvictionTimes(AppCacheGroup* group) {
1677 auto task = base::MakeRefCounted<UpdateEvictionTimesTask>(this, group);
1678 task->Schedule();
1679 }
1680
1681 std::unique_ptr<AppCacheResponseReader>
CreateResponseReader(const GURL & manifest_url,int64_t response_id)1682 AppCacheStorageImpl::CreateResponseReader(const GURL& manifest_url,
1683 int64_t response_id) {
1684 return std::make_unique<AppCacheResponseReader>(
1685 response_id, is_disabled_ ? nullptr : disk_cache()->GetWeakPtr());
1686 }
1687
1688 std::unique_ptr<AppCacheResponseWriter>
CreateResponseWriter(const GURL & manifest_url)1689 AppCacheStorageImpl::CreateResponseWriter(const GURL& manifest_url) {
1690 return std::make_unique<AppCacheResponseWriter>(
1691 NewResponseId(), is_disabled_ ? nullptr : disk_cache()->GetWeakPtr());
1692 }
1693
1694 std::unique_ptr<AppCacheResponseMetadataWriter>
CreateResponseMetadataWriter(int64_t response_id)1695 AppCacheStorageImpl::CreateResponseMetadataWriter(int64_t response_id) {
1696 return std::make_unique<AppCacheResponseMetadataWriter>(
1697 response_id, is_disabled_ ? nullptr : disk_cache()->GetWeakPtr());
1698 }
1699
DoomResponses(const GURL & manifest_url,const std::vector<int64_t> & response_ids)1700 void AppCacheStorageImpl::DoomResponses(
1701 const GURL& manifest_url,
1702 const std::vector<int64_t>& response_ids) {
1703 if (response_ids.empty())
1704 return;
1705
1706 // Start deleting them from the disk cache lazily.
1707 StartDeletingResponses(response_ids);
1708
1709 // Also schedule a database task to record these ids in the
1710 // deletable responses table.
1711 // TODO(michaeln): There is a race here. If the browser crashes
1712 // prior to committing these rows to the database and prior to us
1713 // having deleted them from the disk cache, we'll never delete them.
1714 auto task = base::MakeRefCounted<InsertDeletableResponseIdsTask>(this);
1715 task->response_ids_ = response_ids;
1716 task->Schedule();
1717 }
1718
DeleteResponses(const GURL & manifest_url,const std::vector<int64_t> & response_ids)1719 void AppCacheStorageImpl::DeleteResponses(
1720 const GURL& manifest_url,
1721 const std::vector<int64_t>& response_ids) {
1722 if (response_ids.empty())
1723 return;
1724 StartDeletingResponses(response_ids);
1725 }
1726
IsInitialized()1727 bool AppCacheStorageImpl::IsInitialized() {
1728 return IsInitTaskComplete();
1729 }
1730
DelayedStartDeletingUnusedResponses()1731 void AppCacheStorageImpl::DelayedStartDeletingUnusedResponses() {
1732 // Only if we haven't already begun.
1733 if (!did_start_deleting_responses_) {
1734 auto task = base::MakeRefCounted<GetDeletableResponseIdsTask>(
1735 this, last_deletable_response_rowid_);
1736 task->Schedule();
1737 }
1738 }
1739
StartDeletingResponses(const std::vector<int64_t> & response_ids)1740 void AppCacheStorageImpl::StartDeletingResponses(
1741 const std::vector<int64_t>& response_ids) {
1742 DCHECK(!response_ids.empty());
1743 did_start_deleting_responses_ = true;
1744 deletable_response_ids_.insert(
1745 deletable_response_ids_.end(),
1746 response_ids.begin(), response_ids.end());
1747 if (!is_response_deletion_scheduled_)
1748 ScheduleDeleteOneResponse();
1749 }
1750
ScheduleDeleteOneResponse()1751 void AppCacheStorageImpl::ScheduleDeleteOneResponse() {
1752 DCHECK(!is_response_deletion_scheduled_);
1753 const base::TimeDelta kBriefDelay = base::TimeDelta::FromMilliseconds(10);
1754 base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
1755 FROM_HERE,
1756 base::BindOnce(&AppCacheStorageImpl::DeleteOneResponse,
1757 weak_factory_.GetWeakPtr()),
1758 kBriefDelay);
1759 is_response_deletion_scheduled_ = true;
1760 }
1761
DeleteOneResponse()1762 void AppCacheStorageImpl::DeleteOneResponse() {
1763 DCHECK(is_response_deletion_scheduled_);
1764 DCHECK(!deletable_response_ids_.empty());
1765
1766 if (is_disabled_) {
1767 deletable_response_ids_.clear();
1768 deleted_response_ids_.clear();
1769 is_response_deletion_scheduled_ = false;
1770 return;
1771 }
1772
1773 // TODO(michaeln): add group_id to DoomEntry args
1774 int64_t id = deletable_response_ids_.front();
1775 int rv = disk_cache()->DoomEntry(
1776 id, base::BindOnce(&AppCacheStorageImpl::OnDeletedOneResponse,
1777 base::Unretained(this)));
1778 if (rv != net::ERR_IO_PENDING)
1779 OnDeletedOneResponse(rv);
1780 }
1781
OnDeletedOneResponse(int rv)1782 void AppCacheStorageImpl::OnDeletedOneResponse(int rv) {
1783 is_response_deletion_scheduled_ = false;
1784 if (is_disabled_)
1785 return;
1786
1787 int64_t id = deletable_response_ids_.front();
1788 deletable_response_ids_.pop_front();
1789 if (rv != net::ERR_ABORTED)
1790 deleted_response_ids_.push_back(id);
1791
1792 const size_t kBatchSize = 50U;
1793 if (deleted_response_ids_.size() >= kBatchSize ||
1794 deletable_response_ids_.empty()) {
1795 auto task = base::MakeRefCounted<DeleteDeletableResponseIdsTask>(this);
1796 task->response_ids_.swap(deleted_response_ids_);
1797 task->Schedule();
1798 }
1799
1800 if (deletable_response_ids_.empty()) {
1801 auto task = base::MakeRefCounted<GetDeletableResponseIdsTask>(
1802 this, last_deletable_response_rowid_);
1803 task->Schedule();
1804 return;
1805 }
1806
1807 ScheduleDeleteOneResponse();
1808 }
1809
1810 AppCacheStorageImpl::CacheLoadTask*
GetPendingCacheLoadTask(int64_t cache_id)1811 AppCacheStorageImpl::GetPendingCacheLoadTask(int64_t cache_id) {
1812 auto found = pending_cache_loads_.find(cache_id);
1813 if (found != pending_cache_loads_.end())
1814 return found->second;
1815 return nullptr;
1816 }
1817
1818 AppCacheStorageImpl::GroupLoadTask*
GetPendingGroupLoadTask(const GURL & manifest_url)1819 AppCacheStorageImpl::GetPendingGroupLoadTask(const GURL& manifest_url) {
1820 auto found = pending_group_loads_.find(manifest_url);
1821 if (found != pending_group_loads_.end())
1822 return found->second;
1823 return nullptr;
1824 }
1825
GetPendingForeignMarkingsForCache(int64_t cache_id)1826 std::vector<GURL> AppCacheStorageImpl::GetPendingForeignMarkingsForCache(
1827 int64_t cache_id) {
1828 std::vector<GURL> urls;
1829 for (const auto& pair : pending_foreign_markings_) {
1830 if (pair.second == cache_id)
1831 urls.push_back(pair.first);
1832 }
1833 return urls;
1834 }
1835
ScheduleSimpleTask(base::OnceClosure task)1836 void AppCacheStorageImpl::ScheduleSimpleTask(base::OnceClosure task) {
1837 pending_simple_tasks_.push_back(std::move(task));
1838 base::SequencedTaskRunnerHandle::Get()->PostTask(
1839 FROM_HERE, base::BindOnce(&AppCacheStorageImpl::RunOnePendingSimpleTask,
1840 weak_factory_.GetWeakPtr()));
1841 }
1842
RunOnePendingSimpleTask()1843 void AppCacheStorageImpl::RunOnePendingSimpleTask() {
1844 DCHECK(!pending_simple_tasks_.empty());
1845 base::OnceClosure task = std::move(pending_simple_tasks_.front());
1846 pending_simple_tasks_.pop_front();
1847 std::move(task).Run();
1848 }
1849
disk_cache()1850 AppCacheDiskCache* AppCacheStorageImpl::disk_cache() {
1851 DCHECK(IsInitTaskComplete());
1852 DCHECK(!is_disabled_);
1853
1854 if (!disk_cache_) {
1855 int rv = net::OK;
1856 disk_cache_ = std::make_unique<AppCacheDiskCache>();
1857 if (is_incognito_) {
1858 rv = disk_cache_->InitWithMemBackend(
1859 0, base::BindOnce(&AppCacheStorageImpl::OnDiskCacheInitialized,
1860 base::Unretained(this)));
1861 } else {
1862 expecting_cleanup_complete_on_disable_ = true;
1863
1864 rv = disk_cache_->InitWithDiskBackend(
1865 cache_directory_.Append(kDiskCacheDirectoryName), false,
1866 base::BindOnce(&AppCacheStorageImpl::OnDiskCacheCleanupComplete,
1867 weak_factory_.GetWeakPtr()),
1868 base::BindOnce(&AppCacheStorageImpl::OnDiskCacheInitialized,
1869 base::Unretained(this)));
1870 }
1871
1872 if (rv != net::ERR_IO_PENDING)
1873 OnDiskCacheInitialized(rv);
1874 }
1875 return disk_cache_.get();
1876 }
1877
OnDiskCacheInitialized(int rv)1878 void AppCacheStorageImpl::OnDiskCacheInitialized(int rv) {
1879 if (rv != net::OK) {
1880 // We're unable to open the disk cache, this is a fatal error that we can't
1881 // really recover from. We handle it by temporarily disabling the appcache
1882 // deleting the directory on disk and reinitializing the appcache system.
1883 Disable();
1884 if (rv != net::ERR_ABORTED)
1885 DeleteAndStartOver();
1886 }
1887 }
1888
DeleteAndStartOver()1889 void AppCacheStorageImpl::DeleteAndStartOver() {
1890 DCHECK(is_disabled_);
1891 if (!is_incognito_) {
1892 VLOG(1) << "Deleting existing appcache data and starting over.";
1893
1894 // We can have tasks in flight to close file handles on both the db
1895 // and cache threads, we need to allow those tasks to cycle thru
1896 // prior to deleting the files and calling reinit. We will know that the
1897 // cache ones will be finished once we get into OnDiskCacheCleanupComplete,
1898 // so let that known to synchronize with the DB thread.
1899 delete_and_start_over_pending_ = true;
1900
1901 // Won't get a callback about cleanup being done, so call it ourselves.
1902 if (!expecting_cleanup_complete_on_disable_)
1903 OnDiskCacheCleanupComplete();
1904 }
1905 }
1906
OnDiskCacheCleanupComplete()1907 void AppCacheStorageImpl::OnDiskCacheCleanupComplete() {
1908 expecting_cleanup_complete_on_disable_ = false;
1909 if (delete_and_start_over_pending_) {
1910 delete_and_start_over_pending_ = false;
1911 db_task_runner_->PostTaskAndReply(
1912 FROM_HERE,
1913 base::BindOnce(base::IgnoreResult(&base::DeleteFile), cache_directory_,
1914 true),
1915 base::BindOnce(&AppCacheStorageImpl::CallScheduleReinitialize,
1916 weak_factory_.GetWeakPtr()));
1917 }
1918 }
1919
CallScheduleReinitialize()1920 void AppCacheStorageImpl::CallScheduleReinitialize() {
1921 service_->ScheduleReinitialize();
1922 // note: 'this' may be deleted at this point.
1923 }
1924
LazilyCommitLastAccessTimes()1925 void AppCacheStorageImpl::LazilyCommitLastAccessTimes() {
1926 if (lazy_commit_timer_.IsRunning())
1927 return;
1928 const base::TimeDelta kDelay = base::TimeDelta::FromMinutes(5);
1929 lazy_commit_timer_.Start(
1930 FROM_HERE, kDelay,
1931 base::BindOnce(&AppCacheStorageImpl::OnLazyCommitTimer,
1932 weak_factory_.GetWeakPtr()));
1933 }
1934
OnLazyCommitTimer()1935 void AppCacheStorageImpl::OnLazyCommitTimer() {
1936 lazy_commit_timer_.Stop();
1937 if (is_disabled())
1938 return;
1939 auto task = base::MakeRefCounted<CommitLastAccessTimesTask>(this);
1940 task->Schedule();
1941 }
1942
1943 } // namespace content
1944