[Storage] Blob Storage Refactoring pt 1:
[chromium-blink-merge.git] / content / browser / appcache / mock_appcache_storage.cc
blob91630f7d351ef9e41a2fbd9c65244791a71a0dec
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"
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"
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
26 // background thread.
28 namespace content {
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;
45 MockAppCacheStorage::~MockAppCacheStorage() {
48 void MockAppCacheStorage::GetAllInfo(Delegate* delegate) {
49 ScheduleTask(
50 base::Bind(&MockAppCacheStorage::ProcessGetAllInfo,
51 weak_factory_.GetWeakPtr(),
52 make_scoped_refptr(GetOrCreateDelegateReference(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;
65 ProcessLoadCache(id, GetOrCreateDelegateReference(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;
79 ProcessLoadOrCreateGroup(
80 manifest_url, GetOrCreateDelegateReference(delegate));
83 void MockAppCacheStorage::StoreGroupAndNewestCache(
84 AppCacheGroup* group, AppCache* newest_cache, Delegate* delegate) {
85 DCHECK(group && delegate && newest_cache);
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))));
95 void MockAppCacheStorage::FindResponseForMainRequest(
96 const GURL& url, const GURL& preferred_manifest_url, Delegate* delegate) {
97 DCHECK(delegate);
99 // Note: MockAppCacheStorage does not respect the preferred_manifest_url.
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))));
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());
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;
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);
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);
142 void MockAppCacheStorage::MakeGroupObsolete(AppCacheGroup* group,
143 Delegate* delegate,
144 int response_code) {
145 DCHECK(group && delegate);
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));
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());
163 AppCacheResponseWriter* MockAppCacheStorage::CreateResponseWriter(
164 const GURL& manifest_url, int64 group_id) {
165 return new AppCacheResponseWriter(NewResponseId(), group_id, disk_cache());
168 void MockAppCacheStorage::DoomResponses(
169 const GURL& manifest_url, const std::vector<int64>& response_ids) {
170 DeleteResponses(manifest_url, response_ids);
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;
184 void MockAppCacheStorage::ProcessGetAllInfo(
185 scoped_refptr<DelegateReference> delegate_ref) {
186 if (delegate_ref->delegate)
187 delegate_ref->delegate->OnAllInfo(simulated_appcache_info_.get());
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);
197 void MockAppCacheStorage::ProcessLoadOrCreateGroup(
198 const GURL& manifest_url, scoped_refptr<DelegateReference> delegate_ref) {
199 scoped_refptr<AppCacheGroup> group(working_set_.GetGroup(manifest_url));
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());
206 if (delegate_ref->delegate)
207 delegate_ref->delegate->OnGroupLoaded(group.get(), manifest_url);
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;
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());
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);
234 if (delegate)
235 delegate->OnGroupAndNewestCacheStored(
236 group.get(), newest_cache.get(), true, false);
239 namespace {
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;
249 FoundCandidate()
250 : cache_id(kAppCacheNoCacheId), group_id(0), is_cache_in_use(false) {}
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());
264 bool take_new_entry = true;
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();
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;
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);
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;
300 } // namespace
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_);
313 return;
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;
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;
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;
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);
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;
363 // We have a bias for hits from caches that are in use.
364 bool is_in_use = IsCacheStored(cache) && !cache->HasOneRef();
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);
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;
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;
414 // Didn't find anything.
415 delegate_ref->delegate->OnMainResponseFound(
416 url, AppCacheEntry(), GURL(), AppCacheEntry(), kAppCacheNoCacheId, 0,
417 GURL());
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;
431 RemoveStoredGroup(group.get());
432 if (group->newest_complete_cache())
433 RemoveStoredCache(group->newest_complete_cache());
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);
440 group->set_obsolete(true);
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());
447 if (delegate_ref->delegate)
448 delegate_ref->delegate->OnGroupMadeObsolete(
449 group.get(), true, response_code);
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()));
460 void MockAppCacheStorage::RunOnePendingTask() {
461 DCHECK(!pending_tasks_.empty());
462 base::Closure task = pending_tasks_.front();
463 pending_tasks_.pop_front();
464 task.Run();
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)));
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());
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;
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)));
498 void MockAppCacheStorage::RemoveStoredGroup(AppCacheGroup* group) {
499 stored_groups_.erase(group->manifest_url());
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;
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;
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;
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;
532 return true;
535 bool MockAppCacheStorage::ShouldCacheLoadAppearAsync(const AppCache* cache) {
536 if (!cache)
537 return true;
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();
544 } // namespace content