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