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.
5 #include "webkit/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
30 MockAppCacheStorage::MockAppCacheStorage(AppCacheService
* service
)
31 : AppCacheStorage(service
),
33 simulate_make_group_obsolete_failure_(false),
34 simulate_store_group_and_newest_cache_failure_(false),
35 simulate_find_main_resource_(false),
36 simulate_find_sub_resource_(false),
37 simulated_found_cache_id_(kNoCacheId
),
38 simulated_found_group_id_(0),
39 simulated_found_network_namespace_(false) {
42 last_response_id_
= 0;
45 MockAppCacheStorage::~MockAppCacheStorage() {
48 void MockAppCacheStorage::GetAllInfo(Delegate
* delegate
) {
50 base::Bind(&MockAppCacheStorage::ProcessGetAllInfo
,
51 weak_factory_
.GetWeakPtr(),
52 make_scoped_refptr(GetOrCreateDelegateReference(delegate
))));
55 void MockAppCacheStorage::LoadCache(int64 id
, Delegate
* delegate
) {
57 AppCache
* cache
= working_set_
.GetCache(id
);
58 if (ShouldCacheLoadAppearAsync(cache
)) {
60 base::Bind(&MockAppCacheStorage::ProcessLoadCache
,
61 weak_factory_
.GetWeakPtr(), id
,
62 make_scoped_refptr(GetOrCreateDelegateReference(delegate
))));
65 ProcessLoadCache(id
, GetOrCreateDelegateReference(delegate
));
68 void MockAppCacheStorage::LoadOrCreateGroup(
69 const GURL
& manifest_url
, Delegate
* delegate
) {
71 AppCacheGroup
* group
= working_set_
.GetGroup(manifest_url
);
72 if (ShouldGroupLoadAppearAsync(group
)) {
74 base::Bind(&MockAppCacheStorage::ProcessLoadOrCreateGroup
,
75 weak_factory_
.GetWeakPtr(), manifest_url
,
76 make_scoped_refptr(GetOrCreateDelegateReference(delegate
))));
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.
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
) {
99 // Note: MockAppCacheStorage does not respect the preferred_manifest_url.
101 // Always make this operation look async.
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;
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
);
135 AppCacheEntry
* entry
= cache
->GetEntry(entry_url
);
138 entry
->add_types(AppCacheEntry::FOREIGN
);
142 void MockAppCacheStorage::MakeGroupObsolete(
143 AppCacheGroup
* group
, Delegate
* delegate
) {
144 DCHECK(group
&& delegate
);
146 // Always make this method look async.
148 base::Bind(&MockAppCacheStorage::ProcessMakeGroupObsolete
,
149 weak_factory_
.GetWeakPtr(), make_scoped_refptr(group
),
150 make_scoped_refptr(GetOrCreateDelegateReference(delegate
))));
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());
160 AppCacheResponseWriter
* MockAppCacheStorage::CreateResponseWriter(
161 const GURL
& manifest_url
, int64 group_id
) {
162 return new AppCacheResponseWriter(NewResponseId(), group_id
, disk_cache());
165 void MockAppCacheStorage::DoomResponses(
166 const GURL
& manifest_url
, const std::vector
<int64
>& response_ids
) {
167 DeleteResponses(manifest_url
, 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
);
181 void MockAppCacheStorage::ProcessGetAllInfo(
182 scoped_refptr
<DelegateReference
> delegate_ref
) {
183 if (delegate_ref
->delegate
)
184 delegate_ref
->delegate
->OnAllInfo(simulated_appcache_info_
.get());
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
);
194 void MockAppCacheStorage::ProcessLoadOrCreateGroup(
195 const GURL
& manifest_url
, scoped_refptr
<DelegateReference
> delegate_ref
) {
196 scoped_refptr
<AppCacheGroup
> group(working_set_
.GetGroup(manifest_url
));
198 // Newly created groups are not put in the stored_groups collection
199 // until StoreGroupAndNewestCache is called.
201 group
= new AppCacheGroup(service_
->storage(), manifest_url
, NewGroupId());
203 if (delegate_ref
->delegate
)
204 delegate_ref
->delegate
->OnGroupLoaded(group
.get(), manifest_url
);
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_
) {
214 delegate
->OnGroupAndNewestCacheStored(
215 group
.get(), newest_cache
.get(), false, false);
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());
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
);
232 delegate
->OnGroupAndNewestCacheStored(
233 group
.get(), newest_cache
.get(), true, false);
238 struct FoundCandidate
{
239 GURL namespace_entry_url
;
244 bool is_cache_in_use
;
247 : cache_id(kNoCacheId
), group_id(0), is_cache_in_use(false) {}
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
,
258 AppCacheGroup
* group
) {
259 DCHECK(entry
.has_response_id());
261 bool take_new_entry
= true;
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();
269 best_candidate_namespace
->spec().length();
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;
277 take_new_entry
= false;
281 if (take_new_entry
) {
282 if (namespace_type
== FALLBACK_NAMESPACE
) {
283 best_candidate
->namespace_entry_url
=
284 cache
->GetFallbackEntryUrl(namespace_url
);
286 best_candidate
->namespace_entry_url
=
287 cache
->GetInterceptEntryUrl(namespace_url
);
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
;
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_
);
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
)
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
;
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())) {
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
);
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())) {
360 // We have a bias for hits from caches that are in use.
361 bool is_in_use
= IsCacheStored(cache
) && !cache
->HasOneRef();
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
;
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(
377 found_entry
, found_intercept_namespace
, is_in_use
,
378 &found_candidate
, &found_intercept_candidate_namespace
,
381 DCHECK(found_fallback_entry
.has_response_id());
382 MaybeTakeNewNamespaceEntry(
384 found_fallback_entry
, found_fallback_namespace
, is_in_use
,
385 &found_fallback_candidate
, &found_fallback_candidate_namespace
,
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
);
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
);
411 // Didn't find anything.
412 delegate_ref
->delegate
->OnMainResponseFound(
413 url
, AppCacheEntry(), GURL(), AppCacheEntry(), kNoCacheId
, 0, GURL());
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);
425 RemoveStoredGroup(group
.get());
426 if (group
->newest_complete_cache())
427 RemoveStoredCache(group
->newest_complete_cache());
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
);
434 group
->set_obsolete(true);
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());
441 if (delegate_ref
->delegate
)
442 delegate_ref
->delegate
->OnGroupMadeObsolete(group
.get(), true);
445 void MockAppCacheStorage::ScheduleTask(const base::Closure
& task
) {
446 pending_tasks_
.push_back(task
);
447 base::MessageLoop::current()->PostTask(
449 base::Bind(&MockAppCacheStorage::RunOnePendingTask
,
450 weak_factory_
.GetWeakPtr()));
453 void MockAppCacheStorage::RunOnePendingTask() {
454 DCHECK(!pending_tasks_
.empty());
455 base::Closure task
= pending_tasks_
.front();
456 pending_tasks_
.pop_front();
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
)));
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());
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
);
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
)));
491 void MockAppCacheStorage::RemoveStoredGroup(AppCacheGroup
* group
) {
492 stored_groups_
.erase(group
->manifest_url());
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.
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())
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()))
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
))
528 bool MockAppCacheStorage::ShouldCacheLoadAppearAsync(const AppCache
* cache
) {
532 // If the 'stored' ref is the only ref, real storage will have to load from
534 return IsCacheStored(cache
) && cache
->HasOneRef();
537 } // namespace appcache