• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
MockAppCacheStorage(AppCacheService * service)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 
~MockAppCacheStorage()45 MockAppCacheStorage::~MockAppCacheStorage() {
46 }
47 
GetAllInfo(Delegate * delegate)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 
LoadCache(int64 id,Delegate * delegate)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 
LoadOrCreateGroup(const GURL & manifest_url,Delegate * delegate)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 
StoreGroupAndNewestCache(AppCacheGroup * group,AppCache * newest_cache,Delegate * delegate)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 
FindResponseForMainRequest(const GURL & url,const GURL & preferred_manifest_url,Delegate * delegate)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 
FindResponseForSubRequest(AppCache * cache,const GURL & url,AppCacheEntry * found_entry,AppCacheEntry * found_fallback_entry,bool * found_network_namespace)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 
MarkEntryAsForeign(const GURL & entry_url,int64 cache_id)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 
MakeGroupObsolete(AppCacheGroup * group,Delegate * delegate)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 
CreateResponseReader(const GURL & manifest_url,int64 group_id,int64 response_id)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 
CreateResponseWriter(const GURL & manifest_url,int64 group_id)160 AppCacheResponseWriter* MockAppCacheStorage::CreateResponseWriter(
161     const GURL& manifest_url, int64 group_id) {
162   return new AppCacheResponseWriter(NewResponseId(),  group_id, disk_cache());
163 }
164 
DoomResponses(const GURL & manifest_url,const std::vector<int64> & response_ids)165 void MockAppCacheStorage::DoomResponses(
166     const GURL& manifest_url, const std::vector<int64>& response_ids) {
167   DeleteResponses(manifest_url, response_ids);
168 }
169 
DeleteResponses(const GURL & manifest_url,const std::vector<int64> & response_ids)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 
ProcessGetAllInfo(scoped_refptr<DelegateReference> delegate_ref)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 
ProcessLoadCache(int64 id,scoped_refptr<DelegateReference> delegate_ref)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 
ProcessLoadOrCreateGroup(const GURL & manifest_url,scoped_refptr<DelegateReference> delegate_ref)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 
ProcessStoreGroupAndNewestCache(scoped_refptr<AppCacheGroup> group,scoped_refptr<AppCache> newest_cache,scoped_refptr<DelegateReference> delegate_ref)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 
FoundCandidateappcache::__anonf25fd5270111::FoundCandidate246   FoundCandidate()
247       : cache_id(kNoCacheId), group_id(0), is_cache_in_use(false) {}
248 };
249 
MaybeTakeNewNamespaceEntry(NamespaceType namespace_type,const AppCacheEntry & entry,const GURL & namespace_url,bool cache_is_in_use,FoundCandidate * best_candidate,GURL * best_candidate_namespace,AppCache * cache,AppCacheGroup * group)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 
ProcessFindResponseForMainRequest(const GURL & url,scoped_refptr<DelegateReference> delegate_ref)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 
ProcessMakeGroupObsolete(scoped_refptr<AppCacheGroup> group,scoped_refptr<DelegateReference> delegate_ref)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 
ScheduleTask(const base::Closure & task)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 
RunOnePendingTask()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 
AddStoredCache(AppCache * cache)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 
RemoveStoredCache(AppCache * cache)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 
RemoveStoredCaches(const AppCacheGroup::Caches & caches)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 
AddStoredGroup(AppCacheGroup * group)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 
RemoveStoredGroup(AppCacheGroup * group)491 void MockAppCacheStorage::RemoveStoredGroup(AppCacheGroup* group) {
492   stored_groups_.erase(group->manifest_url());
493 }
494 
ShouldGroupLoadAppearAsync(const AppCacheGroup * group)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 
ShouldCacheLoadAppearAsync(const AppCache * cache)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