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