Home | History | Annotate | Download | only in appcache
      1 // Copyright 2014 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/mock_appcache_storage.h"
      6 
      7 #include "base/bind.h"
      8 #include "base/logging.h"
      9 #include "base/memory/ref_counted.h"
     10 #include "base/message_loop/message_loop.h"
     11 #include "base/stl_util.h"
     12 #include "webkit/browser/appcache/appcache.h"
     13 #include "webkit/browser/appcache/appcache_entry.h"
     14 #include "webkit/browser/appcache/appcache_group.h"
     15 #include "webkit/browser/appcache/appcache_response.h"
     16 #include "webkit/browser/appcache/appcache_service_impl.h"
     17 
     18 // This is a quick and easy 'mock' implementation of the storage interface
     19 // that doesn't put anything to disk.
     20 //
     21 // We simply add an extra reference to objects when they're put in storage,
     22 // and remove the extra reference when they are removed from storage.
     23 // Responses are never really removed from the in-memory disk cache.
     24 // Delegate callbacks are made asyncly to appropiately mimic what will
     25 // happen with a real disk-backed storage impl that involves IO on a
     26 // background thread.
     27 
     28 using appcache::AppCacheResponseWriter;
     29 using appcache::AppCacheServiceImpl;
     30 using appcache::APPCACHE_FALLBACK_NAMESPACE;
     31 using appcache::APPCACHE_INTERCEPT_NAMESPACE;
     32 using appcache::kAppCacheNoCacheId;
     33 using appcache::AppCacheNamespaceType;
     34 
     35 namespace content {
     36 
     37 MockAppCacheStorage::MockAppCacheStorage(AppCacheServiceImpl* service)
     38     : AppCacheStorage(service),
     39       simulate_make_group_obsolete_failure_(false),
     40       simulate_store_group_and_newest_cache_failure_(false),
     41       simulate_find_main_resource_(false),
     42       simulate_find_sub_resource_(false),
     43       simulated_found_cache_id_(kAppCacheNoCacheId),
     44       simulated_found_group_id_(0),
     45       simulated_found_network_namespace_(false),
     46       weak_factory_(this) {
     47   last_cache_id_ = 0;
     48   last_group_id_ = 0;
     49   last_response_id_ = 0;
     50 }
     51 
     52 MockAppCacheStorage::~MockAppCacheStorage() {
     53 }
     54 
     55 void MockAppCacheStorage::GetAllInfo(Delegate* delegate) {
     56   ScheduleTask(
     57       base::Bind(&MockAppCacheStorage::ProcessGetAllInfo,
     58                  weak_factory_.GetWeakPtr(),
     59                  make_scoped_refptr(GetOrCreateDelegateReference(delegate))));
     60 }
     61 
     62 void MockAppCacheStorage::LoadCache(int64 id, Delegate* delegate) {
     63   DCHECK(delegate);
     64   AppCache* cache = working_set_.GetCache(id);
     65   if (ShouldCacheLoadAppearAsync(cache)) {
     66     ScheduleTask(
     67         base::Bind(&MockAppCacheStorage::ProcessLoadCache,
     68                    weak_factory_.GetWeakPtr(), id,
     69                    make_scoped_refptr(GetOrCreateDelegateReference(delegate))));
     70     return;
     71   }
     72   ProcessLoadCache(id, GetOrCreateDelegateReference(delegate));
     73 }
     74 
     75 void MockAppCacheStorage::LoadOrCreateGroup(
     76     const GURL& manifest_url, Delegate* delegate) {
     77   DCHECK(delegate);
     78   AppCacheGroup* group = working_set_.GetGroup(manifest_url);
     79   if (ShouldGroupLoadAppearAsync(group)) {
     80     ScheduleTask(
     81         base::Bind(&MockAppCacheStorage::ProcessLoadOrCreateGroup,
     82                    weak_factory_.GetWeakPtr(), manifest_url,
     83                    make_scoped_refptr(GetOrCreateDelegateReference(delegate))));
     84     return;
     85   }
     86   ProcessLoadOrCreateGroup(
     87       manifest_url, GetOrCreateDelegateReference(delegate));
     88 }
     89 
     90 void MockAppCacheStorage::StoreGroupAndNewestCache(
     91     AppCacheGroup* group, AppCache* newest_cache, Delegate* delegate) {
     92   DCHECK(group && delegate && newest_cache);
     93 
     94   // Always make this operation look async.
     95   ScheduleTask(
     96       base::Bind(&MockAppCacheStorage::ProcessStoreGroupAndNewestCache,
     97                  weak_factory_.GetWeakPtr(), make_scoped_refptr(group),
     98                  make_scoped_refptr(newest_cache),
     99                  make_scoped_refptr(GetOrCreateDelegateReference(delegate))));
    100 }
    101 
    102 void MockAppCacheStorage::FindResponseForMainRequest(
    103     const GURL& url, const GURL& preferred_manifest_url, Delegate* delegate) {
    104   DCHECK(delegate);
    105 
    106   // Note: MockAppCacheStorage does not respect the preferred_manifest_url.
    107 
    108   // Always make this operation look async.
    109   ScheduleTask(
    110       base::Bind(&MockAppCacheStorage::ProcessFindResponseForMainRequest,
    111                  weak_factory_.GetWeakPtr(), url,
    112                  make_scoped_refptr(GetOrCreateDelegateReference(delegate))));
    113 }
    114 
    115 void MockAppCacheStorage::FindResponseForSubRequest(
    116     AppCache* cache, const GURL& url,
    117     AppCacheEntry* found_entry, AppCacheEntry* found_fallback_entry,
    118     bool* found_network_namespace) {
    119   DCHECK(cache && cache->is_complete());
    120 
    121   // This layer of indirection is here to facilitate testing.
    122   if (simulate_find_sub_resource_) {
    123     *found_entry = simulated_found_entry_;
    124     *found_fallback_entry = simulated_found_fallback_entry_;
    125     *found_network_namespace = simulated_found_network_namespace_;
    126     simulate_find_sub_resource_ = false;
    127     return;
    128   }
    129 
    130   GURL fallback_namespace_not_used;
    131   GURL intercept_namespace_not_used;
    132   cache->FindResponseForRequest(
    133       url, found_entry, &intercept_namespace_not_used,
    134       found_fallback_entry,  &fallback_namespace_not_used,
    135       found_network_namespace);
    136 }
    137 
    138 void MockAppCacheStorage::MarkEntryAsForeign(
    139     const GURL& entry_url, int64 cache_id) {
    140   AppCache* cache = working_set_.GetCache(cache_id);
    141   if (cache) {
    142     AppCacheEntry* entry = cache->GetEntry(entry_url);
    143     DCHECK(entry);
    144     if (entry)
    145       entry->add_types(AppCacheEntry::FOREIGN);
    146   }
    147 }
    148 
    149 void MockAppCacheStorage::MakeGroupObsolete(AppCacheGroup* group,
    150                                             Delegate* delegate,
    151                                             int response_code) {
    152   DCHECK(group && delegate);
    153 
    154   // Always make this method look async.
    155   ScheduleTask(
    156       base::Bind(&MockAppCacheStorage::ProcessMakeGroupObsolete,
    157                  weak_factory_.GetWeakPtr(),
    158                  make_scoped_refptr(group),
    159                  make_scoped_refptr(GetOrCreateDelegateReference(delegate)),
    160                  response_code));
    161 }
    162 
    163 AppCacheResponseReader* MockAppCacheStorage::CreateResponseReader(
    164     const GURL& manifest_url, int64 group_id, int64 response_id) {
    165   if (simulated_reader_)
    166     return simulated_reader_.release();
    167   return new AppCacheResponseReader(response_id, group_id, disk_cache());
    168 }
    169 
    170 AppCacheResponseWriter* MockAppCacheStorage::CreateResponseWriter(
    171     const GURL& manifest_url, int64 group_id) {
    172   return new AppCacheResponseWriter(NewResponseId(),  group_id, disk_cache());
    173 }
    174 
    175 void MockAppCacheStorage::DoomResponses(
    176     const GURL& manifest_url, const std::vector<int64>& response_ids) {
    177   DeleteResponses(manifest_url, response_ids);
    178 }
    179 
    180 void MockAppCacheStorage::DeleteResponses(
    181     const GURL& manifest_url, const std::vector<int64>& response_ids) {
    182   // We don't bother with actually removing responses from the disk-cache,
    183   // just keep track of which ids have been doomed or deleted
    184   std::vector<int64>::const_iterator it = response_ids.begin();
    185   while (it != response_ids.end()) {
    186     doomed_response_ids_.insert(*it);
    187     ++it;
    188   }
    189 }
    190 
    191 void MockAppCacheStorage::ProcessGetAllInfo(
    192     scoped_refptr<DelegateReference> delegate_ref) {
    193   if (delegate_ref->delegate)
    194     delegate_ref->delegate->OnAllInfo(simulated_appcache_info_.get());
    195 }
    196 
    197 void MockAppCacheStorage::ProcessLoadCache(
    198     int64 id, scoped_refptr<DelegateReference> delegate_ref) {
    199   AppCache* cache = working_set_.GetCache(id);
    200   if (delegate_ref->delegate)
    201     delegate_ref->delegate->OnCacheLoaded(cache, id);
    202 }
    203 
    204 void MockAppCacheStorage::ProcessLoadOrCreateGroup(
    205     const GURL& manifest_url, scoped_refptr<DelegateReference> delegate_ref) {
    206   scoped_refptr<AppCacheGroup> group(working_set_.GetGroup(manifest_url));
    207 
    208   // Newly created groups are not put in the stored_groups collection
    209   // until StoreGroupAndNewestCache is called.
    210   if (!group.get())
    211     group = new AppCacheGroup(service_->storage(), manifest_url, NewGroupId());
    212 
    213   if (delegate_ref->delegate)
    214     delegate_ref->delegate->OnGroupLoaded(group.get(), manifest_url);
    215 }
    216 
    217 void MockAppCacheStorage::ProcessStoreGroupAndNewestCache(
    218     scoped_refptr<AppCacheGroup> group,
    219     scoped_refptr<AppCache> newest_cache,
    220     scoped_refptr<DelegateReference> delegate_ref) {
    221   Delegate* delegate = delegate_ref->delegate;
    222   if (simulate_store_group_and_newest_cache_failure_) {
    223     if (delegate)
    224       delegate->OnGroupAndNewestCacheStored(
    225           group.get(), newest_cache.get(), false, false);
    226     return;
    227   }
    228 
    229   AddStoredGroup(group.get());
    230   if (newest_cache.get() != group->newest_complete_cache()) {
    231     newest_cache->set_complete(true);
    232     group->AddCache(newest_cache.get());
    233     AddStoredCache(newest_cache.get());
    234 
    235     // Copy the collection prior to removal, on final release
    236     // of a cache the group's collection will change.
    237     AppCacheGroup::Caches copy = group->old_caches();
    238     RemoveStoredCaches(copy);
    239   }
    240 
    241   if (delegate)
    242     delegate->OnGroupAndNewestCacheStored(
    243         group.get(), newest_cache.get(), true, false);
    244 }
    245 
    246 namespace {
    247 
    248 struct FoundCandidate {
    249   GURL namespace_entry_url;
    250   AppCacheEntry entry;
    251   int64 cache_id;
    252   int64 group_id;
    253   GURL manifest_url;
    254   bool is_cache_in_use;
    255 
    256   FoundCandidate()
    257       : cache_id(kAppCacheNoCacheId), group_id(0), is_cache_in_use(false) {}
    258 };
    259 
    260 void MaybeTakeNewNamespaceEntry(
    261     AppCacheNamespaceType namespace_type,
    262     const AppCacheEntry &entry,
    263     const GURL& namespace_url,
    264     bool cache_is_in_use,
    265     FoundCandidate* best_candidate,
    266     GURL* best_candidate_namespace,
    267     AppCache* cache,
    268     AppCacheGroup* group) {
    269   DCHECK(entry.has_response_id());
    270 
    271   bool take_new_entry = true;
    272 
    273   // Does the new candidate entry trump our current best candidate?
    274   if (best_candidate->entry.has_response_id()) {
    275     // Longer namespace prefix matches win.
    276     size_t candidate_length =
    277         namespace_url.spec().length();
    278     size_t best_length =
    279         best_candidate_namespace->spec().length();
    280 
    281     if (candidate_length > best_length) {
    282       take_new_entry = true;
    283     } else if (candidate_length == best_length &&
    284                cache_is_in_use && !best_candidate->is_cache_in_use) {
    285       take_new_entry = true;
    286     } else {
    287       take_new_entry = false;
    288     }
    289   }
    290 
    291   if (take_new_entry) {
    292     if (namespace_type == APPCACHE_FALLBACK_NAMESPACE) {
    293       best_candidate->namespace_entry_url =
    294           cache->GetFallbackEntryUrl(namespace_url);
    295     } else {
    296       best_candidate->namespace_entry_url =
    297           cache->GetInterceptEntryUrl(namespace_url);
    298     }
    299     best_candidate->entry = entry;
    300     best_candidate->cache_id = cache->cache_id();
    301     best_candidate->group_id = group->group_id();
    302     best_candidate->manifest_url = group->manifest_url();
    303     best_candidate->is_cache_in_use = cache_is_in_use;
    304     *best_candidate_namespace = namespace_url;
    305   }
    306 }
    307 }  // namespace
    308 
    309 void MockAppCacheStorage::ProcessFindResponseForMainRequest(
    310     const GURL& url, scoped_refptr<DelegateReference> delegate_ref) {
    311   if (simulate_find_main_resource_) {
    312     simulate_find_main_resource_ = false;
    313     if (delegate_ref->delegate) {
    314       delegate_ref->delegate->OnMainResponseFound(
    315           url, simulated_found_entry_,
    316           simulated_found_fallback_url_, simulated_found_fallback_entry_,
    317           simulated_found_cache_id_, simulated_found_group_id_,
    318           simulated_found_manifest_url_);
    319     }
    320     return;
    321   }
    322 
    323   // This call has no persistent side effects, if the delegate has gone
    324   // away, we can just bail out early.
    325   if (!delegate_ref->delegate)
    326     return;
    327 
    328   // TODO(michaeln): The heuristics around choosing amoungst
    329   // multiple candidates is under specified, and just plain
    330   // not fully understood. Refine these over time. In particular,
    331   // * prefer candidates from newer caches
    332   // * take into account the cache associated with the document
    333   //   that initiated the navigation
    334   // * take into account the cache associated with the document
    335   //   currently residing in the frame being navigated
    336   FoundCandidate found_candidate;
    337   GURL found_intercept_candidate_namespace;
    338   FoundCandidate found_fallback_candidate;
    339   GURL found_fallback_candidate_namespace;
    340 
    341   for (StoredGroupMap::const_iterator it = stored_groups_.begin();
    342        it != stored_groups_.end(); ++it) {
    343     AppCacheGroup* group = it->second.get();
    344     AppCache* cache = group->newest_complete_cache();
    345     if (group->is_obsolete() || !cache ||
    346         (url.GetOrigin() != group->manifest_url().GetOrigin())) {
    347       continue;
    348     }
    349 
    350     AppCacheEntry found_entry;
    351     AppCacheEntry found_fallback_entry;
    352     GURL found_intercept_namespace;
    353     GURL found_fallback_namespace;
    354     bool ignore_found_network_namespace = false;
    355     bool found = cache->FindResponseForRequest(
    356                             url, &found_entry, &found_intercept_namespace,
    357                             &found_fallback_entry, &found_fallback_namespace,
    358                             &ignore_found_network_namespace);
    359 
    360     // 6.11.1 Navigating across documents, Step 10.
    361     // Network namespacing doesn't apply to main resource loads,
    362     // and foreign entries are excluded.
    363     if (!found || ignore_found_network_namespace ||
    364         (found_entry.has_response_id() && found_entry.IsForeign()) ||
    365         (found_fallback_entry.has_response_id() &&
    366          found_fallback_entry.IsForeign())) {
    367       continue;
    368     }
    369 
    370     // We have a bias for hits from caches that are in use.
    371     bool is_in_use = IsCacheStored(cache) && !cache->HasOneRef();
    372 
    373     if (found_entry.has_response_id() &&
    374         found_intercept_namespace.is_empty()) {
    375       found_candidate.namespace_entry_url = GURL();
    376       found_candidate.entry = found_entry;
    377       found_candidate.cache_id = cache->cache_id();
    378       found_candidate.group_id = group->group_id();
    379       found_candidate.manifest_url = group->manifest_url();
    380       found_candidate.is_cache_in_use = is_in_use;
    381       if (is_in_use)
    382         break;  // We break out of the loop with this direct hit.
    383     } else if (found_entry.has_response_id() &&
    384                !found_intercept_namespace.is_empty()) {
    385       MaybeTakeNewNamespaceEntry(
    386           APPCACHE_INTERCEPT_NAMESPACE,
    387           found_entry, found_intercept_namespace, is_in_use,
    388           &found_candidate, &found_intercept_candidate_namespace,
    389           cache, group);
    390     } else {
    391       DCHECK(found_fallback_entry.has_response_id());
    392       MaybeTakeNewNamespaceEntry(
    393           APPCACHE_FALLBACK_NAMESPACE,
    394           found_fallback_entry, found_fallback_namespace, is_in_use,
    395           &found_fallback_candidate, &found_fallback_candidate_namespace,
    396           cache, group);
    397     }
    398   }
    399 
    400   // Found a direct hit or an intercept namespace hit.
    401   if (found_candidate.entry.has_response_id()) {
    402     delegate_ref->delegate->OnMainResponseFound(
    403         url, found_candidate.entry, found_candidate.namespace_entry_url,
    404         AppCacheEntry(),  found_candidate.cache_id, found_candidate.group_id,
    405         found_candidate.manifest_url);
    406     return;
    407   }
    408 
    409   // Found a fallback namespace.
    410   if (found_fallback_candidate.entry.has_response_id()) {
    411     delegate_ref->delegate->OnMainResponseFound(
    412         url, AppCacheEntry(),
    413         found_fallback_candidate.namespace_entry_url,
    414         found_fallback_candidate.entry,
    415         found_fallback_candidate.cache_id,
    416         found_fallback_candidate.group_id,
    417         found_fallback_candidate.manifest_url);
    418     return;
    419   }
    420 
    421   // Didn't find anything.
    422   delegate_ref->delegate->OnMainResponseFound(
    423       url, AppCacheEntry(), GURL(), AppCacheEntry(), kAppCacheNoCacheId, 0,
    424       GURL());
    425 }
    426 
    427 void MockAppCacheStorage::ProcessMakeGroupObsolete(
    428     scoped_refptr<AppCacheGroup> group,
    429     scoped_refptr<DelegateReference> delegate_ref,
    430     int response_code) {
    431   if (simulate_make_group_obsolete_failure_) {
    432     if (delegate_ref->delegate)
    433       delegate_ref->delegate->OnGroupMadeObsolete(
    434           group.get(), false, response_code);
    435     return;
    436   }
    437 
    438   RemoveStoredGroup(group.get());
    439   if (group->newest_complete_cache())
    440     RemoveStoredCache(group->newest_complete_cache());
    441 
    442   // Copy the collection prior to removal, on final release
    443   // of a cache the group's collection will change.
    444   AppCacheGroup::Caches copy = group->old_caches();
    445   RemoveStoredCaches(copy);
    446 
    447   group->set_obsolete(true);
    448 
    449   // Also remove from the working set, caches for an 'obsolete' group
    450   // may linger in use, but the group itself cannot be looked up by
    451   // 'manifest_url' in the working set any longer.
    452   working_set()->RemoveGroup(group.get());
    453 
    454   if (delegate_ref->delegate)
    455     delegate_ref->delegate->OnGroupMadeObsolete(
    456         group.get(), true, response_code);
    457 }
    458 
    459 void MockAppCacheStorage::ScheduleTask(const base::Closure& task) {
    460   pending_tasks_.push_back(task);
    461   base::MessageLoop::current()->PostTask(
    462       FROM_HERE,
    463       base::Bind(&MockAppCacheStorage::RunOnePendingTask,
    464                  weak_factory_.GetWeakPtr()));
    465 }
    466 
    467 void MockAppCacheStorage::RunOnePendingTask() {
    468   DCHECK(!pending_tasks_.empty());
    469   base::Closure task = pending_tasks_.front();
    470   pending_tasks_.pop_front();
    471   task.Run();
    472 }
    473 
    474 void MockAppCacheStorage::AddStoredCache(AppCache* cache) {
    475   int64 cache_id = cache->cache_id();
    476   if (stored_caches_.find(cache_id) == stored_caches_.end()) {
    477     stored_caches_.insert(
    478         StoredCacheMap::value_type(cache_id, make_scoped_refptr(cache)));
    479   }
    480 }
    481 
    482 void MockAppCacheStorage::RemoveStoredCache(AppCache* cache) {
    483   // Do not remove from the working set, active caches are still usable
    484   // and may be looked up by id until they fall out of use.
    485   stored_caches_.erase(cache->cache_id());
    486 }
    487 
    488 void MockAppCacheStorage::RemoveStoredCaches(
    489     const AppCacheGroup::Caches& caches) {
    490   AppCacheGroup::Caches::const_iterator it = caches.begin();
    491   while (it != caches.end()) {
    492     RemoveStoredCache(*it);
    493     ++it;
    494   }
    495 }
    496 
    497 void MockAppCacheStorage::AddStoredGroup(AppCacheGroup* group) {
    498   const GURL& url = group->manifest_url();
    499   if (stored_groups_.find(url) == stored_groups_.end()) {
    500     stored_groups_.insert(
    501         StoredGroupMap::value_type(url, make_scoped_refptr(group)));
    502   }
    503 }
    504 
    505 void MockAppCacheStorage::RemoveStoredGroup(AppCacheGroup* group) {
    506   stored_groups_.erase(group->manifest_url());
    507 }
    508 
    509 bool MockAppCacheStorage::ShouldGroupLoadAppearAsync(
    510     const AppCacheGroup* group) {
    511   // We'll have to query the database to see if a group for the
    512   // manifest_url exists on disk. So return true for async.
    513   if (!group)
    514     return true;
    515 
    516   // Groups without a newest cache can't have been put to disk yet, so
    517   // we can synchronously return a reference we have in the working set.
    518   if (!group->newest_complete_cache())
    519     return false;
    520 
    521   // The LoadGroup interface implies also loading the newest cache, so
    522   // if loading the newest cache should appear async, so too must the
    523   // loading of this group.
    524   if (!ShouldCacheLoadAppearAsync(group->newest_complete_cache()))
    525     return false;
    526 
    527 
    528   // If any of the old caches are "in use", then the group must also
    529   // be memory resident and not require async loading.
    530   const AppCacheGroup::Caches& old_caches = group->old_caches();
    531   AppCacheGroup::Caches::const_iterator it = old_caches.begin();
    532   while (it != old_caches.end()) {
    533     // "in use" caches don't require async loading
    534     if (!ShouldCacheLoadAppearAsync(*it))
    535       return false;
    536     ++it;
    537   }
    538 
    539   return true;
    540 }
    541 
    542 bool MockAppCacheStorage::ShouldCacheLoadAppearAsync(const AppCache* cache) {
    543   if (!cache)
    544     return true;
    545 
    546   // If the 'stored' ref is the only ref, real storage will have to load from
    547   // the database.
    548   return IsCacheStored(cache) && cache->HasOneRef();
    549 }
    550 
    551 }  // namespace content
    552