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.
5 #include "content/browser/appcache/mock_appcache_storage.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"
18 // This is a quick and easy 'mock' implementation of the storage interface
19 // that doesn't put anything to disk.
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
28 using appcache::AppCacheResponseWriter;
29 using appcache::AppCacheService;
30 using appcache::FALLBACK_NAMESPACE;
31 using appcache::INTERCEPT_NAMESPACE;
32 using appcache::kNoCacheId;
33 using appcache::NamespaceType;
37 MockAppCacheStorage::MockAppCacheStorage(AppCacheService* 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_(kNoCacheId),
44 simulated_found_group_id_(0),
45 simulated_found_network_namespace_(false),
49 last_response_id_ = 0;
52 MockAppCacheStorage::~MockAppCacheStorage() {
55 void MockAppCacheStorage::GetAllInfo(Delegate* delegate) {
57 base::Bind(&MockAppCacheStorage::ProcessGetAllInfo,
58 weak_factory_.GetWeakPtr(),
59 make_scoped_refptr(GetOrCreateDelegateReference(delegate))));
62 void MockAppCacheStorage::LoadCache(int64 id, Delegate* delegate) {
64 AppCache* cache = working_set_.GetCache(id);
65 if (ShouldCacheLoadAppearAsync(cache)) {
67 base::Bind(&MockAppCacheStorage::ProcessLoadCache,
68 weak_factory_.GetWeakPtr(), id,
69 make_scoped_refptr(GetOrCreateDelegateReference(delegate))));
72 ProcessLoadCache(id, GetOrCreateDelegateReference(delegate));
75 void MockAppCacheStorage::LoadOrCreateGroup(
76 const GURL& manifest_url, Delegate* delegate) {
78 AppCacheGroup* group = working_set_.GetGroup(manifest_url);
79 if (ShouldGroupLoadAppearAsync(group)) {
81 base::Bind(&MockAppCacheStorage::ProcessLoadOrCreateGroup,
82 weak_factory_.GetWeakPtr(), manifest_url,
83 make_scoped_refptr(GetOrCreateDelegateReference(delegate))));
86 ProcessLoadOrCreateGroup(
87 manifest_url, GetOrCreateDelegateReference(delegate));
90 void MockAppCacheStorage::StoreGroupAndNewestCache(
91 AppCacheGroup* group, AppCache* newest_cache, Delegate* delegate) {
92 DCHECK(group && delegate && newest_cache);
94 // Always make this operation look async.
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))));
102 void MockAppCacheStorage::FindResponseForMainRequest(
103 const GURL& url, const GURL& preferred_manifest_url, Delegate* delegate) {
106 // Note: MockAppCacheStorage does not respect the preferred_manifest_url.
108 // Always make this operation look async.
110 base::Bind(&MockAppCacheStorage::ProcessFindResponseForMainRequest,
111 weak_factory_.GetWeakPtr(), url,
112 make_scoped_refptr(GetOrCreateDelegateReference(delegate))));
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());
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;
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);
138 void MockAppCacheStorage::MarkEntryAsForeign(
139 const GURL& entry_url, int64 cache_id) {
140 AppCache* cache = working_set_.GetCache(cache_id);
142 AppCacheEntry* entry = cache->GetEntry(entry_url);
145 entry->add_types(AppCacheEntry::FOREIGN);
149 void MockAppCacheStorage::MakeGroupObsolete(AppCacheGroup* group,
152 DCHECK(group && delegate);
154 // Always make this method look async.
156 base::Bind(&MockAppCacheStorage::ProcessMakeGroupObsolete,
157 weak_factory_.GetWeakPtr(),
158 make_scoped_refptr(group),
159 make_scoped_refptr(GetOrCreateDelegateReference(delegate)),
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());
170 AppCacheResponseWriter* MockAppCacheStorage::CreateResponseWriter(
171 const GURL& manifest_url, int64 group_id) {
172 return new AppCacheResponseWriter(NewResponseId(), group_id, disk_cache());
175 void MockAppCacheStorage::DoomResponses(
176 const GURL& manifest_url, const std::vector<int64>& response_ids) {
177 DeleteResponses(manifest_url, response_ids);
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);
191 void MockAppCacheStorage::ProcessGetAllInfo(
192 scoped_refptr<DelegateReference> delegate_ref) {
193 if (delegate_ref->delegate)
194 delegate_ref->delegate->OnAllInfo(simulated_appcache_info_.get());
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);
204 void MockAppCacheStorage::ProcessLoadOrCreateGroup(
205 const GURL& manifest_url, scoped_refptr<DelegateReference> delegate_ref) {
206 scoped_refptr<AppCacheGroup> group(working_set_.GetGroup(manifest_url));
208 // Newly created groups are not put in the stored_groups collection
209 // until StoreGroupAndNewestCache is called.
211 group = new AppCacheGroup(service_->storage(), manifest_url, NewGroupId());
213 if (delegate_ref->delegate)
214 delegate_ref->delegate->OnGroupLoaded(group.get(), manifest_url);
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_) {
224 delegate->OnGroupAndNewestCacheStored(
225 group.get(), newest_cache.get(), false, false);
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());
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);
242 delegate->OnGroupAndNewestCacheStored(
243 group.get(), newest_cache.get(), true, false);
248 struct FoundCandidate {
249 GURL namespace_entry_url;
254 bool is_cache_in_use;
257 : cache_id(kNoCacheId), group_id(0), is_cache_in_use(false) {}
260 void MaybeTakeNewNamespaceEntry(
261 NamespaceType 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,
268 AppCacheGroup* group) {
269 DCHECK(entry.has_response_id());
271 bool take_new_entry = true;
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();
279 best_candidate_namespace->spec().length();
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;
287 take_new_entry = false;
291 if (take_new_entry) {
292 if (namespace_type == FALLBACK_NAMESPACE) {
293 best_candidate->namespace_entry_url =
294 cache->GetFallbackEntryUrl(namespace_url);
296 best_candidate->namespace_entry_url =
297 cache->GetInterceptEntryUrl(namespace_url);
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;
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_);
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)
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;
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())) {
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);
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())) {
370 // We have a bias for hits from caches that are in use.
371 bool is_in_use = IsCacheStored(cache) && !cache->HasOneRef();
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;
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(
387 found_entry, found_intercept_namespace, is_in_use,
388 &found_candidate, &found_intercept_candidate_namespace,
391 DCHECK(found_fallback_entry.has_response_id());
392 MaybeTakeNewNamespaceEntry(
394 found_fallback_entry, found_fallback_namespace, is_in_use,
395 &found_fallback_candidate, &found_fallback_candidate_namespace,
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);
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);
421 // Didn't find anything.
422 delegate_ref->delegate->OnMainResponseFound(
423 url, AppCacheEntry(), GURL(), AppCacheEntry(), kNoCacheId, 0, GURL());
426 void MockAppCacheStorage::ProcessMakeGroupObsolete(
427 scoped_refptr<AppCacheGroup> group,
428 scoped_refptr<DelegateReference> delegate_ref,
430 if (simulate_make_group_obsolete_failure_) {
431 if (delegate_ref->delegate)
432 delegate_ref->delegate->OnGroupMadeObsolete(
433 group.get(), false, response_code);
437 RemoveStoredGroup(group.get());
438 if (group->newest_complete_cache())
439 RemoveStoredCache(group->newest_complete_cache());
441 // Copy the collection prior to removal, on final release
442 // of a cache the group's collection will change.
443 AppCacheGroup::Caches copy = group->old_caches();
444 RemoveStoredCaches(copy);
446 group->set_obsolete(true);
448 // Also remove from the working set, caches for an 'obsolete' group
449 // may linger in use, but the group itself cannot be looked up by
450 // 'manifest_url' in the working set any longer.
451 working_set()->RemoveGroup(group.get());
453 if (delegate_ref->delegate)
454 delegate_ref->delegate->OnGroupMadeObsolete(
455 group.get(), true, response_code);
458 void MockAppCacheStorage::ScheduleTask(const base::Closure& task) {
459 pending_tasks_.push_back(task);
460 base::MessageLoop::current()->PostTask(
462 base::Bind(&MockAppCacheStorage::RunOnePendingTask,
463 weak_factory_.GetWeakPtr()));
466 void MockAppCacheStorage::RunOnePendingTask() {
467 DCHECK(!pending_tasks_.empty());
468 base::Closure task = pending_tasks_.front();
469 pending_tasks_.pop_front();
473 void MockAppCacheStorage::AddStoredCache(AppCache* cache) {
474 int64 cache_id = cache->cache_id();
475 if (stored_caches_.find(cache_id) == stored_caches_.end()) {
476 stored_caches_.insert(
477 StoredCacheMap::value_type(cache_id, make_scoped_refptr(cache)));
481 void MockAppCacheStorage::RemoveStoredCache(AppCache* cache) {
482 // Do not remove from the working set, active caches are still usable
483 // and may be looked up by id until they fall out of use.
484 stored_caches_.erase(cache->cache_id());
487 void MockAppCacheStorage::RemoveStoredCaches(
488 const AppCacheGroup::Caches& caches) {
489 AppCacheGroup::Caches::const_iterator it = caches.begin();
490 while (it != caches.end()) {
491 RemoveStoredCache(*it);
496 void MockAppCacheStorage::AddStoredGroup(AppCacheGroup* group) {
497 const GURL& url = group->manifest_url();
498 if (stored_groups_.find(url) == stored_groups_.end()) {
499 stored_groups_.insert(
500 StoredGroupMap::value_type(url, make_scoped_refptr(group)));
504 void MockAppCacheStorage::RemoveStoredGroup(AppCacheGroup* group) {
505 stored_groups_.erase(group->manifest_url());
508 bool MockAppCacheStorage::ShouldGroupLoadAppearAsync(
509 const AppCacheGroup* group) {
510 // We'll have to query the database to see if a group for the
511 // manifest_url exists on disk. So return true for async.
515 // Groups without a newest cache can't have been put to disk yet, so
516 // we can synchronously return a reference we have in the working set.
517 if (!group->newest_complete_cache())
520 // The LoadGroup interface implies also loading the newest cache, so
521 // if loading the newest cache should appear async, so too must the
522 // loading of this group.
523 if (!ShouldCacheLoadAppearAsync(group->newest_complete_cache()))
527 // If any of the old caches are "in use", then the group must also
528 // be memory resident and not require async loading.
529 const AppCacheGroup::Caches& old_caches = group->old_caches();
530 AppCacheGroup::Caches::const_iterator it = old_caches.begin();
531 while (it != old_caches.end()) {
532 // "in use" caches don't require async loading
533 if (!ShouldCacheLoadAppearAsync(*it))
541 bool MockAppCacheStorage::ShouldCacheLoadAppearAsync(const AppCache* cache) {
545 // If the 'stored' ref is the only ref, real storage will have to load from
547 return IsCacheStored(cache) && cache->HasOneRef();
550 } // namespace content