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