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