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/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
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),
44 last_response_id_
= 0;
47 MockAppCacheStorage::~MockAppCacheStorage() {
50 void MockAppCacheStorage::GetAllInfo(Delegate
* delegate
) {
52 base::Bind(&MockAppCacheStorage::ProcessGetAllInfo
,
53 weak_factory_
.GetWeakPtr(),
54 make_scoped_refptr(GetOrCreateDelegateReference(delegate
))));
57 void MockAppCacheStorage::LoadCache(int64 id
, Delegate
* delegate
) {
59 AppCache
* cache
= working_set_
.GetCache(id
);
60 if (ShouldCacheLoadAppearAsync(cache
)) {
62 base::Bind(&MockAppCacheStorage::ProcessLoadCache
,
63 weak_factory_
.GetWeakPtr(), id
,
64 make_scoped_refptr(GetOrCreateDelegateReference(delegate
))));
67 ProcessLoadCache(id
, GetOrCreateDelegateReference(delegate
));
70 void MockAppCacheStorage::LoadOrCreateGroup(
71 const GURL
& manifest_url
, Delegate
* delegate
) {
73 AppCacheGroup
* group
= working_set_
.GetGroup(manifest_url
);
74 if (ShouldGroupLoadAppearAsync(group
)) {
76 base::Bind(&MockAppCacheStorage::ProcessLoadOrCreateGroup
,
77 weak_factory_
.GetWeakPtr(), manifest_url
,
78 make_scoped_refptr(GetOrCreateDelegateReference(delegate
))));
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.
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
) {
101 // Note: MockAppCacheStorage does not respect the preferred_manifest_url.
103 // Always make this operation look async.
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;
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
);
137 AppCacheEntry
* entry
= cache
->GetEntry(entry_url
);
140 entry
->add_types(AppCacheEntry::FOREIGN
);
144 void MockAppCacheStorage::MakeGroupObsolete(AppCacheGroup
* group
,
147 DCHECK(group
&& delegate
);
149 // Always make this method look async.
151 base::Bind(&MockAppCacheStorage::ProcessMakeGroupObsolete
,
152 weak_factory_
.GetWeakPtr(),
153 make_scoped_refptr(group
),
154 make_scoped_refptr(GetOrCreateDelegateReference(delegate
)),
158 AppCacheResponseReader
* MockAppCacheStorage::CreateResponseReader(
159 const GURL
& manifest_url
, int64 group_id
, int64 response_id
) {
160 if (simulated_reader_
)
161 return simulated_reader_
.release();
162 return new AppCacheResponseReader(response_id
, group_id
, disk_cache());
165 AppCacheResponseWriter
* MockAppCacheStorage::CreateResponseWriter(
166 const GURL
& manifest_url
, int64 group_id
) {
167 return new AppCacheResponseWriter(NewResponseId(), group_id
, disk_cache());
170 AppCacheResponseMetadataWriter
*
171 MockAppCacheStorage::CreateResponseMetadataWriter(int64 group_id
,
173 return new AppCacheResponseMetadataWriter(response_id
, group_id
,
177 void MockAppCacheStorage::DoomResponses(
178 const GURL
& manifest_url
, const std::vector
<int64
>& response_ids
) {
179 DeleteResponses(manifest_url
, response_ids
);
182 void MockAppCacheStorage::DeleteResponses(
183 const GURL
& manifest_url
, const std::vector
<int64
>& response_ids
) {
184 // We don't bother with actually removing responses from the disk-cache,
185 // just keep track of which ids have been doomed or deleted
186 std::vector
<int64
>::const_iterator it
= response_ids
.begin();
187 while (it
!= response_ids
.end()) {
188 doomed_response_ids_
.insert(*it
);
193 void MockAppCacheStorage::ProcessGetAllInfo(
194 scoped_refptr
<DelegateReference
> delegate_ref
) {
195 if (delegate_ref
->delegate
)
196 delegate_ref
->delegate
->OnAllInfo(simulated_appcache_info_
.get());
199 void MockAppCacheStorage::ProcessLoadCache(
200 int64 id
, scoped_refptr
<DelegateReference
> delegate_ref
) {
201 AppCache
* cache
= working_set_
.GetCache(id
);
202 if (delegate_ref
->delegate
)
203 delegate_ref
->delegate
->OnCacheLoaded(cache
, id
);
206 void MockAppCacheStorage::ProcessLoadOrCreateGroup(
207 const GURL
& manifest_url
, scoped_refptr
<DelegateReference
> delegate_ref
) {
208 scoped_refptr
<AppCacheGroup
> group(working_set_
.GetGroup(manifest_url
));
210 // Newly created groups are not put in the stored_groups collection
211 // until StoreGroupAndNewestCache is called.
213 group
= new AppCacheGroup(service_
->storage(), manifest_url
, NewGroupId());
215 if (delegate_ref
->delegate
)
216 delegate_ref
->delegate
->OnGroupLoaded(group
.get(), manifest_url
);
219 void MockAppCacheStorage::ProcessStoreGroupAndNewestCache(
220 scoped_refptr
<AppCacheGroup
> group
,
221 scoped_refptr
<AppCache
> newest_cache
,
222 scoped_refptr
<DelegateReference
> delegate_ref
) {
223 Delegate
* delegate
= delegate_ref
->delegate
;
224 if (simulate_store_group_and_newest_cache_failure_
) {
226 delegate
->OnGroupAndNewestCacheStored(
227 group
.get(), newest_cache
.get(), false, false);
231 AddStoredGroup(group
.get());
232 if (newest_cache
.get() != group
->newest_complete_cache()) {
233 newest_cache
->set_complete(true);
234 group
->AddCache(newest_cache
.get());
235 AddStoredCache(newest_cache
.get());
237 // Copy the collection prior to removal, on final release
238 // of a cache the group's collection will change.
239 AppCacheGroup::Caches copy
= group
->old_caches();
240 RemoveStoredCaches(copy
);
244 delegate
->OnGroupAndNewestCacheStored(
245 group
.get(), newest_cache
.get(), true, false);
250 struct FoundCandidate
{
251 GURL namespace_entry_url
;
256 bool is_cache_in_use
;
259 : cache_id(kAppCacheNoCacheId
), group_id(0), is_cache_in_use(false) {}
262 void MaybeTakeNewNamespaceEntry(
263 AppCacheNamespaceType namespace_type
,
264 const AppCacheEntry
&entry
,
265 const GURL
& namespace_url
,
266 bool cache_is_in_use
,
267 FoundCandidate
* best_candidate
,
268 GURL
* best_candidate_namespace
,
270 AppCacheGroup
* group
) {
271 DCHECK(entry
.has_response_id());
273 bool take_new_entry
= true;
275 // Does the new candidate entry trump our current best candidate?
276 if (best_candidate
->entry
.has_response_id()) {
277 // Longer namespace prefix matches win.
278 size_t candidate_length
=
279 namespace_url
.spec().length();
281 best_candidate_namespace
->spec().length();
283 if (candidate_length
> best_length
) {
284 take_new_entry
= true;
285 } else if (candidate_length
== best_length
&&
286 cache_is_in_use
&& !best_candidate
->is_cache_in_use
) {
287 take_new_entry
= true;
289 take_new_entry
= false;
293 if (take_new_entry
) {
294 if (namespace_type
== APPCACHE_FALLBACK_NAMESPACE
) {
295 best_candidate
->namespace_entry_url
=
296 cache
->GetFallbackEntryUrl(namespace_url
);
298 best_candidate
->namespace_entry_url
=
299 cache
->GetInterceptEntryUrl(namespace_url
);
301 best_candidate
->entry
= entry
;
302 best_candidate
->cache_id
= cache
->cache_id();
303 best_candidate
->group_id
= group
->group_id();
304 best_candidate
->manifest_url
= group
->manifest_url();
305 best_candidate
->is_cache_in_use
= cache_is_in_use
;
306 *best_candidate_namespace
= namespace_url
;
311 void MockAppCacheStorage::ProcessFindResponseForMainRequest(
312 const GURL
& url
, scoped_refptr
<DelegateReference
> delegate_ref
) {
313 if (simulate_find_main_resource_
) {
314 simulate_find_main_resource_
= false;
315 if (delegate_ref
->delegate
) {
316 delegate_ref
->delegate
->OnMainResponseFound(
317 url
, simulated_found_entry_
,
318 simulated_found_fallback_url_
, simulated_found_fallback_entry_
,
319 simulated_found_cache_id_
, simulated_found_group_id_
,
320 simulated_found_manifest_url_
);
325 // This call has no persistent side effects, if the delegate has gone
326 // away, we can just bail out early.
327 if (!delegate_ref
->delegate
)
330 // TODO(michaeln): The heuristics around choosing amoungst
331 // multiple candidates is under specified, and just plain
332 // not fully understood. Refine these over time. In particular,
333 // * prefer candidates from newer caches
334 // * take into account the cache associated with the document
335 // that initiated the navigation
336 // * take into account the cache associated with the document
337 // currently residing in the frame being navigated
338 FoundCandidate found_candidate
;
339 GURL found_intercept_candidate_namespace
;
340 FoundCandidate found_fallback_candidate
;
341 GURL found_fallback_candidate_namespace
;
343 for (StoredGroupMap::const_iterator it
= stored_groups_
.begin();
344 it
!= stored_groups_
.end(); ++it
) {
345 AppCacheGroup
* group
= it
->second
.get();
346 AppCache
* cache
= group
->newest_complete_cache();
347 if (group
->is_obsolete() || !cache
||
348 (url
.GetOrigin() != group
->manifest_url().GetOrigin())) {
352 AppCacheEntry found_entry
;
353 AppCacheEntry found_fallback_entry
;
354 GURL found_intercept_namespace
;
355 GURL found_fallback_namespace
;
356 bool ignore_found_network_namespace
= false;
357 bool found
= cache
->FindResponseForRequest(
358 url
, &found_entry
, &found_intercept_namespace
,
359 &found_fallback_entry
, &found_fallback_namespace
,
360 &ignore_found_network_namespace
);
362 // 6.11.1 Navigating across documents, Step 10.
363 // Network namespacing doesn't apply to main resource loads,
364 // and foreign entries are excluded.
365 if (!found
|| ignore_found_network_namespace
||
366 (found_entry
.has_response_id() && found_entry
.IsForeign()) ||
367 (found_fallback_entry
.has_response_id() &&
368 found_fallback_entry
.IsForeign())) {
372 // We have a bias for hits from caches that are in use.
373 bool is_in_use
= IsCacheStored(cache
) && !cache
->HasOneRef();
375 if (found_entry
.has_response_id() &&
376 found_intercept_namespace
.is_empty()) {
377 found_candidate
.namespace_entry_url
= GURL();
378 found_candidate
.entry
= found_entry
;
379 found_candidate
.cache_id
= cache
->cache_id();
380 found_candidate
.group_id
= group
->group_id();
381 found_candidate
.manifest_url
= group
->manifest_url();
382 found_candidate
.is_cache_in_use
= is_in_use
;
384 break; // We break out of the loop with this direct hit.
385 } else if (found_entry
.has_response_id() &&
386 !found_intercept_namespace
.is_empty()) {
387 MaybeTakeNewNamespaceEntry(
388 APPCACHE_INTERCEPT_NAMESPACE
,
389 found_entry
, found_intercept_namespace
, is_in_use
,
390 &found_candidate
, &found_intercept_candidate_namespace
,
393 DCHECK(found_fallback_entry
.has_response_id());
394 MaybeTakeNewNamespaceEntry(
395 APPCACHE_FALLBACK_NAMESPACE
,
396 found_fallback_entry
, found_fallback_namespace
, is_in_use
,
397 &found_fallback_candidate
, &found_fallback_candidate_namespace
,
402 // Found a direct hit or an intercept namespace hit.
403 if (found_candidate
.entry
.has_response_id()) {
404 delegate_ref
->delegate
->OnMainResponseFound(
405 url
, found_candidate
.entry
, found_candidate
.namespace_entry_url
,
406 AppCacheEntry(), found_candidate
.cache_id
, found_candidate
.group_id
,
407 found_candidate
.manifest_url
);
411 // Found a fallback namespace.
412 if (found_fallback_candidate
.entry
.has_response_id()) {
413 delegate_ref
->delegate
->OnMainResponseFound(
414 url
, AppCacheEntry(),
415 found_fallback_candidate
.namespace_entry_url
,
416 found_fallback_candidate
.entry
,
417 found_fallback_candidate
.cache_id
,
418 found_fallback_candidate
.group_id
,
419 found_fallback_candidate
.manifest_url
);
423 // Didn't find anything.
424 delegate_ref
->delegate
->OnMainResponseFound(
425 url
, AppCacheEntry(), GURL(), AppCacheEntry(), kAppCacheNoCacheId
, 0,
429 void MockAppCacheStorage::ProcessMakeGroupObsolete(
430 scoped_refptr
<AppCacheGroup
> group
,
431 scoped_refptr
<DelegateReference
> delegate_ref
,
433 if (simulate_make_group_obsolete_failure_
) {
434 if (delegate_ref
->delegate
)
435 delegate_ref
->delegate
->OnGroupMadeObsolete(
436 group
.get(), false, response_code
);
440 RemoveStoredGroup(group
.get());
441 if (group
->newest_complete_cache())
442 RemoveStoredCache(group
->newest_complete_cache());
444 // Copy the collection prior to removal, on final release
445 // of a cache the group's collection will change.
446 AppCacheGroup::Caches copy
= group
->old_caches();
447 RemoveStoredCaches(copy
);
449 group
->set_obsolete(true);
451 // Also remove from the working set, caches for an 'obsolete' group
452 // may linger in use, but the group itself cannot be looked up by
453 // 'manifest_url' in the working set any longer.
454 working_set()->RemoveGroup(group
.get());
456 if (delegate_ref
->delegate
)
457 delegate_ref
->delegate
->OnGroupMadeObsolete(
458 group
.get(), true, response_code
);
461 void MockAppCacheStorage::ScheduleTask(const base::Closure
& task
) {
462 pending_tasks_
.push_back(task
);
463 base::ThreadTaskRunnerHandle::Get()->PostTask(
464 FROM_HERE
, base::Bind(&MockAppCacheStorage::RunOnePendingTask
,
465 weak_factory_
.GetWeakPtr()));
468 void MockAppCacheStorage::RunOnePendingTask() {
469 DCHECK(!pending_tasks_
.empty());
470 base::Closure task
= pending_tasks_
.front();
471 pending_tasks_
.pop_front();
475 void MockAppCacheStorage::AddStoredCache(AppCache
* cache
) {
476 int64 cache_id
= cache
->cache_id();
477 if (stored_caches_
.find(cache_id
) == stored_caches_
.end()) {
478 stored_caches_
.insert(
479 StoredCacheMap::value_type(cache_id
, make_scoped_refptr(cache
)));
483 void MockAppCacheStorage::RemoveStoredCache(AppCache
* cache
) {
484 // Do not remove from the working set, active caches are still usable
485 // and may be looked up by id until they fall out of use.
486 stored_caches_
.erase(cache
->cache_id());
489 void MockAppCacheStorage::RemoveStoredCaches(
490 const AppCacheGroup::Caches
& caches
) {
491 AppCacheGroup::Caches::const_iterator it
= caches
.begin();
492 while (it
!= caches
.end()) {
493 RemoveStoredCache(*it
);
498 void MockAppCacheStorage::AddStoredGroup(AppCacheGroup
* group
) {
499 const GURL
& url
= group
->manifest_url();
500 if (stored_groups_
.find(url
) == stored_groups_
.end()) {
501 stored_groups_
.insert(
502 StoredGroupMap::value_type(url
, make_scoped_refptr(group
)));
506 void MockAppCacheStorage::RemoveStoredGroup(AppCacheGroup
* group
) {
507 stored_groups_
.erase(group
->manifest_url());
510 bool MockAppCacheStorage::ShouldGroupLoadAppearAsync(
511 const AppCacheGroup
* group
) {
512 // We'll have to query the database to see if a group for the
513 // manifest_url exists on disk. So return true for async.
517 // Groups without a newest cache can't have been put to disk yet, so
518 // we can synchronously return a reference we have in the working set.
519 if (!group
->newest_complete_cache())
522 // The LoadGroup interface implies also loading the newest cache, so
523 // if loading the newest cache should appear async, so too must the
524 // loading of this group.
525 if (!ShouldCacheLoadAppearAsync(group
->newest_complete_cache()))
529 // If any of the old caches are "in use", then the group must also
530 // be memory resident and not require async loading.
531 const AppCacheGroup::Caches
& old_caches
= group
->old_caches();
532 AppCacheGroup::Caches::const_iterator it
= old_caches
.begin();
533 while (it
!= old_caches
.end()) {
534 // "in use" caches don't require async loading
535 if (!ShouldCacheLoadAppearAsync(*it
))
543 bool MockAppCacheStorage::ShouldCacheLoadAppearAsync(const AppCache
* cache
) {
547 // If the 'stored' ref is the only ref, real storage will have to load from
549 return IsCacheStored(cache
) && cache
->HasOneRef();
552 } // namespace content