Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / content / browser / appcache / mock_appcache_storage.cc
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.
4
5 #include "content/browser/appcache/mock_appcache_storage.h"
6
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 "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"
17
18 // This is a quick and easy 'mock' implementation of the storage interface
19 // that doesn't put anything to disk.
20 //
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.
27
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;
34
35 namespace content {
36
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),
46       weak_factory_(this) {
47   last_cache_id_ = 0;
48   last_group_id_ = 0;
49   last_response_id_ = 0;
50 }
51
52 MockAppCacheStorage::~MockAppCacheStorage() {
53 }
54
55 void MockAppCacheStorage::GetAllInfo(Delegate* delegate) {
56   ScheduleTask(
57       base::Bind(&MockAppCacheStorage::ProcessGetAllInfo,
58                  weak_factory_.GetWeakPtr(),
59                  make_scoped_refptr(GetOrCreateDelegateReference(delegate))));
60 }
61
62 void MockAppCacheStorage::LoadCache(int64 id, Delegate* delegate) {
63   DCHECK(delegate);
64   AppCache* cache = working_set_.GetCache(id);
65   if (ShouldCacheLoadAppearAsync(cache)) {
66     ScheduleTask(
67         base::Bind(&MockAppCacheStorage::ProcessLoadCache,
68                    weak_factory_.GetWeakPtr(), id,
69                    make_scoped_refptr(GetOrCreateDelegateReference(delegate))));
70     return;
71   }
72   ProcessLoadCache(id, GetOrCreateDelegateReference(delegate));
73 }
74
75 void MockAppCacheStorage::LoadOrCreateGroup(
76     const GURL& manifest_url, Delegate* delegate) {
77   DCHECK(delegate);
78   AppCacheGroup* group = working_set_.GetGroup(manifest_url);
79   if (ShouldGroupLoadAppearAsync(group)) {
80     ScheduleTask(
81         base::Bind(&MockAppCacheStorage::ProcessLoadOrCreateGroup,
82                    weak_factory_.GetWeakPtr(), manifest_url,
83                    make_scoped_refptr(GetOrCreateDelegateReference(delegate))));
84     return;
85   }
86   ProcessLoadOrCreateGroup(
87       manifest_url, GetOrCreateDelegateReference(delegate));
88 }
89
90 void MockAppCacheStorage::StoreGroupAndNewestCache(
91     AppCacheGroup* group, AppCache* newest_cache, Delegate* delegate) {
92   DCHECK(group && delegate && newest_cache);
93
94   // Always make this operation look async.
95   ScheduleTask(
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))));
100 }
101
102 void MockAppCacheStorage::FindResponseForMainRequest(
103     const GURL& url, const GURL& preferred_manifest_url, Delegate* delegate) {
104   DCHECK(delegate);
105
106   // Note: MockAppCacheStorage does not respect the preferred_manifest_url.
107
108   // Always make this operation look async.
109   ScheduleTask(
110       base::Bind(&MockAppCacheStorage::ProcessFindResponseForMainRequest,
111                  weak_factory_.GetWeakPtr(), url,
112                  make_scoped_refptr(GetOrCreateDelegateReference(delegate))));
113 }
114
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());
120
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;
127     return;
128   }
129
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);
136 }
137
138 void MockAppCacheStorage::MarkEntryAsForeign(
139     const GURL& entry_url, int64 cache_id) {
140   AppCache* cache = working_set_.GetCache(cache_id);
141   if (cache) {
142     AppCacheEntry* entry = cache->GetEntry(entry_url);
143     DCHECK(entry);
144     if (entry)
145       entry->add_types(AppCacheEntry::FOREIGN);
146   }
147 }
148
149 void MockAppCacheStorage::MakeGroupObsolete(AppCacheGroup* group,
150                                             Delegate* delegate,
151                                             int response_code) {
152   DCHECK(group && delegate);
153
154   // Always make this method look async.
155   ScheduleTask(
156       base::Bind(&MockAppCacheStorage::ProcessMakeGroupObsolete,
157                  weak_factory_.GetWeakPtr(),
158                  make_scoped_refptr(group),
159                  make_scoped_refptr(GetOrCreateDelegateReference(delegate)),
160                  response_code));
161 }
162
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());
168 }
169
170 AppCacheResponseWriter* MockAppCacheStorage::CreateResponseWriter(
171     const GURL& manifest_url, int64 group_id) {
172   return new AppCacheResponseWriter(NewResponseId(),  group_id, disk_cache());
173 }
174
175 void MockAppCacheStorage::DoomResponses(
176     const GURL& manifest_url, const std::vector<int64>& response_ids) {
177   DeleteResponses(manifest_url, response_ids);
178 }
179
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);
187     ++it;
188   }
189 }
190
191 void MockAppCacheStorage::ProcessGetAllInfo(
192     scoped_refptr<DelegateReference> delegate_ref) {
193   if (delegate_ref->delegate)
194     delegate_ref->delegate->OnAllInfo(simulated_appcache_info_.get());
195 }
196
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);
202 }
203
204 void MockAppCacheStorage::ProcessLoadOrCreateGroup(
205     const GURL& manifest_url, scoped_refptr<DelegateReference> delegate_ref) {
206   scoped_refptr<AppCacheGroup> group(working_set_.GetGroup(manifest_url));
207
208   // Newly created groups are not put in the stored_groups collection
209   // until StoreGroupAndNewestCache is called.
210   if (!group.get())
211     group = new AppCacheGroup(service_->storage(), manifest_url, NewGroupId());
212
213   if (delegate_ref->delegate)
214     delegate_ref->delegate->OnGroupLoaded(group.get(), manifest_url);
215 }
216
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_) {
223     if (delegate)
224       delegate->OnGroupAndNewestCacheStored(
225           group.get(), newest_cache.get(), false, false);
226     return;
227   }
228
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());
234
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);
239   }
240
241   if (delegate)
242     delegate->OnGroupAndNewestCacheStored(
243         group.get(), newest_cache.get(), true, false);
244 }
245
246 namespace {
247
248 struct FoundCandidate {
249   GURL namespace_entry_url;
250   AppCacheEntry entry;
251   int64 cache_id;
252   int64 group_id;
253   GURL manifest_url;
254   bool is_cache_in_use;
255
256   FoundCandidate()
257       : cache_id(kNoCacheId), group_id(0), is_cache_in_use(false) {}
258 };
259
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,
267     AppCache* cache,
268     AppCacheGroup* group) {
269   DCHECK(entry.has_response_id());
270
271   bool take_new_entry = true;
272
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();
278     size_t best_length =
279         best_candidate_namespace->spec().length();
280
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;
286     } else {
287       take_new_entry = false;
288     }
289   }
290
291   if (take_new_entry) {
292     if (namespace_type == FALLBACK_NAMESPACE) {
293       best_candidate->namespace_entry_url =
294           cache->GetFallbackEntryUrl(namespace_url);
295     } else {
296       best_candidate->namespace_entry_url =
297           cache->GetInterceptEntryUrl(namespace_url);
298     }
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;
305   }
306 }
307 }  // namespace
308
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_);
319     }
320     return;
321   }
322
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)
326     return;
327
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;
340
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())) {
347       continue;
348     }
349
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);
359
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())) {
367       continue;
368     }
369
370     // We have a bias for hits from caches that are in use.
371     bool is_in_use = IsCacheStored(cache) && !cache->HasOneRef();
372
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;
381       if (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(
386           INTERCEPT_NAMESPACE,
387           found_entry, found_intercept_namespace, is_in_use,
388           &found_candidate, &found_intercept_candidate_namespace,
389           cache, group);
390     } else {
391       DCHECK(found_fallback_entry.has_response_id());
392       MaybeTakeNewNamespaceEntry(
393           FALLBACK_NAMESPACE,
394           found_fallback_entry, found_fallback_namespace, is_in_use,
395           &found_fallback_candidate, &found_fallback_candidate_namespace,
396           cache, group);
397     }
398   }
399
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);
406     return;
407   }
408
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);
418     return;
419   }
420
421   // Didn't find anything.
422   delegate_ref->delegate->OnMainResponseFound(
423       url, AppCacheEntry(), GURL(), AppCacheEntry(), kNoCacheId, 0, GURL());
424 }
425
426 void MockAppCacheStorage::ProcessMakeGroupObsolete(
427     scoped_refptr<AppCacheGroup> group,
428     scoped_refptr<DelegateReference> delegate_ref,
429     int response_code) {
430   if (simulate_make_group_obsolete_failure_) {
431     if (delegate_ref->delegate)
432       delegate_ref->delegate->OnGroupMadeObsolete(
433           group.get(), false, response_code);
434     return;
435   }
436
437   RemoveStoredGroup(group.get());
438   if (group->newest_complete_cache())
439     RemoveStoredCache(group->newest_complete_cache());
440
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);
445
446   group->set_obsolete(true);
447
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());
452
453   if (delegate_ref->delegate)
454     delegate_ref->delegate->OnGroupMadeObsolete(
455         group.get(), true, response_code);
456 }
457
458 void MockAppCacheStorage::ScheduleTask(const base::Closure& task) {
459   pending_tasks_.push_back(task);
460   base::MessageLoop::current()->PostTask(
461       FROM_HERE,
462       base::Bind(&MockAppCacheStorage::RunOnePendingTask,
463                  weak_factory_.GetWeakPtr()));
464 }
465
466 void MockAppCacheStorage::RunOnePendingTask() {
467   DCHECK(!pending_tasks_.empty());
468   base::Closure task = pending_tasks_.front();
469   pending_tasks_.pop_front();
470   task.Run();
471 }
472
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)));
478   }
479 }
480
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());
485 }
486
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);
492     ++it;
493   }
494 }
495
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)));
501   }
502 }
503
504 void MockAppCacheStorage::RemoveStoredGroup(AppCacheGroup* group) {
505   stored_groups_.erase(group->manifest_url());
506 }
507
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.
512   if (!group)
513     return true;
514
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())
518     return false;
519
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()))
524     return false;
525
526
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))
534       return false;
535     ++it;
536   }
537
538   return true;
539 }
540
541 bool MockAppCacheStorage::ShouldCacheLoadAppearAsync(const AppCache* cache) {
542   if (!cache)
543     return true;
544
545   // If the 'stored' ref is the only ref, real storage will have to load from
546   // the database.
547   return IsCacheStored(cache) && cache->HasOneRef();
548 }
549
550 }  // namespace content