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