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