diff options
author | pilgrim@chromium.org <pilgrim@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-06-25 20:57:55 +0000 |
---|---|---|
committer | pilgrim@chromium.org <pilgrim@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-06-25 20:57:55 +0000 |
commit | 98d6d4562142f682d3bc8419c0f29a4f7fd0f263 (patch) | |
tree | 005b061e143cefc1d2c890a700b28edb57b8573d /content | |
parent | 0282b03c86d52533c58af2c6e8de4c7bc7a89155 (diff) | |
download | chromium_src-98d6d4562142f682d3bc8419c0f29a4f7fd0f263.zip chromium_src-98d6d4562142f682d3bc8419c0f29a4f7fd0f263.tar.gz chromium_src-98d6d4562142f682d3bc8419c0f29a4f7fd0f263.tar.bz2 |
Move all remaining appcache-related code to content namespace
This introduces NO FUNCTIONAL CHANGES. It is strictly a file move and namespace change.
BUG=338338
R=michaeln@chromium.org
TBR=cevans, darin
Review URL: https://codereview.chromium.org/344493002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@279808 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'content')
106 files changed, 13431 insertions, 610 deletions
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn index 7ec025c..c3e3226 100644 --- a/content/browser/BUILD.gn +++ b/content/browser/BUILD.gn @@ -6,6 +6,12 @@ import("//build/config/features.gni") import("//build/config/ui.gni") import("//content/browser/browser.gni") +config("storage_config") { + if (is_android) { + defines = [ "APPCACHE_USE_SIMPLE_CACHE" ] + } +} + source_set("browser") { # Only targets in the content tree can depend directly on this target. visibility = [ "//content/*" ] diff --git a/content/browser/appcache/DEPS b/content/browser/appcache/DEPS new file mode 100644 index 0000000..8608d5f --- /dev/null +++ b/content/browser/appcache/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+net/disk_cache", +] diff --git a/content/browser/appcache/appcache.cc b/content/browser/appcache/appcache.cc new file mode 100644 index 0000000..7b23f64 --- /dev/null +++ b/content/browser/appcache/appcache.cc @@ -0,0 +1,326 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/appcache/appcache.h" + +#include <algorithm> + +#include "base/logging.h" +#include "base/stl_util.h" +#include "content/browser/appcache/appcache_executable_handler.h" +#include "content/browser/appcache/appcache_group.h" +#include "content/browser/appcache/appcache_host.h" +#include "content/browser/appcache/appcache_storage.h" +#include "content/common/appcache_interfaces.h" + +namespace content { + +AppCache::AppCache(AppCacheStorage* storage, int64 cache_id) + : cache_id_(cache_id), + owning_group_(NULL), + online_whitelist_all_(false), + is_complete_(false), + cache_size_(0), + storage_(storage) { + storage_->working_set()->AddCache(this); +} + +AppCache::~AppCache() { + DCHECK(associated_hosts_.empty()); + if (owning_group_.get()) { + DCHECK(is_complete_); + owning_group_->RemoveCache(this); + } + DCHECK(!owning_group_.get()); + storage_->working_set()->RemoveCache(this); + STLDeleteContainerPairSecondPointers( + executable_handlers_.begin(), executable_handlers_.end()); +} + +void AppCache::UnassociateHost(AppCacheHost* host) { + associated_hosts_.erase(host); +} + +void AppCache::AddEntry(const GURL& url, const AppCacheEntry& entry) { + DCHECK(entries_.find(url) == entries_.end()); + entries_.insert(EntryMap::value_type(url, entry)); + cache_size_ += entry.response_size(); +} + +bool AppCache::AddOrModifyEntry(const GURL& url, const AppCacheEntry& entry) { + std::pair<EntryMap::iterator, bool> ret = + entries_.insert(EntryMap::value_type(url, entry)); + + // Entry already exists. Merge the types of the new and existing entries. + if (!ret.second) + ret.first->second.add_types(entry.types()); + else + cache_size_ += entry.response_size(); // New entry. Add to cache size. + return ret.second; +} + +void AppCache::RemoveEntry(const GURL& url) { + EntryMap::iterator found = entries_.find(url); + DCHECK(found != entries_.end()); + cache_size_ -= found->second.response_size(); + entries_.erase(found); +} + +AppCacheEntry* AppCache::GetEntry(const GURL& url) { + EntryMap::iterator it = entries_.find(url); + return (it != entries_.end()) ? &(it->second) : NULL; +} + +const AppCacheEntry* AppCache::GetEntryAndUrlWithResponseId( + int64 response_id, GURL* optional_url_out) { + for (EntryMap::const_iterator iter = entries_.begin(); + iter != entries_.end(); ++iter) { + if (iter->second.response_id() == response_id) { + if (optional_url_out) + *optional_url_out = iter->first; + return &iter->second; + } + } + return NULL; +} + +AppCacheExecutableHandler* AppCache::GetExecutableHandler(int64 response_id) { + HandlerMap::const_iterator found = executable_handlers_.find(response_id); + if (found != executable_handlers_.end()) + return found->second; + return NULL; +} + +AppCacheExecutableHandler* AppCache::GetOrCreateExecutableHandler( + int64 response_id, net::IOBuffer* handler_source) { + AppCacheExecutableHandler* handler = GetExecutableHandler(response_id); + if (handler) + return handler; + + GURL handler_url; + const AppCacheEntry* entry = GetEntryAndUrlWithResponseId( + response_id, &handler_url); + if (!entry || !entry->IsExecutable()) + return NULL; + + DCHECK(storage_->service()->handler_factory()); + scoped_ptr<AppCacheExecutableHandler> own_ptr = + storage_->service()->handler_factory()-> + CreateHandler(handler_url, handler_source); + handler = own_ptr.release(); + if (!handler) + return NULL; + executable_handlers_[response_id] = handler; + return handler; +} + +GURL AppCache::GetNamespaceEntryUrl(const AppCacheNamespaceVector& namespaces, + const GURL& namespace_url) const { + size_t count = namespaces.size(); + for (size_t i = 0; i < count; ++i) { + if (namespaces[i].namespace_url == namespace_url) + return namespaces[i].target_url; + } + NOTREACHED(); + return GURL(); +} + +namespace { +bool SortNamespacesByLength( + const AppCacheNamespace& lhs, const AppCacheNamespace& rhs) { + return lhs.namespace_url.spec().length() > rhs.namespace_url.spec().length(); +} +} + +void AppCache::InitializeWithManifest(Manifest* manifest) { + DCHECK(manifest); + intercept_namespaces_.swap(manifest->intercept_namespaces); + fallback_namespaces_.swap(manifest->fallback_namespaces); + online_whitelist_namespaces_.swap(manifest->online_whitelist_namespaces); + online_whitelist_all_ = manifest->online_whitelist_all; + + // Sort the namespaces by url string length, longest to shortest, + // since longer matches trump when matching a url to a namespace. + std::sort(intercept_namespaces_.begin(), intercept_namespaces_.end(), + SortNamespacesByLength); + std::sort(fallback_namespaces_.begin(), fallback_namespaces_.end(), + SortNamespacesByLength); +} + +void AppCache::InitializeWithDatabaseRecords( + const AppCacheDatabase::CacheRecord& cache_record, + const std::vector<AppCacheDatabase::EntryRecord>& entries, + const std::vector<AppCacheDatabase::NamespaceRecord>& intercepts, + const std::vector<AppCacheDatabase::NamespaceRecord>& fallbacks, + const std::vector<AppCacheDatabase::OnlineWhiteListRecord>& whitelists) { + DCHECK(cache_id_ == cache_record.cache_id); + online_whitelist_all_ = cache_record.online_wildcard; + update_time_ = cache_record.update_time; + + for (size_t i = 0; i < entries.size(); ++i) { + const AppCacheDatabase::EntryRecord& entry = entries.at(i); + AddEntry(entry.url, AppCacheEntry(entry.flags, entry.response_id, + entry.response_size)); + } + DCHECK(cache_size_ == cache_record.cache_size); + + for (size_t i = 0; i < intercepts.size(); ++i) + intercept_namespaces_.push_back(intercepts.at(i).namespace_); + + for (size_t i = 0; i < fallbacks.size(); ++i) + fallback_namespaces_.push_back(fallbacks.at(i).namespace_); + + // Sort the fallback namespaces by url string length, longest to shortest, + // since longer matches trump when matching a url to a namespace. + std::sort(intercept_namespaces_.begin(), intercept_namespaces_.end(), + SortNamespacesByLength); + std::sort(fallback_namespaces_.begin(), fallback_namespaces_.end(), + SortNamespacesByLength); + + for (size_t i = 0; i < whitelists.size(); ++i) { + const AppCacheDatabase::OnlineWhiteListRecord& record = whitelists.at(i); + online_whitelist_namespaces_.push_back( + AppCacheNamespace(APPCACHE_NETWORK_NAMESPACE, + record.namespace_url, + GURL(), + record.is_pattern)); + } +} + +void AppCache::ToDatabaseRecords( + const AppCacheGroup* group, + AppCacheDatabase::CacheRecord* cache_record, + std::vector<AppCacheDatabase::EntryRecord>* entries, + std::vector<AppCacheDatabase::NamespaceRecord>* intercepts, + std::vector<AppCacheDatabase::NamespaceRecord>* fallbacks, + std::vector<AppCacheDatabase::OnlineWhiteListRecord>* whitelists) { + DCHECK(group && cache_record && entries && fallbacks && whitelists); + DCHECK(entries->empty() && fallbacks->empty() && whitelists->empty()); + + cache_record->cache_id = cache_id_; + cache_record->group_id = group->group_id(); + cache_record->online_wildcard = online_whitelist_all_; + cache_record->update_time = update_time_; + cache_record->cache_size = 0; + + for (EntryMap::const_iterator iter = entries_.begin(); + iter != entries_.end(); ++iter) { + entries->push_back(AppCacheDatabase::EntryRecord()); + AppCacheDatabase::EntryRecord& record = entries->back(); + record.url = iter->first; + record.cache_id = cache_id_; + record.flags = iter->second.types(); + record.response_id = iter->second.response_id(); + record.response_size = iter->second.response_size(); + cache_record->cache_size += record.response_size; + } + + GURL origin = group->manifest_url().GetOrigin(); + + for (size_t i = 0; i < intercept_namespaces_.size(); ++i) { + intercepts->push_back(AppCacheDatabase::NamespaceRecord()); + AppCacheDatabase::NamespaceRecord& record = intercepts->back(); + record.cache_id = cache_id_; + record.origin = origin; + record.namespace_ = intercept_namespaces_[i]; + } + + for (size_t i = 0; i < fallback_namespaces_.size(); ++i) { + fallbacks->push_back(AppCacheDatabase::NamespaceRecord()); + AppCacheDatabase::NamespaceRecord& record = fallbacks->back(); + record.cache_id = cache_id_; + record.origin = origin; + record.namespace_ = fallback_namespaces_[i]; + } + + for (size_t i = 0; i < online_whitelist_namespaces_.size(); ++i) { + whitelists->push_back(AppCacheDatabase::OnlineWhiteListRecord()); + AppCacheDatabase::OnlineWhiteListRecord& record = whitelists->back(); + record.cache_id = cache_id_; + record.namespace_url = online_whitelist_namespaces_[i].namespace_url; + record.is_pattern = online_whitelist_namespaces_[i].is_pattern; + } +} + +bool AppCache::FindResponseForRequest(const GURL& url, + AppCacheEntry* found_entry, GURL* found_intercept_namespace, + AppCacheEntry* found_fallback_entry, GURL* found_fallback_namespace, + bool* found_network_namespace) { + // Ignore fragments when looking up URL in the cache. + GURL url_no_ref; + if (url.has_ref()) { + GURL::Replacements replacements; + replacements.ClearRef(); + url_no_ref = url.ReplaceComponents(replacements); + } else { + url_no_ref = url; + } + + // 6.6.6 Changes to the networking model + + AppCacheEntry* entry = GetEntry(url_no_ref); + if (entry) { + *found_entry = *entry; + return true; + } + + if ((*found_network_namespace = IsInNetworkNamespace(url_no_ref))) + return true; + + const AppCacheNamespace* intercept_namespace = + FindInterceptNamespace(url_no_ref); + if (intercept_namespace) { + entry = GetEntry(intercept_namespace->target_url); + DCHECK(entry); + *found_entry = *entry; + *found_intercept_namespace = intercept_namespace->namespace_url; + return true; + } + + const AppCacheNamespace* fallback_namespace = + FindFallbackNamespace(url_no_ref); + if (fallback_namespace) { + entry = GetEntry(fallback_namespace->target_url); + DCHECK(entry); + *found_fallback_entry = *entry; + *found_fallback_namespace = fallback_namespace->namespace_url; + return true; + } + + *found_network_namespace = online_whitelist_all_; + return *found_network_namespace; +} + + +void AppCache::ToResourceInfoVector(AppCacheResourceInfoVector* infos) const { + DCHECK(infos && infos->empty()); + for (EntryMap::const_iterator iter = entries_.begin(); + iter != entries_.end(); ++iter) { + infos->push_back(AppCacheResourceInfo()); + AppCacheResourceInfo& info = infos->back(); + info.url = iter->first; + info.is_master = iter->second.IsMaster(); + info.is_manifest = iter->second.IsManifest(); + info.is_intercept = iter->second.IsIntercept(); + info.is_fallback = iter->second.IsFallback(); + info.is_foreign = iter->second.IsForeign(); + info.is_explicit = iter->second.IsExplicit(); + info.size = iter->second.response_size(); + info.response_id = iter->second.response_id(); + } +} + +// static +const AppCacheNamespace* AppCache::FindNamespace( + const AppCacheNamespaceVector& namespaces, + const GURL& url) { + size_t count = namespaces.size(); + for (size_t i = 0; i < count; ++i) { + if (namespaces[i].IsMatch(url)) + return &namespaces[i]; + } + return NULL; +} + +} // namespace content diff --git a/content/browser/appcache/appcache.h b/content/browser/appcache/appcache.h new file mode 100644 index 0000000..d449139 --- /dev/null +++ b/content/browser/appcache/appcache.h @@ -0,0 +1,214 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_APPCACHE_APPCACHE_H_ +#define CONTENT_BROWSER_APPCACHE_APPCACHE_H_ + +#include <map> +#include <set> +#include <vector> + +#include "base/gtest_prod_util.h" +#include "base/memory/ref_counted.h" +#include "base/time/time.h" +#include "content/browser/appcache/appcache_database.h" +#include "content/browser/appcache/appcache_entry.h" +#include "content/browser/appcache/manifest_parser.h" +#include "content/common/content_export.h" +#include "url/gurl.h" + +namespace net { +class IOBuffer; +} + +namespace content { +FORWARD_DECLARE_TEST(AppCacheTest, InitializeWithManifest); +FORWARD_DECLARE_TEST(AppCacheTest, ToFromDatabaseRecords); +class AppCacheTest; +class AppCacheStorageImplTest; +class AppCacheUpdateJobTest; +} + +namespace content { + +class AppCacheExecutableHandler; +class AppCacheGroup; +class AppCacheHost; +class AppCacheStorage; + +// Set of cached resources for an application. A cache exists as long as a +// host is associated with it, the cache is in an appcache group or the +// cache is being created during an appcache upate. +class CONTENT_EXPORT AppCache + : public base::RefCounted<AppCache> { + public: + typedef std::map<GURL, AppCacheEntry> EntryMap; + typedef std::set<AppCacheHost*> AppCacheHosts; + + AppCache(AppCacheStorage* storage, int64 cache_id); + + int64 cache_id() const { return cache_id_; } + + AppCacheGroup* owning_group() const { return owning_group_.get(); } + + bool is_complete() const { return is_complete_; } + void set_complete(bool value) { is_complete_ = value; } + + // Adds a new entry. Entry must not already be in cache. + void AddEntry(const GURL& url, const AppCacheEntry& entry); + + // Adds a new entry or modifies an existing entry by merging the types + // of the new entry with the existing entry. Returns true if a new entry + // is added, false if the flags are merged into an existing entry. + bool AddOrModifyEntry(const GURL& url, const AppCacheEntry& entry); + + // Removes an entry from the EntryMap, the URL must be in the set. + void RemoveEntry(const GURL& url); + + // Do not store or delete the returned ptr, they're owned by 'this'. + AppCacheEntry* GetEntry(const GURL& url); + const AppCacheEntry* GetEntryWithResponseId(int64 response_id) { + return GetEntryAndUrlWithResponseId(response_id, NULL); + } + const AppCacheEntry* GetEntryAndUrlWithResponseId( + int64 response_id, GURL* optional_url); + const EntryMap& entries() const { return entries_; } + + // The AppCache owns the collection of executable handlers that have + // been started for this instance. The getter looks up an existing + // handler returning null if not found, the GetOrCreate method will + // cons one up if not found. + // Do not store the returned ptrs, they're owned by 'this'. + AppCacheExecutableHandler* GetExecutableHandler(int64 response_id); + AppCacheExecutableHandler* GetOrCreateExecutableHandler( + int64 response_id, net::IOBuffer* handler_source); + + // Returns the URL of the resource used as entry for 'namespace_url'. + GURL GetFallbackEntryUrl(const GURL& namespace_url) const { + return GetNamespaceEntryUrl(fallback_namespaces_, namespace_url); + } + GURL GetInterceptEntryUrl(const GURL& namespace_url) const { + return GetNamespaceEntryUrl(intercept_namespaces_, namespace_url); + } + + AppCacheHosts& associated_hosts() { return associated_hosts_; } + + bool IsNewerThan(AppCache* cache) const { + // TODO(michaeln): revisit, the system clock can be set + // back in time which would confuse this logic. + if (update_time_ > cache->update_time_) + return true; + + // Tie breaker. Newer caches have a larger cache ID. + if (update_time_ == cache->update_time_) + return cache_id_ > cache->cache_id_; + + return false; + } + + base::Time update_time() const { return update_time_; } + + int64 cache_size() const { return cache_size_; } + + void set_update_time(base::Time ticks) { update_time_ = ticks; } + + // Initializes the cache with information in the manifest. + // Do not use the manifest after this call. + void InitializeWithManifest(Manifest* manifest); + + // Initializes the cache with the information in the database records. + void InitializeWithDatabaseRecords( + const AppCacheDatabase::CacheRecord& cache_record, + const std::vector<AppCacheDatabase::EntryRecord>& entries, + const std::vector<AppCacheDatabase::NamespaceRecord>& intercepts, + const std::vector<AppCacheDatabase::NamespaceRecord>& fallbacks, + const std::vector<AppCacheDatabase::OnlineWhiteListRecord>& whitelists); + + // Returns the database records to be stored in the AppCacheDatabase + // to represent this cache. + void ToDatabaseRecords( + const AppCacheGroup* group, + AppCacheDatabase::CacheRecord* cache_record, + std::vector<AppCacheDatabase::EntryRecord>* entries, + std::vector<AppCacheDatabase::NamespaceRecord>* intercepts, + std::vector<AppCacheDatabase::NamespaceRecord>* fallbacks, + std::vector<AppCacheDatabase::OnlineWhiteListRecord>* whitelists); + + bool FindResponseForRequest(const GURL& url, + AppCacheEntry* found_entry, GURL* found_intercept_namespace, + AppCacheEntry* found_fallback_entry, GURL* found_fallback_namespace, + bool* found_network_namespace); + + // Populates the 'infos' vector with an element per entry in the appcache. + void ToResourceInfoVector(AppCacheResourceInfoVector* infos) const; + + static const AppCacheNamespace* FindNamespace( + const AppCacheNamespaceVector& namespaces, + const GURL& url); + + private: + friend class AppCacheGroup; + friend class AppCacheHost; + friend class content::AppCacheTest; + friend class content::AppCacheStorageImplTest; + friend class content::AppCacheUpdateJobTest; + friend class base::RefCounted<AppCache>; + + ~AppCache(); + + // Use AppCacheGroup::Add/RemoveCache() to manipulate owning group. + void set_owning_group(AppCacheGroup* group) { owning_group_ = group; } + + // FindResponseForRequest helpers + const AppCacheNamespace* FindInterceptNamespace(const GURL& url) { + return FindNamespace(intercept_namespaces_, url); + } + const AppCacheNamespace* FindFallbackNamespace(const GURL& url) { + return FindNamespace(fallback_namespaces_, url); + } + bool IsInNetworkNamespace(const GURL& url) { + return FindNamespace(online_whitelist_namespaces_, url) != NULL; + } + + GURL GetNamespaceEntryUrl(const AppCacheNamespaceVector& namespaces, + const GURL& namespace_url) const; + + // Use AppCacheHost::Associate*Cache() to manipulate host association. + void AssociateHost(AppCacheHost* host) { + associated_hosts_.insert(host); + } + void UnassociateHost(AppCacheHost* host); + + const int64 cache_id_; + scoped_refptr<AppCacheGroup> owning_group_; + AppCacheHosts associated_hosts_; + + EntryMap entries_; // contains entries of all types + + AppCacheNamespaceVector intercept_namespaces_; + AppCacheNamespaceVector fallback_namespaces_; + AppCacheNamespaceVector online_whitelist_namespaces_; + bool online_whitelist_all_; + + bool is_complete_; + + // when this cache was last updated + base::Time update_time_; + + int64 cache_size_; + + typedef std::map<int64, AppCacheExecutableHandler*> HandlerMap; + HandlerMap executable_handlers_; + + // to notify storage when cache is deleted + AppCacheStorage* storage_; + + FRIEND_TEST_ALL_PREFIXES(content::AppCacheTest, InitializeWithManifest); + FRIEND_TEST_ALL_PREFIXES(content::AppCacheTest, ToFromDatabaseRecords); + DISALLOW_COPY_AND_ASSIGN(AppCache); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_APPCACHE_APPCACHE_H_ diff --git a/content/browser/appcache/appcache_backend_impl.cc b/content/browser/appcache/appcache_backend_impl.cc new file mode 100644 index 0000000..099c54e --- /dev/null +++ b/content/browser/appcache/appcache_backend_impl.cc @@ -0,0 +1,182 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/appcache/appcache_backend_impl.h" + +#include "base/stl_util.h" +#include "content/browser/appcache/appcache.h" +#include "content/browser/appcache/appcache_group.h" +#include "content/browser/appcache/appcache_service_impl.h" + +namespace content { + +AppCacheBackendImpl::AppCacheBackendImpl() + : service_(NULL), + frontend_(NULL), + process_id_(0) { +} + +AppCacheBackendImpl::~AppCacheBackendImpl() { + STLDeleteValues(&hosts_); + if (service_) + service_->UnregisterBackend(this); +} + +void AppCacheBackendImpl::Initialize(AppCacheServiceImpl* service, + AppCacheFrontend* frontend, + int process_id) { + DCHECK(!service_ && !frontend_ && frontend && service); + service_ = service; + frontend_ = frontend; + process_id_ = process_id; + service_->RegisterBackend(this); +} + +bool AppCacheBackendImpl::RegisterHost(int id) { + if (GetHost(id)) + return false; + + hosts_.insert( + HostMap::value_type(id, new AppCacheHost(id, frontend_, service_))); + return true; +} + +bool AppCacheBackendImpl::UnregisterHost(int id) { + HostMap::iterator found = hosts_.find(id); + if (found == hosts_.end()) + return false; + + delete found->second; + hosts_.erase(found); + return true; +} + +bool AppCacheBackendImpl::SetSpawningHostId( + int host_id, + int spawning_host_id) { + AppCacheHost* host = GetHost(host_id); + if (!host) + return false; + host->SetSpawningHostId(process_id_, spawning_host_id); + return true; +} + +bool AppCacheBackendImpl::SelectCache( + int host_id, + const GURL& document_url, + const int64 cache_document_was_loaded_from, + const GURL& manifest_url) { + AppCacheHost* host = GetHost(host_id); + if (!host) + return false; + + host->SelectCache(document_url, cache_document_was_loaded_from, + manifest_url); + return true; +} + +bool AppCacheBackendImpl::SelectCacheForWorker( + int host_id, int parent_process_id, int parent_host_id) { + AppCacheHost* host = GetHost(host_id); + if (!host) + return false; + + host->SelectCacheForWorker(parent_process_id, parent_host_id); + return true; +} + +bool AppCacheBackendImpl::SelectCacheForSharedWorker( + int host_id, int64 appcache_id) { + AppCacheHost* host = GetHost(host_id); + if (!host) + return false; + + host->SelectCacheForSharedWorker(appcache_id); + return true; +} + +bool AppCacheBackendImpl::MarkAsForeignEntry( + int host_id, + const GURL& document_url, + int64 cache_document_was_loaded_from) { + AppCacheHost* host = GetHost(host_id); + if (!host) + return false; + + host->MarkAsForeignEntry(document_url, cache_document_was_loaded_from); + return true; +} + +bool AppCacheBackendImpl::GetStatusWithCallback( + int host_id, const GetStatusCallback& callback, void* callback_param) { + AppCacheHost* host = GetHost(host_id); + if (!host) + return false; + + host->GetStatusWithCallback(callback, callback_param); + return true; +} + +bool AppCacheBackendImpl::StartUpdateWithCallback( + int host_id, const StartUpdateCallback& callback, void* callback_param) { + AppCacheHost* host = GetHost(host_id); + if (!host) + return false; + + host->StartUpdateWithCallback(callback, callback_param); + return true; +} + +bool AppCacheBackendImpl::SwapCacheWithCallback( + int host_id, const SwapCacheCallback& callback, void* callback_param) { + AppCacheHost* host = GetHost(host_id); + if (!host) + return false; + + host->SwapCacheWithCallback(callback, callback_param); + return true; +} + +void AppCacheBackendImpl::GetResourceList( + int host_id, std::vector<AppCacheResourceInfo>* resource_infos) { + AppCacheHost* host = GetHost(host_id); + if (!host) + return; + + host->GetResourceList(resource_infos); +} + +scoped_ptr<AppCacheHost> AppCacheBackendImpl::TransferHostOut(int host_id) { + HostMap::iterator found = hosts_.find(host_id); + if (found == hosts_.end()) { + NOTREACHED(); + return scoped_ptr<AppCacheHost>(); + } + + AppCacheHost* transferree = found->second; + + // Put a new empty host in its place. + found->second = new AppCacheHost(host_id, frontend_, service_); + + // We give up ownership. + transferree->PrepareForTransfer(); + return scoped_ptr<AppCacheHost>(transferree); +} + +void AppCacheBackendImpl::TransferHostIn( + int new_host_id, scoped_ptr<AppCacheHost> host) { + HostMap::iterator found = hosts_.find(new_host_id); + if (found == hosts_.end()) { + NOTREACHED(); + return; + } + + delete found->second; + + // We take onwership. + host->CompleteTransfer(new_host_id, frontend_); + found->second = host.release(); +} + +} // namespace content diff --git a/content/browser/appcache/appcache_backend_impl.h b/content/browser/appcache/appcache_backend_impl.h new file mode 100644 index 0000000..bd6f440 --- /dev/null +++ b/content/browser/appcache/appcache_backend_impl.h @@ -0,0 +1,75 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_APPCACHE_APPCACHE_BACKEND_IMPL_H_ +#define CONTENT_BROWSER_APPCACHE_APPCACHE_BACKEND_IMPL_H_ + +#include "base/containers/hash_tables.h" +#include "content/browser/appcache/appcache_host.h" +#include "content/common/content_export.h" + +namespace content { + +class AppCacheServiceImpl; + +class CONTENT_EXPORT AppCacheBackendImpl { + public: + AppCacheBackendImpl(); + ~AppCacheBackendImpl(); + + void Initialize(AppCacheServiceImpl* service, + AppCacheFrontend* frontend, + int process_id); + + int process_id() const { return process_id_; } + + // Methods to support the AppCacheBackend interface. A false return + // value indicates an invalid host_id and that no action was taken + // by the backend impl. + bool RegisterHost(int host_id); + bool UnregisterHost(int host_id); + bool SetSpawningHostId(int host_id, int spawning_host_id); + bool SelectCache(int host_id, + const GURL& document_url, + const int64 cache_document_was_loaded_from, + const GURL& manifest_url); + void GetResourceList( + int host_id, std::vector<AppCacheResourceInfo>* resource_infos); + bool SelectCacheForWorker(int host_id, int parent_process_id, + int parent_host_id); + bool SelectCacheForSharedWorker(int host_id, int64 appcache_id); + bool MarkAsForeignEntry(int host_id, const GURL& document_url, + int64 cache_document_was_loaded_from); + bool GetStatusWithCallback(int host_id, const GetStatusCallback& callback, + void* callback_param); + bool StartUpdateWithCallback(int host_id, const StartUpdateCallback& callback, + void* callback_param); + bool SwapCacheWithCallback(int host_id, const SwapCacheCallback& callback, + void* callback_param); + + // Returns a pointer to a registered host. The backend retains ownership. + AppCacheHost* GetHost(int host_id) { + HostMap::iterator it = hosts_.find(host_id); + return (it != hosts_.end()) ? (it->second) : NULL; + } + + typedef base::hash_map<int, AppCacheHost*> HostMap; + const HostMap& hosts() { return hosts_; } + + // Methods to support cross site navigations. Hosts are transferred + // from process to process accordingly, deparented from the old + // processes backend and reparented to the new. + scoped_ptr<AppCacheHost> TransferHostOut(int host_id); + void TransferHostIn(int new_host_id, scoped_ptr<AppCacheHost> host); + + private: + AppCacheServiceImpl* service_; + AppCacheFrontend* frontend_; + int process_id_; + HostMap hosts_; +}; + +} // namespace + +#endif // CONTENT_BROWSER_APPCACHE_APPCACHE_BACKEND_IMPL_H_ diff --git a/content/browser/appcache/appcache_database.cc b/content/browser/appcache/appcache_database.cc new file mode 100644 index 0000000..546c1dc --- /dev/null +++ b/content/browser/appcache/appcache_database.cc @@ -0,0 +1,1226 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/appcache/appcache_database.h" + +#include "base/auto_reset.h" +#include "base/bind.h" +#include "base/command_line.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "base/strings/utf_string_conversions.h" +#include "content/browser/appcache/appcache_entry.h" +#include "content/browser/appcache/appcache_histograms.h" +#include "sql/connection.h" +#include "sql/error_delegate_util.h" +#include "sql/meta_table.h" +#include "sql/statement.h" +#include "sql/transaction.h" + +namespace content { + +// Schema ------------------------------------------------------------------- +namespace { + +#if defined(APPCACHE_USE_SIMPLE_CACHE) +const int kCurrentVersion = 6; +const int kCompatibleVersion = 6; +#else +const int kCurrentVersion = 5; +const int kCompatibleVersion = 5; +#endif + +// A mechanism to run experiments that may affect in data being persisted +// in different ways such that when the experiment is toggled on/off via +// cmd line flags, the database gets reset. The active flags are stored at +// the time of database creation and compared when reopening. If different +// the database is reset. +const char kExperimentFlagsKey[] = "ExperimentFlags"; + +const char kGroupsTable[] = "Groups"; +const char kCachesTable[] = "Caches"; +const char kEntriesTable[] = "Entries"; +const char kNamespacesTable[] = "Namespaces"; +const char kOnlineWhiteListsTable[] = "OnlineWhiteLists"; +const char kDeletableResponseIdsTable[] = "DeletableResponseIds"; + +struct TableInfo { + const char* table_name; + const char* columns; +}; + +struct IndexInfo { + const char* index_name; + const char* table_name; + const char* columns; + bool unique; +}; + +const TableInfo kTables[] = { + { kGroupsTable, + "(group_id INTEGER PRIMARY KEY," + " origin TEXT," + " manifest_url TEXT," + " creation_time INTEGER," + " last_access_time INTEGER)" }, + + { kCachesTable, + "(cache_id INTEGER PRIMARY KEY," + " group_id INTEGER," + " online_wildcard INTEGER CHECK(online_wildcard IN (0, 1))," + " update_time INTEGER," + " cache_size INTEGER)" }, // intentionally not normalized + + { kEntriesTable, + "(cache_id INTEGER," + " url TEXT," + " flags INTEGER," + " response_id INTEGER," + " response_size INTEGER)" }, + + { kNamespacesTable, + "(cache_id INTEGER," + " origin TEXT," // intentionally not normalized + " type INTEGER," + " namespace_url TEXT," + " target_url TEXT," + " is_pattern INTEGER CHECK(is_pattern IN (0, 1)))" }, + + { kOnlineWhiteListsTable, + "(cache_id INTEGER," + " namespace_url TEXT," + " is_pattern INTEGER CHECK(is_pattern IN (0, 1)))" }, + + { kDeletableResponseIdsTable, + "(response_id INTEGER NOT NULL)" }, +}; + +const IndexInfo kIndexes[] = { + { "GroupsOriginIndex", + kGroupsTable, + "(origin)", + false }, + + { "GroupsManifestIndex", + kGroupsTable, + "(manifest_url)", + true }, + + { "CachesGroupIndex", + kCachesTable, + "(group_id)", + false }, + + { "EntriesCacheIndex", + kEntriesTable, + "(cache_id)", + false }, + + { "EntriesCacheAndUrlIndex", + kEntriesTable, + "(cache_id, url)", + true }, + + { "EntriesResponseIdIndex", + kEntriesTable, + "(response_id)", + true }, + + { "NamespacesCacheIndex", + kNamespacesTable, + "(cache_id)", + false }, + + { "NamespacesOriginIndex", + kNamespacesTable, + "(origin)", + false }, + + { "NamespacesCacheAndUrlIndex", + kNamespacesTable, + "(cache_id, namespace_url)", + true }, + + { "OnlineWhiteListCacheIndex", + kOnlineWhiteListsTable, + "(cache_id)", + false }, + + { "DeletableResponsesIdIndex", + kDeletableResponseIdsTable, + "(response_id)", + true }, +}; + +const int kTableCount = ARRAYSIZE_UNSAFE(kTables); +const int kIndexCount = ARRAYSIZE_UNSAFE(kIndexes); + +bool CreateTable(sql::Connection* db, const TableInfo& info) { + std::string sql("CREATE TABLE "); + sql += info.table_name; + sql += info.columns; + return db->Execute(sql.c_str()); +} + +bool CreateIndex(sql::Connection* db, const IndexInfo& info) { + std::string sql; + if (info.unique) + sql += "CREATE UNIQUE INDEX "; + else + sql += "CREATE INDEX "; + sql += info.index_name; + sql += " ON "; + sql += info.table_name; + sql += info.columns; + return db->Execute(sql.c_str()); +} + +std::string GetActiveExperimentFlags() { + if (CommandLine::ForCurrentProcess()->HasSwitch(kEnableExecutableHandlers)) + return std::string("executableHandlersEnabled"); + return std::string(); +} + +} // anon namespace + +// AppCacheDatabase ---------------------------------------------------------- + +AppCacheDatabase::GroupRecord::GroupRecord() + : group_id(0) { +} + +AppCacheDatabase::GroupRecord::~GroupRecord() { +} + +AppCacheDatabase::NamespaceRecord::NamespaceRecord() + : cache_id(0) { +} + +AppCacheDatabase::NamespaceRecord::~NamespaceRecord() { +} + + +AppCacheDatabase::AppCacheDatabase(const base::FilePath& path) + : db_file_path_(path), + is_disabled_(false), + is_recreating_(false), + was_corruption_detected_(false) { +} + +AppCacheDatabase::~AppCacheDatabase() { +} + +void AppCacheDatabase::Disable() { + VLOG(1) << "Disabling appcache database."; + is_disabled_ = true; + ResetConnectionAndTables(); +} + +int64 AppCacheDatabase::GetOriginUsage(const GURL& origin) { + std::vector<CacheRecord> records; + if (!FindCachesForOrigin(origin, &records)) + return 0; + + int64 origin_usage = 0; + std::vector<CacheRecord>::const_iterator iter = records.begin(); + while (iter != records.end()) { + origin_usage += iter->cache_size; + ++iter; + } + return origin_usage; +} + +bool AppCacheDatabase::GetAllOriginUsage(std::map<GURL, int64>* usage_map) { + std::set<GURL> origins; + if (!FindOriginsWithGroups(&origins)) + return false; + for (std::set<GURL>::const_iterator origin = origins.begin(); + origin != origins.end(); ++origin) { + (*usage_map)[*origin] = GetOriginUsage(*origin); + } + return true; +} + +bool AppCacheDatabase::FindOriginsWithGroups(std::set<GURL>* origins) { + DCHECK(origins && origins->empty()); + if (!LazyOpen(false)) + return false; + + const char* kSql = + "SELECT DISTINCT(origin) FROM Groups"; + + sql::Statement statement(db_->GetUniqueStatement(kSql)); + + while (statement.Step()) + origins->insert(GURL(statement.ColumnString(0))); + + return statement.Succeeded(); +} + +bool AppCacheDatabase::FindLastStorageIds( + int64* last_group_id, int64* last_cache_id, int64* last_response_id, + int64* last_deletable_response_rowid) { + DCHECK(last_group_id && last_cache_id && last_response_id && + last_deletable_response_rowid); + + *last_group_id = 0; + *last_cache_id = 0; + *last_response_id = 0; + *last_deletable_response_rowid = 0; + + if (!LazyOpen(false)) + return false; + + const char* kMaxGroupIdSql = "SELECT MAX(group_id) FROM Groups"; + const char* kMaxCacheIdSql = "SELECT MAX(cache_id) FROM Caches"; + const char* kMaxResponseIdFromEntriesSql = + "SELECT MAX(response_id) FROM Entries"; + const char* kMaxResponseIdFromDeletablesSql = + "SELECT MAX(response_id) FROM DeletableResponseIds"; + const char* kMaxDeletableResponseRowIdSql = + "SELECT MAX(rowid) FROM DeletableResponseIds"; + int64 max_group_id; + int64 max_cache_id; + int64 max_response_id_from_entries; + int64 max_response_id_from_deletables; + int64 max_deletable_response_rowid; + if (!RunUniqueStatementWithInt64Result(kMaxGroupIdSql, &max_group_id) || + !RunUniqueStatementWithInt64Result(kMaxCacheIdSql, &max_cache_id) || + !RunUniqueStatementWithInt64Result(kMaxResponseIdFromEntriesSql, + &max_response_id_from_entries) || + !RunUniqueStatementWithInt64Result(kMaxResponseIdFromDeletablesSql, + &max_response_id_from_deletables) || + !RunUniqueStatementWithInt64Result(kMaxDeletableResponseRowIdSql, + &max_deletable_response_rowid)) { + return false; + } + + *last_group_id = max_group_id; + *last_cache_id = max_cache_id; + *last_response_id = std::max(max_response_id_from_entries, + max_response_id_from_deletables); + *last_deletable_response_rowid = max_deletable_response_rowid; + return true; +} + +bool AppCacheDatabase::FindGroup(int64 group_id, GroupRecord* record) { + DCHECK(record); + if (!LazyOpen(false)) + return false; + + const char* kSql = + "SELECT group_id, origin, manifest_url," + " creation_time, last_access_time" + " FROM Groups WHERE group_id = ?"; + + sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + + statement.BindInt64(0, group_id); + if (!statement.Step()) + return false; + + ReadGroupRecord(statement, record); + DCHECK(record->group_id == group_id); + return true; +} + +bool AppCacheDatabase::FindGroupForManifestUrl( + const GURL& manifest_url, GroupRecord* record) { + DCHECK(record); + if (!LazyOpen(false)) + return false; + + const char* kSql = + "SELECT group_id, origin, manifest_url," + " creation_time, last_access_time" + " FROM Groups WHERE manifest_url = ?"; + + sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + statement.BindString(0, manifest_url.spec()); + + if (!statement.Step()) + return false; + + ReadGroupRecord(statement, record); + DCHECK(record->manifest_url == manifest_url); + return true; +} + +bool AppCacheDatabase::FindGroupsForOrigin( + const GURL& origin, std::vector<GroupRecord>* records) { + DCHECK(records && records->empty()); + if (!LazyOpen(false)) + return false; + + const char* kSql = + "SELECT group_id, origin, manifest_url," + " creation_time, last_access_time" + " FROM Groups WHERE origin = ?"; + + sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + statement.BindString(0, origin.spec()); + + while (statement.Step()) { + records->push_back(GroupRecord()); + ReadGroupRecord(statement, &records->back()); + DCHECK(records->back().origin == origin); + } + + return statement.Succeeded(); +} + +bool AppCacheDatabase::FindGroupForCache(int64 cache_id, GroupRecord* record) { + DCHECK(record); + if (!LazyOpen(false)) + return false; + + const char* kSql = + "SELECT g.group_id, g.origin, g.manifest_url," + " g.creation_time, g.last_access_time" + " FROM Groups g, Caches c" + " WHERE c.cache_id = ? AND c.group_id = g.group_id"; + + sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + statement.BindInt64(0, cache_id); + + if (!statement.Step()) + return false; + + ReadGroupRecord(statement, record); + return true; +} + +bool AppCacheDatabase::UpdateGroupLastAccessTime( + int64 group_id, base::Time time) { + if (!LazyOpen(true)) + return false; + + const char* kSql = + "UPDATE Groups SET last_access_time = ? WHERE group_id = ?"; + + sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + statement.BindInt64(0, time.ToInternalValue()); + statement.BindInt64(1, group_id); + + return statement.Run() && db_->GetLastChangeCount(); +} + +bool AppCacheDatabase::InsertGroup(const GroupRecord* record) { + if (!LazyOpen(true)) + return false; + + const char* kSql = + "INSERT INTO Groups" + " (group_id, origin, manifest_url, creation_time, last_access_time)" + " VALUES(?, ?, ?, ?, ?)"; + + sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + statement.BindInt64(0, record->group_id); + statement.BindString(1, record->origin.spec()); + statement.BindString(2, record->manifest_url.spec()); + statement.BindInt64(3, record->creation_time.ToInternalValue()); + statement.BindInt64(4, record->last_access_time.ToInternalValue()); + + return statement.Run(); +} + +bool AppCacheDatabase::DeleteGroup(int64 group_id) { + if (!LazyOpen(false)) + return false; + + const char* kSql = + "DELETE FROM Groups WHERE group_id = ?"; + + sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + statement.BindInt64(0, group_id); + + return statement.Run(); +} + +bool AppCacheDatabase::FindCache(int64 cache_id, CacheRecord* record) { + DCHECK(record); + if (!LazyOpen(false)) + return false; + + const char* kSql = + "SELECT cache_id, group_id, online_wildcard, update_time, cache_size" + " FROM Caches WHERE cache_id = ?"; + + sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + statement.BindInt64(0, cache_id); + + if (!statement.Step()) + return false; + + ReadCacheRecord(statement, record); + return true; +} + +bool AppCacheDatabase::FindCacheForGroup(int64 group_id, CacheRecord* record) { + DCHECK(record); + if (!LazyOpen(false)) + return false; + + const char* kSql = + "SELECT cache_id, group_id, online_wildcard, update_time, cache_size" + " FROM Caches WHERE group_id = ?"; + + sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + statement.BindInt64(0, group_id); + + if (!statement.Step()) + return false; + + ReadCacheRecord(statement, record); + return true; +} + +bool AppCacheDatabase::FindCachesForOrigin( + const GURL& origin, std::vector<CacheRecord>* records) { + DCHECK(records); + std::vector<GroupRecord> group_records; + if (!FindGroupsForOrigin(origin, &group_records)) + return false; + + CacheRecord cache_record; + std::vector<GroupRecord>::const_iterator iter = group_records.begin(); + while (iter != group_records.end()) { + if (FindCacheForGroup(iter->group_id, &cache_record)) + records->push_back(cache_record); + ++iter; + } + return true; +} + +bool AppCacheDatabase::InsertCache(const CacheRecord* record) { + if (!LazyOpen(true)) + return false; + + const char* kSql = + "INSERT INTO Caches (cache_id, group_id, online_wildcard," + " update_time, cache_size)" + " VALUES(?, ?, ?, ?, ?)"; + + sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + statement.BindInt64(0, record->cache_id); + statement.BindInt64(1, record->group_id); + statement.BindBool(2, record->online_wildcard); + statement.BindInt64(3, record->update_time.ToInternalValue()); + statement.BindInt64(4, record->cache_size); + + return statement.Run(); +} + +bool AppCacheDatabase::DeleteCache(int64 cache_id) { + if (!LazyOpen(false)) + return false; + + const char* kSql = + "DELETE FROM Caches WHERE cache_id = ?"; + + sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + statement.BindInt64(0, cache_id); + + return statement.Run(); +} + +bool AppCacheDatabase::FindEntriesForCache( + int64 cache_id, std::vector<EntryRecord>* records) { + DCHECK(records && records->empty()); + if (!LazyOpen(false)) + return false; + + const char* kSql = + "SELECT cache_id, url, flags, response_id, response_size FROM Entries" + " WHERE cache_id = ?"; + + sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + statement.BindInt64(0, cache_id); + + while (statement.Step()) { + records->push_back(EntryRecord()); + ReadEntryRecord(statement, &records->back()); + DCHECK(records->back().cache_id == cache_id); + } + + return statement.Succeeded(); +} + +bool AppCacheDatabase::FindEntriesForUrl( + const GURL& url, std::vector<EntryRecord>* records) { + DCHECK(records && records->empty()); + if (!LazyOpen(false)) + return false; + + const char* kSql = + "SELECT cache_id, url, flags, response_id, response_size FROM Entries" + " WHERE url = ?"; + + sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + statement.BindString(0, url.spec()); + + while (statement.Step()) { + records->push_back(EntryRecord()); + ReadEntryRecord(statement, &records->back()); + DCHECK(records->back().url == url); + } + + return statement.Succeeded(); +} + +bool AppCacheDatabase::FindEntry( + int64 cache_id, const GURL& url, EntryRecord* record) { + DCHECK(record); + if (!LazyOpen(false)) + return false; + + const char* kSql = + "SELECT cache_id, url, flags, response_id, response_size FROM Entries" + " WHERE cache_id = ? AND url = ?"; + + sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + statement.BindInt64(0, cache_id); + statement.BindString(1, url.spec()); + + if (!statement.Step()) + return false; + + ReadEntryRecord(statement, record); + DCHECK(record->cache_id == cache_id); + DCHECK(record->url == url); + return true; +} + +bool AppCacheDatabase::InsertEntry(const EntryRecord* record) { + if (!LazyOpen(true)) + return false; + + const char* kSql = + "INSERT INTO Entries (cache_id, url, flags, response_id, response_size)" + " VALUES(?, ?, ?, ?, ?)"; + + sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + statement.BindInt64(0, record->cache_id); + statement.BindString(1, record->url.spec()); + statement.BindInt(2, record->flags); + statement.BindInt64(3, record->response_id); + statement.BindInt64(4, record->response_size); + + return statement.Run(); +} + +bool AppCacheDatabase::InsertEntryRecords( + const std::vector<EntryRecord>& records) { + if (records.empty()) + return true; + sql::Transaction transaction(db_.get()); + if (!transaction.Begin()) + return false; + std::vector<EntryRecord>::const_iterator iter = records.begin(); + while (iter != records.end()) { + if (!InsertEntry(&(*iter))) + return false; + ++iter; + } + return transaction.Commit(); +} + +bool AppCacheDatabase::DeleteEntriesForCache(int64 cache_id) { + if (!LazyOpen(false)) + return false; + + const char* kSql = + "DELETE FROM Entries WHERE cache_id = ?"; + + sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + statement.BindInt64(0, cache_id); + + return statement.Run(); +} + +bool AppCacheDatabase::AddEntryFlags( + const GURL& entry_url, int64 cache_id, int additional_flags) { + if (!LazyOpen(false)) + return false; + + const char* kSql = + "UPDATE Entries SET flags = flags | ? WHERE cache_id = ? AND url = ?"; + + sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + statement.BindInt(0, additional_flags); + statement.BindInt64(1, cache_id); + statement.BindString(2, entry_url.spec()); + + return statement.Run() && db_->GetLastChangeCount(); +} + +bool AppCacheDatabase::FindNamespacesForOrigin( + const GURL& origin, + std::vector<NamespaceRecord>* intercepts, + std::vector<NamespaceRecord>* fallbacks) { + DCHECK(intercepts && intercepts->empty()); + DCHECK(fallbacks && fallbacks->empty()); + if (!LazyOpen(false)) + return false; + + const char* kSql = + "SELECT cache_id, origin, type, namespace_url, target_url, is_pattern" + " FROM Namespaces WHERE origin = ?"; + + sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + statement.BindString(0, origin.spec()); + + ReadNamespaceRecords(&statement, intercepts, fallbacks); + + return statement.Succeeded(); +} + +bool AppCacheDatabase::FindNamespacesForCache( + int64 cache_id, + std::vector<NamespaceRecord>* intercepts, + std::vector<NamespaceRecord>* fallbacks) { + DCHECK(intercepts && intercepts->empty()); + DCHECK(fallbacks && fallbacks->empty()); + if (!LazyOpen(false)) + return false; + + const char* kSql = + "SELECT cache_id, origin, type, namespace_url, target_url, is_pattern" + " FROM Namespaces WHERE cache_id = ?"; + + sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + statement.BindInt64(0, cache_id); + + ReadNamespaceRecords(&statement, intercepts, fallbacks); + + return statement.Succeeded(); +} + +bool AppCacheDatabase::InsertNamespace( + const NamespaceRecord* record) { + if (!LazyOpen(true)) + return false; + + const char* kSql = + "INSERT INTO Namespaces" + " (cache_id, origin, type, namespace_url, target_url, is_pattern)" + " VALUES (?, ?, ?, ?, ?, ?)"; + + // Note: quick and dirty storage for the 'executable' bit w/o changing + // schemas, we use the high bit of 'type' field. + int type_with_executable_bit = record->namespace_.type; + if (record->namespace_.is_executable) { + type_with_executable_bit |= 0x8000000; + DCHECK(CommandLine::ForCurrentProcess()->HasSwitch( + kEnableExecutableHandlers)); + } + + sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + statement.BindInt64(0, record->cache_id); + statement.BindString(1, record->origin.spec()); + statement.BindInt(2, type_with_executable_bit); + statement.BindString(3, record->namespace_.namespace_url.spec()); + statement.BindString(4, record->namespace_.target_url.spec()); + statement.BindBool(5, record->namespace_.is_pattern); + return statement.Run(); +} + +bool AppCacheDatabase::InsertNamespaceRecords( + const std::vector<NamespaceRecord>& records) { + if (records.empty()) + return true; + sql::Transaction transaction(db_.get()); + if (!transaction.Begin()) + return false; + std::vector<NamespaceRecord>::const_iterator iter = records.begin(); + while (iter != records.end()) { + if (!InsertNamespace(&(*iter))) + return false; + ++iter; + } + return transaction.Commit(); +} + +bool AppCacheDatabase::DeleteNamespacesForCache(int64 cache_id) { + if (!LazyOpen(false)) + return false; + + const char* kSql = + "DELETE FROM Namespaces WHERE cache_id = ?"; + + sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + statement.BindInt64(0, cache_id); + + return statement.Run(); +} + +bool AppCacheDatabase::FindOnlineWhiteListForCache( + int64 cache_id, std::vector<OnlineWhiteListRecord>* records) { + DCHECK(records && records->empty()); + if (!LazyOpen(false)) + return false; + + const char* kSql = + "SELECT cache_id, namespace_url, is_pattern FROM OnlineWhiteLists" + " WHERE cache_id = ?"; + + sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + statement.BindInt64(0, cache_id); + + while (statement.Step()) { + records->push_back(OnlineWhiteListRecord()); + this->ReadOnlineWhiteListRecord(statement, &records->back()); + DCHECK(records->back().cache_id == cache_id); + } + return statement.Succeeded(); +} + +bool AppCacheDatabase::InsertOnlineWhiteList( + const OnlineWhiteListRecord* record) { + if (!LazyOpen(true)) + return false; + + const char* kSql = + "INSERT INTO OnlineWhiteLists (cache_id, namespace_url, is_pattern)" + " VALUES (?, ?, ?)"; + + sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + statement.BindInt64(0, record->cache_id); + statement.BindString(1, record->namespace_url.spec()); + statement.BindBool(2, record->is_pattern); + + return statement.Run(); +} + +bool AppCacheDatabase::InsertOnlineWhiteListRecords( + const std::vector<OnlineWhiteListRecord>& records) { + if (records.empty()) + return true; + sql::Transaction transaction(db_.get()); + if (!transaction.Begin()) + return false; + std::vector<OnlineWhiteListRecord>::const_iterator iter = records.begin(); + while (iter != records.end()) { + if (!InsertOnlineWhiteList(&(*iter))) + return false; + ++iter; + } + return transaction.Commit(); +} + +bool AppCacheDatabase::DeleteOnlineWhiteListForCache(int64 cache_id) { + if (!LazyOpen(false)) + return false; + + const char* kSql = + "DELETE FROM OnlineWhiteLists WHERE cache_id = ?"; + + sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + statement.BindInt64(0, cache_id); + + return statement.Run(); +} + +bool AppCacheDatabase::GetDeletableResponseIds( + std::vector<int64>* response_ids, int64 max_rowid, int limit) { + if (!LazyOpen(false)) + return false; + + const char* kSql = + "SELECT response_id FROM DeletableResponseIds " + " WHERE rowid <= ?" + " LIMIT ?"; + + sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + statement.BindInt64(0, max_rowid); + statement.BindInt64(1, limit); + + while (statement.Step()) + response_ids->push_back(statement.ColumnInt64(0)); + return statement.Succeeded(); +} + +bool AppCacheDatabase::InsertDeletableResponseIds( + const std::vector<int64>& response_ids) { + const char* kSql = + "INSERT INTO DeletableResponseIds (response_id) VALUES (?)"; + return RunCachedStatementWithIds(SQL_FROM_HERE, kSql, response_ids); +} + +bool AppCacheDatabase::DeleteDeletableResponseIds( + const std::vector<int64>& response_ids) { + const char* kSql = + "DELETE FROM DeletableResponseIds WHERE response_id = ?"; + return RunCachedStatementWithIds(SQL_FROM_HERE, kSql, response_ids); +} + +bool AppCacheDatabase::RunCachedStatementWithIds( + const sql::StatementID& statement_id, const char* sql, + const std::vector<int64>& ids) { + DCHECK(sql); + if (!LazyOpen(true)) + return false; + + sql::Transaction transaction(db_.get()); + if (!transaction.Begin()) + return false; + + sql::Statement statement(db_->GetCachedStatement(statement_id, sql)); + + std::vector<int64>::const_iterator iter = ids.begin(); + while (iter != ids.end()) { + statement.BindInt64(0, *iter); + if (!statement.Run()) + return false; + statement.Reset(true); + ++iter; + } + + return transaction.Commit(); +} + +bool AppCacheDatabase::RunUniqueStatementWithInt64Result( + const char* sql, int64* result) { + DCHECK(sql); + sql::Statement statement(db_->GetUniqueStatement(sql)); + if (!statement.Step()) { + return false; + } + *result = statement.ColumnInt64(0); + return true; +} + +bool AppCacheDatabase::FindResponseIdsForCacheHelper( + int64 cache_id, std::vector<int64>* ids_vector, + std::set<int64>* ids_set) { + DCHECK(ids_vector || ids_set); + DCHECK(!(ids_vector && ids_set)); + if (!LazyOpen(false)) + return false; + + const char* kSql = + "SELECT response_id FROM Entries WHERE cache_id = ?"; + + sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + + statement.BindInt64(0, cache_id); + while (statement.Step()) { + int64 id = statement.ColumnInt64(0); + if (ids_set) + ids_set->insert(id); + else + ids_vector->push_back(id); + } + + return statement.Succeeded(); +} + +void AppCacheDatabase::ReadGroupRecord( + const sql::Statement& statement, GroupRecord* record) { + record->group_id = statement.ColumnInt64(0); + record->origin = GURL(statement.ColumnString(1)); + record->manifest_url = GURL(statement.ColumnString(2)); + record->creation_time = + base::Time::FromInternalValue(statement.ColumnInt64(3)); + record->last_access_time = + base::Time::FromInternalValue(statement.ColumnInt64(4)); +} + +void AppCacheDatabase::ReadCacheRecord( + const sql::Statement& statement, CacheRecord* record) { + record->cache_id = statement.ColumnInt64(0); + record->group_id = statement.ColumnInt64(1); + record->online_wildcard = statement.ColumnBool(2); + record->update_time = + base::Time::FromInternalValue(statement.ColumnInt64(3)); + record->cache_size = statement.ColumnInt64(4); +} + +void AppCacheDatabase::ReadEntryRecord( + const sql::Statement& statement, EntryRecord* record) { + record->cache_id = statement.ColumnInt64(0); + record->url = GURL(statement.ColumnString(1)); + record->flags = statement.ColumnInt(2); + record->response_id = statement.ColumnInt64(3); + record->response_size = statement.ColumnInt64(4); +} + +void AppCacheDatabase::ReadNamespaceRecords( + sql::Statement* statement, + NamespaceRecordVector* intercepts, + NamespaceRecordVector* fallbacks) { + while (statement->Step()) { + AppCacheNamespaceType type = static_cast<AppCacheNamespaceType>( + statement->ColumnInt(2)); + NamespaceRecordVector* records = + (type == APPCACHE_FALLBACK_NAMESPACE) ? fallbacks : intercepts; + records->push_back(NamespaceRecord()); + ReadNamespaceRecord(statement, &records->back()); + } +} + +void AppCacheDatabase::ReadNamespaceRecord( + const sql::Statement* statement, NamespaceRecord* record) { + record->cache_id = statement->ColumnInt64(0); + record->origin = GURL(statement->ColumnString(1)); + int type_with_executable_bit = statement->ColumnInt(2); + record->namespace_.namespace_url = GURL(statement->ColumnString(3)); + record->namespace_.target_url = GURL(statement->ColumnString(4)); + record->namespace_.is_pattern = statement->ColumnBool(5); + + // Note: quick and dirty storage for the 'executable' bit w/o changing + // schemas, we use the high bit of 'type' field. + record->namespace_.type = static_cast<AppCacheNamespaceType> + (type_with_executable_bit & 0x7ffffff); + record->namespace_.is_executable = + (type_with_executable_bit & 0x80000000) != 0; + DCHECK(!record->namespace_.is_executable || + CommandLine::ForCurrentProcess()->HasSwitch(kEnableExecutableHandlers)); +} + +void AppCacheDatabase::ReadOnlineWhiteListRecord( + const sql::Statement& statement, OnlineWhiteListRecord* record) { + record->cache_id = statement.ColumnInt64(0); + record->namespace_url = GURL(statement.ColumnString(1)); + record->is_pattern = statement.ColumnBool(2); +} + +bool AppCacheDatabase::LazyOpen(bool create_if_needed) { + if (db_) + return true; + + // If we tried and failed once, don't try again in the same session + // to avoid creating an incoherent mess on disk. + if (is_disabled_) + return false; + + // Avoid creating a database at all if we can. + bool use_in_memory_db = db_file_path_.empty(); + if (!create_if_needed && + (use_in_memory_db || !base::PathExists(db_file_path_))) { + return false; + } + + db_.reset(new sql::Connection); + meta_table_.reset(new sql::MetaTable); + + db_->set_histogram_tag("AppCache"); + + bool opened = false; + if (use_in_memory_db) { + opened = db_->OpenInMemory(); + } else if (!base::CreateDirectory(db_file_path_.DirName())) { + LOG(ERROR) << "Failed to create appcache directory."; + } else { + opened = db_->Open(db_file_path_); + if (opened) + db_->Preload(); + } + + if (!opened || !db_->QuickIntegrityCheck() || !EnsureDatabaseVersion()) { + LOG(ERROR) << "Failed to open the appcache database."; + AppCacheHistograms::CountInitResult( + AppCacheHistograms::SQL_DATABASE_ERROR); + + // We're unable to open the database. This is a fatal error + // which we can't recover from. We try to handle it by deleting + // the existing appcache data and starting with a clean slate in + // this browser session. + if (!use_in_memory_db && DeleteExistingAndCreateNewDatabase()) + return true; + + Disable(); + return false; + } + + AppCacheHistograms::CountInitResult(AppCacheHistograms::INIT_OK); + was_corruption_detected_ = false; + db_->set_error_callback( + base::Bind(&AppCacheDatabase::OnDatabaseError, base::Unretained(this))); + return true; +} + +bool AppCacheDatabase::EnsureDatabaseVersion() { + if (!sql::MetaTable::DoesTableExist(db_.get())) + return CreateSchema(); + + if (!meta_table_->Init(db_.get(), kCurrentVersion, kCompatibleVersion)) + return false; + + if (meta_table_->GetCompatibleVersionNumber() > kCurrentVersion) { + LOG(WARNING) << "AppCache database is too new."; + return false; + } + + std::string stored_flags; + meta_table_->GetValue(kExperimentFlagsKey, &stored_flags); + if (stored_flags != GetActiveExperimentFlags()) + return false; + + if (meta_table_->GetVersionNumber() < kCurrentVersion) + return UpgradeSchema(); + +#ifndef NDEBUG + DCHECK(sql::MetaTable::DoesTableExist(db_.get())); + for (int i = 0; i < kTableCount; ++i) { + DCHECK(db_->DoesTableExist(kTables[i].table_name)); + } + for (int i = 0; i < kIndexCount; ++i) { + DCHECK(db_->DoesIndexExist(kIndexes[i].index_name)); + } +#endif + + return true; +} + +bool AppCacheDatabase::CreateSchema() { + sql::Transaction transaction(db_.get()); + if (!transaction.Begin()) + return false; + + if (!meta_table_->Init(db_.get(), kCurrentVersion, kCompatibleVersion)) + return false; + + if (!meta_table_->SetValue(kExperimentFlagsKey, + GetActiveExperimentFlags())) { + return false; + } + + for (int i = 0; i < kTableCount; ++i) { + if (!CreateTable(db_.get(), kTables[i])) + return false; + } + + for (int i = 0; i < kIndexCount; ++i) { + if (!CreateIndex(db_.get(), kIndexes[i])) + return false; + } + + return transaction.Commit(); +} + +bool AppCacheDatabase::UpgradeSchema() { +#if defined(APPCACHE_USE_SIMPLE_CACHE) + return DeleteExistingAndCreateNewDatabase(); +#else + if (meta_table_->GetVersionNumber() == 3) { + // version 3 was pre 12/17/2011 + DCHECK_EQ(strcmp(kNamespacesTable, kTables[3].table_name), 0); + DCHECK_EQ(strcmp(kNamespacesTable, kIndexes[6].table_name), 0); + DCHECK_EQ(strcmp(kNamespacesTable, kIndexes[7].table_name), 0); + DCHECK_EQ(strcmp(kNamespacesTable, kIndexes[8].table_name), 0); + + const TableInfo kNamespaceTable_v4 = { + kNamespacesTable, + "(cache_id INTEGER," + " origin TEXT," // intentionally not normalized + " type INTEGER," + " namespace_url TEXT," + " target_url TEXT)" + }; + + // Migrate from the old FallbackNameSpaces to the newer Namespaces table, + // but without the is_pattern column added in v5. + sql::Transaction transaction(db_.get()); + if (!transaction.Begin() || + !CreateTable(db_.get(), kNamespaceTable_v4)) { + return false; + } + + // Move data from the old table to the new table, setting the + // 'type' for all current records to the value for + // APPCACHE_FALLBACK_NAMESPACE. + DCHECK_EQ(0, static_cast<int>(APPCACHE_FALLBACK_NAMESPACE)); + if (!db_->Execute( + "INSERT INTO Namespaces" + " SELECT cache_id, origin, 0, namespace_url, fallback_entry_url" + " FROM FallbackNameSpaces")) { + return false; + } + + // Drop the old table, indexes on that table are also removed by this. + if (!db_->Execute("DROP TABLE FallbackNameSpaces")) + return false; + + // Create new indexes. + if (!CreateIndex(db_.get(), kIndexes[6]) || + !CreateIndex(db_.get(), kIndexes[7]) || + !CreateIndex(db_.get(), kIndexes[8])) { + return false; + } + + meta_table_->SetVersionNumber(4); + meta_table_->SetCompatibleVersionNumber(4); + if (!transaction.Commit()) + return false; + } + + if (meta_table_->GetVersionNumber() == 4) { + // version 4 pre 3/30/2013 + // Add the is_pattern column to the Namespaces and OnlineWhitelists tables. + DCHECK_EQ(strcmp(kNamespacesTable, "Namespaces"), 0); + sql::Transaction transaction(db_.get()); + if (!transaction.Begin()) + return false; + if (!db_->Execute( + "ALTER TABLE Namespaces ADD COLUMN" + " is_pattern INTEGER CHECK(is_pattern IN (0, 1))")) { + return false; + } + if (!db_->Execute( + "ALTER TABLE OnlineWhitelists ADD COLUMN" + " is_pattern INTEGER CHECK(is_pattern IN (0, 1))")) { + return false; + } + meta_table_->SetVersionNumber(5); + meta_table_->SetCompatibleVersionNumber(5); + return transaction.Commit(); + } + + // If there is no upgrade path for the version on disk to the current + // version, nuke everything and start over. + return DeleteExistingAndCreateNewDatabase(); +#endif +} + +void AppCacheDatabase::ResetConnectionAndTables() { + meta_table_.reset(); + db_.reset(); +} + +bool AppCacheDatabase::DeleteExistingAndCreateNewDatabase() { + DCHECK(!db_file_path_.empty()); + DCHECK(base::PathExists(db_file_path_)); + VLOG(1) << "Deleting existing appcache data and starting over."; + + ResetConnectionAndTables(); + + // This also deletes the disk cache data. + base::FilePath directory = db_file_path_.DirName(); + if (!base::DeleteFile(directory, true)) + return false; + + // Make sure the steps above actually deleted things. + if (base::PathExists(directory)) + return false; + + if (!base::CreateDirectory(directory)) + return false; + + // So we can't go recursive. + if (is_recreating_) + return false; + + base::AutoReset<bool> auto_reset(&is_recreating_, true); + return LazyOpen(true); +} + +void AppCacheDatabase::OnDatabaseError(int err, sql::Statement* stmt) { + was_corruption_detected_ |= sql::IsErrorCatastrophic(err); + if (!db_->ShouldIgnoreSqliteError(err)) + DLOG(ERROR) << db_->GetErrorMessage(); + // TODO: Maybe use non-catostrophic errors to trigger a full integrity check? +} + +} // namespace content diff --git a/content/browser/appcache/appcache_database.h b/content/browser/appcache/appcache_database.h new file mode 100644 index 0000000..2fe5e12 --- /dev/null +++ b/content/browser/appcache/appcache_database.h @@ -0,0 +1,254 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_APPCACHE_APPCACHE_DATABASE_H_ +#define CONTENT_BROWSER_APPCACHE_APPCACHE_DATABASE_H_ + +#include <map> +#include <set> +#include <vector> + +#include "base/basictypes.h" +#include "base/files/file_path.h" +#include "base/gtest_prod_util.h" +#include "base/memory/scoped_ptr.h" +#include "base/time/time.h" +#include "content/common/appcache_interfaces.h" +#include "content/common/content_export.h" +#include "url/gurl.h" + +namespace sql { +class Connection; +class MetaTable; +class Statement; +class StatementID; +} + +namespace content { +FORWARD_DECLARE_TEST(AppCacheDatabaseTest, CacheRecords); +FORWARD_DECLARE_TEST(AppCacheDatabaseTest, EntryRecords); +FORWARD_DECLARE_TEST(AppCacheDatabaseTest, QuickIntegrityCheck); +FORWARD_DECLARE_TEST(AppCacheDatabaseTest, NamespaceRecords); +FORWARD_DECLARE_TEST(AppCacheDatabaseTest, GroupRecords); +FORWARD_DECLARE_TEST(AppCacheDatabaseTest, LazyOpen); +FORWARD_DECLARE_TEST(AppCacheDatabaseTest, ExperimentalFlags); +FORWARD_DECLARE_TEST(AppCacheDatabaseTest, OnlineWhiteListRecords); +FORWARD_DECLARE_TEST(AppCacheDatabaseTest, ReCreate); +FORWARD_DECLARE_TEST(AppCacheDatabaseTest, DeletableResponseIds); +FORWARD_DECLARE_TEST(AppCacheDatabaseTest, OriginUsage); +FORWARD_DECLARE_TEST(AppCacheDatabaseTest, UpgradeSchema3to5); +FORWARD_DECLARE_TEST(AppCacheDatabaseTest, UpgradeSchema4to5); +FORWARD_DECLARE_TEST(AppCacheDatabaseTest, WasCorrutionDetected); +class AppCacheDatabaseTest; +class AppCacheStorageImplTest; +} + +namespace content { + +class CONTENT_EXPORT AppCacheDatabase { + public: + struct CONTENT_EXPORT GroupRecord { + GroupRecord(); + ~GroupRecord(); + + int64 group_id; + GURL origin; + GURL manifest_url; + base::Time creation_time; + base::Time last_access_time; + }; + + struct CONTENT_EXPORT CacheRecord { + CacheRecord() + : cache_id(0), group_id(0), online_wildcard(false), cache_size(0) {} + + int64 cache_id; + int64 group_id; + bool online_wildcard; + base::Time update_time; + int64 cache_size; // the sum of all response sizes in this cache + }; + + struct EntryRecord { + EntryRecord() : cache_id(0), flags(0), response_id(0), response_size(0) {} + + int64 cache_id; + GURL url; + int flags; + int64 response_id; + int64 response_size; + }; + + struct CONTENT_EXPORT NamespaceRecord { + NamespaceRecord(); + ~NamespaceRecord(); + + int64 cache_id; + GURL origin; + AppCacheNamespace namespace_; + }; + + typedef std::vector<NamespaceRecord> NamespaceRecordVector; + + struct OnlineWhiteListRecord { + OnlineWhiteListRecord() : cache_id(0), is_pattern(false) {} + + int64 cache_id; + GURL namespace_url; + bool is_pattern; + }; + + explicit AppCacheDatabase(const base::FilePath& path); + ~AppCacheDatabase(); + + void Disable(); + bool is_disabled() const { return is_disabled_; } + bool was_corruption_detected() const { return was_corruption_detected_; } + + int64 GetOriginUsage(const GURL& origin); + bool GetAllOriginUsage(std::map<GURL, int64>* usage_map); + + bool FindOriginsWithGroups(std::set<GURL>* origins); + bool FindLastStorageIds( + int64* last_group_id, int64* last_cache_id, int64* last_response_id, + int64* last_deletable_response_rowid); + + bool FindGroup(int64 group_id, GroupRecord* record); + bool FindGroupForManifestUrl(const GURL& manifest_url, GroupRecord* record); + bool FindGroupsForOrigin( + const GURL& origin, std::vector<GroupRecord>* records); + bool FindGroupForCache(int64 cache_id, GroupRecord* record); + bool UpdateGroupLastAccessTime( + int64 group_id, base::Time last_access_time); + bool InsertGroup(const GroupRecord* record); + bool DeleteGroup(int64 group_id); + + bool FindCache(int64 cache_id, CacheRecord* record); + bool FindCacheForGroup(int64 group_id, CacheRecord* record); + bool FindCachesForOrigin( + const GURL& origin, std::vector<CacheRecord>* records); + bool InsertCache(const CacheRecord* record); + bool DeleteCache(int64 cache_id); + + bool FindEntriesForCache( + int64 cache_id, std::vector<EntryRecord>* records); + bool FindEntriesForUrl( + const GURL& url, std::vector<EntryRecord>* records); + bool FindEntry(int64 cache_id, const GURL& url, EntryRecord* record); + bool InsertEntry(const EntryRecord* record); + bool InsertEntryRecords( + const std::vector<EntryRecord>& records); + bool DeleteEntriesForCache(int64 cache_id); + bool AddEntryFlags(const GURL& entry_url, int64 cache_id, + int additional_flags); + bool FindResponseIdsForCacheAsVector( + int64 cache_id, std::vector<int64>* response_ids) { + return FindResponseIdsForCacheHelper(cache_id, response_ids, NULL); + } + bool FindResponseIdsForCacheAsSet( + int64 cache_id, std::set<int64>* response_ids) { + return FindResponseIdsForCacheHelper(cache_id, NULL, response_ids); + } + + bool FindNamespacesForOrigin( + const GURL& origin, + NamespaceRecordVector* intercepts, + NamespaceRecordVector* fallbacks); + bool FindNamespacesForCache( + int64 cache_id, + NamespaceRecordVector* intercepts, + std::vector<NamespaceRecord>* fallbacks); + bool InsertNamespaceRecords( + const NamespaceRecordVector& records); + bool InsertNamespace(const NamespaceRecord* record); + bool DeleteNamespacesForCache(int64 cache_id); + + bool FindOnlineWhiteListForCache( + int64 cache_id, std::vector<OnlineWhiteListRecord>* records); + bool InsertOnlineWhiteList(const OnlineWhiteListRecord* record); + bool InsertOnlineWhiteListRecords( + const std::vector<OnlineWhiteListRecord>& records); + bool DeleteOnlineWhiteListForCache(int64 cache_id); + + bool GetDeletableResponseIds(std::vector<int64>* response_ids, + int64 max_rowid, int limit); + bool InsertDeletableResponseIds(const std::vector<int64>& response_ids); + bool DeleteDeletableResponseIds(const std::vector<int64>& response_ids); + + // So our callers can wrap operations in transactions. + sql::Connection* db_connection() { + LazyOpen(true); + return db_.get(); + } + + private: + bool RunCachedStatementWithIds( + const sql::StatementID& statement_id, const char* sql, + const std::vector<int64>& ids); + bool RunUniqueStatementWithInt64Result(const char* sql, int64* result); + + bool FindResponseIdsForCacheHelper( + int64 cache_id, std::vector<int64>* ids_vector, + std::set<int64>* ids_set); + + // Record retrieval helpers + void ReadGroupRecord(const sql::Statement& statement, GroupRecord* record); + void ReadCacheRecord(const sql::Statement& statement, CacheRecord* record); + void ReadEntryRecord(const sql::Statement& statement, EntryRecord* record); + void ReadNamespaceRecords( + sql::Statement* statement, + NamespaceRecordVector* intercepts, + NamespaceRecordVector* fallbacks); + void ReadNamespaceRecord( + const sql::Statement* statement, NamespaceRecord* record); + void ReadOnlineWhiteListRecord( + const sql::Statement& statement, OnlineWhiteListRecord* record); + + // Database creation + bool LazyOpen(bool create_if_needed); + bool EnsureDatabaseVersion(); + bool CreateSchema(); + bool UpgradeSchema(); + + void ResetConnectionAndTables(); + + // Deletes the existing database file and the entire directory containing + // the database file including the disk cache in which response headers + // and bodies are stored, and then creates a new database file. + bool DeleteExistingAndCreateNewDatabase(); + + void OnDatabaseError(int err, sql::Statement* stmt); + + base::FilePath db_file_path_; + scoped_ptr<sql::Connection> db_; + scoped_ptr<sql::MetaTable> meta_table_; + bool is_disabled_; + bool is_recreating_; + bool was_corruption_detected_; + + friend class content::AppCacheDatabaseTest; + friend class content::AppCacheStorageImplTest; + + FRIEND_TEST_ALL_PREFIXES(content::AppCacheDatabaseTest, CacheRecords); + FRIEND_TEST_ALL_PREFIXES(content::AppCacheDatabaseTest, EntryRecords); + FRIEND_TEST_ALL_PREFIXES(content::AppCacheDatabaseTest, QuickIntegrityCheck); + FRIEND_TEST_ALL_PREFIXES(content::AppCacheDatabaseTest, NamespaceRecords); + FRIEND_TEST_ALL_PREFIXES(content::AppCacheDatabaseTest, GroupRecords); + FRIEND_TEST_ALL_PREFIXES(content::AppCacheDatabaseTest, LazyOpen); + FRIEND_TEST_ALL_PREFIXES(content::AppCacheDatabaseTest, ExperimentalFlags); + FRIEND_TEST_ALL_PREFIXES(content::AppCacheDatabaseTest, + OnlineWhiteListRecords); + FRIEND_TEST_ALL_PREFIXES(content::AppCacheDatabaseTest, ReCreate); + FRIEND_TEST_ALL_PREFIXES(content::AppCacheDatabaseTest, DeletableResponseIds); + FRIEND_TEST_ALL_PREFIXES(content::AppCacheDatabaseTest, OriginUsage); + FRIEND_TEST_ALL_PREFIXES(content::AppCacheDatabaseTest, UpgradeSchema3to5); + FRIEND_TEST_ALL_PREFIXES(content::AppCacheDatabaseTest, UpgradeSchema4to5); + FRIEND_TEST_ALL_PREFIXES(content::AppCacheDatabaseTest, WasCorrutionDetected); + + DISALLOW_COPY_AND_ASSIGN(AppCacheDatabase); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_APPCACHE_APPCACHE_DATABASE_H_ diff --git a/content/browser/appcache/appcache_database_unittest.cc b/content/browser/appcache/appcache_database_unittest.cc index 92fa6a5..44a6c6a 100644 --- a/content/browser/appcache/appcache_database_unittest.cc +++ b/content/browser/appcache/appcache_database_unittest.cc @@ -6,6 +6,8 @@ #include "base/file_util.h" #include "base/files/scoped_temp_dir.h" #include "base/strings/stringprintf.h" +#include "content/browser/appcache/appcache_database.h" +#include "content/browser/appcache/appcache_entry.h" #include "sql/connection.h" #include "sql/meta_table.h" #include "sql/statement.h" @@ -14,14 +16,6 @@ #include "sql/transaction.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/sqlite/sqlite3.h" -#include "webkit/browser/appcache/appcache_database.h" -#include "webkit/browser/appcache/appcache_entry.h" - -using appcache::AppCacheDatabase; -using appcache::AppCacheEntry; -using appcache::APPCACHE_FALLBACK_NAMESPACE; -using appcache::APPCACHE_INTERCEPT_NAMESPACE; -using appcache::APPCACHE_NETWORK_NAMESPACE; namespace { diff --git a/content/browser/appcache/appcache_disk_cache.cc b/content/browser/appcache/appcache_disk_cache.cc new file mode 100644 index 0000000..50995c7 --- /dev/null +++ b/content/browser/appcache/appcache_disk_cache.cc @@ -0,0 +1,351 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/appcache/appcache_disk_cache.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/stl_util.h" +#include "base/strings/string_number_conversions.h" +#include "net/base/cache_type.h" +#include "net/base/net_errors.h" + +namespace content { + +// A callback shim that provides storage for the 'backend_ptr' value +// and will delete a resulting ptr if completion occurs after its +// been canceled. +class AppCacheDiskCache::CreateBackendCallbackShim + : public base::RefCounted<CreateBackendCallbackShim> { + public: + explicit CreateBackendCallbackShim(AppCacheDiskCache* object) + : appcache_diskcache_(object) { + } + + void Cancel() { + appcache_diskcache_ = NULL; + } + + void Callback(int rv) { + if (appcache_diskcache_) + appcache_diskcache_->OnCreateBackendComplete(rv); + } + + scoped_ptr<disk_cache::Backend> backend_ptr_; // Accessed directly. + + private: + friend class base::RefCounted<CreateBackendCallbackShim>; + + ~CreateBackendCallbackShim() { + } + + AppCacheDiskCache* appcache_diskcache_; // Unowned pointer. +}; + +// An implementation of AppCacheDiskCacheInterface::Entry that's a thin +// wrapper around disk_cache::Entry. +class AppCacheDiskCache::EntryImpl : public Entry { + public: + EntryImpl(disk_cache::Entry* disk_cache_entry, + AppCacheDiskCache* owner) + : disk_cache_entry_(disk_cache_entry), owner_(owner) { + DCHECK(disk_cache_entry); + DCHECK(owner); + owner_->AddOpenEntry(this); + } + + // Entry implementation. + virtual int Read(int index, int64 offset, net::IOBuffer* buf, int buf_len, + const net::CompletionCallback& callback) OVERRIDE { + if (offset < 0 || offset > kint32max) + return net::ERR_INVALID_ARGUMENT; + if (!disk_cache_entry_) + return net::ERR_ABORTED; + return disk_cache_entry_->ReadData( + index, static_cast<int>(offset), buf, buf_len, callback); + } + + virtual int Write(int index, int64 offset, net::IOBuffer* buf, int buf_len, + const net::CompletionCallback& callback) OVERRIDE { + if (offset < 0 || offset > kint32max) + return net::ERR_INVALID_ARGUMENT; + if (!disk_cache_entry_) + return net::ERR_ABORTED; + const bool kTruncate = true; + return disk_cache_entry_->WriteData( + index, static_cast<int>(offset), buf, buf_len, callback, kTruncate); + } + + virtual int64 GetSize(int index) OVERRIDE { + return disk_cache_entry_ ? disk_cache_entry_->GetDataSize(index) : 0L; + } + + virtual void Close() OVERRIDE { + if (disk_cache_entry_) + disk_cache_entry_->Close(); + delete this; + } + + void Abandon() { + owner_ = NULL; + disk_cache_entry_->Close(); + disk_cache_entry_ = NULL; + } + + private: + virtual ~EntryImpl() { + if (owner_) + owner_->RemoveOpenEntry(this); + } + + disk_cache::Entry* disk_cache_entry_; + AppCacheDiskCache* owner_; +}; + +// Separate object to hold state for each Create, Delete, or Doom call +// while the call is in-flight and to produce an EntryImpl upon completion. +class AppCacheDiskCache::ActiveCall { + public: + explicit ActiveCall(AppCacheDiskCache* owner) + : entry_(NULL), + owner_(owner), + entry_ptr_(NULL) { + } + + int CreateEntry(int64 key, Entry** entry, + const net::CompletionCallback& callback) { + int rv = owner_->disk_cache()->CreateEntry( + base::Int64ToString(key), &entry_ptr_, + base::Bind(&ActiveCall::OnAsyncCompletion, base::Unretained(this))); + return HandleImmediateReturnValue(rv, entry, callback); + } + + int OpenEntry(int64 key, Entry** entry, + const net::CompletionCallback& callback) { + int rv = owner_->disk_cache()->OpenEntry( + base::Int64ToString(key), &entry_ptr_, + base::Bind(&ActiveCall::OnAsyncCompletion, base::Unretained(this))); + return HandleImmediateReturnValue(rv, entry, callback); + } + + int DoomEntry(int64 key, const net::CompletionCallback& callback) { + int rv = owner_->disk_cache()->DoomEntry( + base::Int64ToString(key), + base::Bind(&ActiveCall::OnAsyncCompletion, base::Unretained(this))); + return HandleImmediateReturnValue(rv, NULL, callback); + } + + private: + int HandleImmediateReturnValue(int rv, Entry** entry, + const net::CompletionCallback& callback) { + if (rv == net::ERR_IO_PENDING) { + // OnAsyncCompletion will be called later. + callback_ = callback; + entry_ = entry; + owner_->AddActiveCall(this); + return net::ERR_IO_PENDING; + } + if (rv == net::OK && entry) + *entry = new EntryImpl(entry_ptr_, owner_); + delete this; + return rv; + } + + void OnAsyncCompletion(int rv) { + owner_->RemoveActiveCall(this); + if (rv == net::OK && entry_) + *entry_ = new EntryImpl(entry_ptr_, owner_); + callback_.Run(rv); + callback_.Reset(); + delete this; + } + + Entry** entry_; + net::CompletionCallback callback_; + AppCacheDiskCache* owner_; + disk_cache::Entry* entry_ptr_; +}; + +AppCacheDiskCache::AppCacheDiskCache() + : is_disabled_(false) { +} + +AppCacheDiskCache::~AppCacheDiskCache() { + Disable(); +} + +int AppCacheDiskCache::InitWithDiskBackend( + const base::FilePath& disk_cache_directory, int disk_cache_size, bool force, + base::MessageLoopProxy* cache_thread, + const net::CompletionCallback& callback) { + return Init(net::APP_CACHE, disk_cache_directory, + disk_cache_size, force, cache_thread, callback); +} + +int AppCacheDiskCache::InitWithMemBackend( + int mem_cache_size, const net::CompletionCallback& callback) { + return Init(net::MEMORY_CACHE, base::FilePath(), mem_cache_size, false, NULL, + callback); +} + +void AppCacheDiskCache::Disable() { + if (is_disabled_) + return; + + is_disabled_ = true; + + if (create_backend_callback_.get()) { + create_backend_callback_->Cancel(); + create_backend_callback_ = NULL; + OnCreateBackendComplete(net::ERR_ABORTED); + } + + // We need to close open file handles in order to reinitalize the + // appcache system on the fly. File handles held in both entries and in + // the main disk_cache::Backend class need to be released. + for (OpenEntries::const_iterator iter = open_entries_.begin(); + iter != open_entries_.end(); ++iter) { + (*iter)->Abandon(); + } + open_entries_.clear(); + disk_cache_.reset(); + STLDeleteElements(&active_calls_); +} + +int AppCacheDiskCache::CreateEntry(int64 key, Entry** entry, + const net::CompletionCallback& callback) { + DCHECK(entry); + DCHECK(!callback.is_null()); + if (is_disabled_) + return net::ERR_ABORTED; + + if (is_initializing()) { + pending_calls_.push_back(PendingCall(CREATE, key, entry, callback)); + return net::ERR_IO_PENDING; + } + + if (!disk_cache_) + return net::ERR_FAILED; + + return (new ActiveCall(this))->CreateEntry(key, entry, callback); +} + +int AppCacheDiskCache::OpenEntry(int64 key, Entry** entry, + const net::CompletionCallback& callback) { + DCHECK(entry); + DCHECK(!callback.is_null()); + if (is_disabled_) + return net::ERR_ABORTED; + + if (is_initializing()) { + pending_calls_.push_back(PendingCall(OPEN, key, entry, callback)); + return net::ERR_IO_PENDING; + } + + if (!disk_cache_) + return net::ERR_FAILED; + + return (new ActiveCall(this))->OpenEntry(key, entry, callback); +} + +int AppCacheDiskCache::DoomEntry(int64 key, + const net::CompletionCallback& callback) { + DCHECK(!callback.is_null()); + if (is_disabled_) + return net::ERR_ABORTED; + + if (is_initializing()) { + pending_calls_.push_back(PendingCall(DOOM, key, NULL, callback)); + return net::ERR_IO_PENDING; + } + + if (!disk_cache_) + return net::ERR_FAILED; + + return (new ActiveCall(this))->DoomEntry(key, callback); +} + +AppCacheDiskCache::PendingCall::PendingCall() + : call_type(CREATE), + key(0), + entry(NULL) { +} + +AppCacheDiskCache::PendingCall::PendingCall(PendingCallType call_type, + int64 key, + Entry** entry, + const net::CompletionCallback& callback) + : call_type(call_type), + key(key), + entry(entry), + callback(callback) { +} + +AppCacheDiskCache::PendingCall::~PendingCall() {} + +int AppCacheDiskCache::Init(net::CacheType cache_type, + const base::FilePath& cache_directory, + int cache_size, bool force, + base::MessageLoopProxy* cache_thread, + const net::CompletionCallback& callback) { + DCHECK(!is_initializing() && !disk_cache_.get()); + is_disabled_ = false; + create_backend_callback_ = new CreateBackendCallbackShim(this); + +#if defined(APPCACHE_USE_SIMPLE_CACHE) + const net::BackendType backend_type = net::CACHE_BACKEND_SIMPLE; +#else + const net::BackendType backend_type = net::CACHE_BACKEND_DEFAULT; +#endif + int rv = disk_cache::CreateCacheBackend( + cache_type, backend_type, cache_directory, cache_size, + force, cache_thread, NULL, &(create_backend_callback_->backend_ptr_), + base::Bind(&CreateBackendCallbackShim::Callback, + create_backend_callback_)); + if (rv == net::ERR_IO_PENDING) + init_callback_ = callback; + else + OnCreateBackendComplete(rv); + return rv; +} + +void AppCacheDiskCache::OnCreateBackendComplete(int rv) { + if (rv == net::OK) { + disk_cache_ = create_backend_callback_->backend_ptr_.Pass(); + } + create_backend_callback_ = NULL; + + // Invoke our clients callback function. + if (!init_callback_.is_null()) { + init_callback_.Run(rv); + init_callback_.Reset(); + } + + // Service pending calls that were queued up while we were initializing. + for (PendingCalls::const_iterator iter = pending_calls_.begin(); + iter < pending_calls_.end(); ++iter) { + int rv = net::ERR_FAILED; + switch (iter->call_type) { + case CREATE: + rv = CreateEntry(iter->key, iter->entry, iter->callback); + break; + case OPEN: + rv = OpenEntry(iter->key, iter->entry, iter->callback); + break; + case DOOM: + rv = DoomEntry(iter->key, iter->callback); + break; + default: + NOTREACHED(); + break; + } + if (rv != net::ERR_IO_PENDING) + iter->callback.Run(rv); + } + pending_calls_.clear(); +} + +} // namespace content diff --git a/content/browser/appcache/appcache_disk_cache.h b/content/browser/appcache/appcache_disk_cache.h new file mode 100644 index 0000000..3d51889 --- /dev/null +++ b/content/browser/appcache/appcache_disk_cache.h @@ -0,0 +1,104 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_APPCACHE_APPCACHE_DISK_CACHE_H_ +#define CONTENT_BROWSER_APPCACHE_APPCACHE_DISK_CACHE_H_ + +#include <set> +#include <vector> + +#include "base/memory/scoped_ptr.h" +#include "content/browser/appcache/appcache_response.h" +#include "content/common/content_export.h" +#include "net/disk_cache/disk_cache.h" + +namespace content { + +// An implementation of AppCacheDiskCacheInterface that +// uses net::DiskCache as the backing store. +class CONTENT_EXPORT AppCacheDiskCache + : public AppCacheDiskCacheInterface { + public: + AppCacheDiskCache(); + virtual ~AppCacheDiskCache(); + + // Initializes the object to use disk backed storage. + int InitWithDiskBackend(const base::FilePath& disk_cache_directory, + int disk_cache_size, bool force, + base::MessageLoopProxy* cache_thread, + const net::CompletionCallback& callback); + + // Initializes the object to use memory only storage. + // This is used for Chrome's incognito browsing. + int InitWithMemBackend(int disk_cache_size, + const net::CompletionCallback& callback); + + void Disable(); + bool is_disabled() const { return is_disabled_; } + + virtual int CreateEntry(int64 key, Entry** entry, + const net::CompletionCallback& callback) OVERRIDE; + virtual int OpenEntry(int64 key, Entry** entry, + const net::CompletionCallback& callback) OVERRIDE; + virtual int DoomEntry(int64 key, + const net::CompletionCallback& callback) OVERRIDE; + + private: + class CreateBackendCallbackShim; + class EntryImpl; + + // PendingCalls allow CreateEntry, OpenEntry, and DoomEntry to be called + // immediately after construction, without waiting for the + // underlying disk_cache::Backend to be fully constructed. Early + // calls are queued up and serviced once the disk_cache::Backend is + // really ready to go. + enum PendingCallType { + CREATE, + OPEN, + DOOM + }; + struct PendingCall { + PendingCallType call_type; + int64 key; + Entry** entry; + net::CompletionCallback callback; + + PendingCall(); + + PendingCall(PendingCallType call_type, int64 key, + Entry** entry, const net::CompletionCallback& callback); + + ~PendingCall(); + }; + typedef std::vector<PendingCall> PendingCalls; + + class ActiveCall; + typedef std::set<ActiveCall*> ActiveCalls; + typedef std::set<EntryImpl*> OpenEntries; + + bool is_initializing() const { + return create_backend_callback_.get() != NULL; + } + disk_cache::Backend* disk_cache() { return disk_cache_.get(); } + int Init(net::CacheType cache_type, const base::FilePath& directory, + int cache_size, bool force, base::MessageLoopProxy* cache_thread, + const net::CompletionCallback& callback); + void OnCreateBackendComplete(int rv); + void AddActiveCall(ActiveCall* call) { active_calls_.insert(call); } + void RemoveActiveCall(ActiveCall* call) { active_calls_.erase(call); } + void AddOpenEntry(EntryImpl* entry) { open_entries_.insert(entry); } + void RemoveOpenEntry(EntryImpl* entry) { open_entries_.erase(entry); } + + bool is_disabled_; + net::CompletionCallback init_callback_; + scoped_refptr<CreateBackendCallbackShim> create_backend_callback_; + PendingCalls pending_calls_; + ActiveCalls active_calls_; + OpenEntries open_entries_; + scoped_ptr<disk_cache::Backend> disk_cache_; +}; + +} // namespace content + +#endif // CONTENT_BROWSER_APPCACHE_APPCACHE_DISK_CACHE_H_ diff --git a/content/browser/appcache/appcache_disk_cache_unittest.cc b/content/browser/appcache/appcache_disk_cache_unittest.cc index 5e1e083..b7e1407 100644 --- a/content/browser/appcache/appcache_disk_cache_unittest.cc +++ b/content/browser/appcache/appcache_disk_cache_unittest.cc @@ -7,12 +7,10 @@ #include "base/files/scoped_temp_dir.h" #include "base/message_loop/message_loop_proxy.h" #include "base/run_loop.h" +#include "content/browser/appcache/appcache_disk_cache.h" #include "net/base/io_buffer.h" #include "net/base/net_errors.h" #include "testing/gtest/include/gtest/gtest.h" -#include "webkit/browser/appcache/appcache_disk_cache.h" - -using appcache::AppCacheDiskCache; namespace content { diff --git a/content/browser/appcache/appcache_dispatcher_host.cc b/content/browser/appcache/appcache_dispatcher_host.cc index 64e46b1..eac1eff 100644 --- a/content/browser/appcache/appcache_dispatcher_host.cc +++ b/content/browser/appcache/appcache_dispatcher_host.cc @@ -103,7 +103,7 @@ void AppCacheDispatcherHost::OnSelectCache( BadMessageReceived(); } } else { - frontend_proxy_.OnCacheSelected(host_id, appcache::AppCacheInfo()); + frontend_proxy_.OnCacheSelected(host_id, AppCacheInfo()); } } @@ -115,7 +115,7 @@ void AppCacheDispatcherHost::OnSelectCacheForWorker( BadMessageReceived(); } } else { - frontend_proxy_.OnCacheSelected(host_id, appcache::AppCacheInfo()); + frontend_proxy_.OnCacheSelected(host_id, AppCacheInfo()); } } @@ -125,7 +125,7 @@ void AppCacheDispatcherHost::OnSelectCacheForSharedWorker( if (!backend_impl_.SelectCacheForSharedWorker(host_id, appcache_id)) BadMessageReceived(); } else { - frontend_proxy_.OnCacheSelected(host_id, appcache::AppCacheInfo()); + frontend_proxy_.OnCacheSelected(host_id, AppCacheInfo()); } } @@ -141,7 +141,7 @@ void AppCacheDispatcherHost::OnMarkAsForeignEntry( } void AppCacheDispatcherHost::OnGetResourceList( - int host_id, std::vector<appcache::AppCacheResourceInfo>* params) { + int host_id, std::vector<AppCacheResourceInfo>* params) { if (appcache_service_.get()) backend_impl_.GetResourceList(host_id, params); } @@ -162,7 +162,7 @@ void AppCacheDispatcherHost::OnGetStatus(int host_id, IPC::Message* reply_msg) { return; } - GetStatusCallback(appcache::APPCACHE_STATUS_UNCACHED, reply_msg); + GetStatusCallback(APPCACHE_STATUS_UNCACHED, reply_msg); } void AppCacheDispatcherHost::OnStartUpdate(int host_id, @@ -205,7 +205,7 @@ void AppCacheDispatcherHost::OnSwapCache(int host_id, IPC::Message* reply_msg) { } void AppCacheDispatcherHost::GetStatusCallback( - appcache::AppCacheStatus status, void* param) { + AppCacheStatus status, void* param) { IPC::Message* reply_msg = reinterpret_cast<IPC::Message*>(param); DCHECK_EQ(pending_reply_msg_.get(), reply_msg); AppCacheHostMsg_GetStatus::WriteReplyParams(reply_msg, status); diff --git a/content/browser/appcache/appcache_dispatcher_host.h b/content/browser/appcache/appcache_dispatcher_host.h index 8bad796..3e32898 100644 --- a/content/browser/appcache/appcache_dispatcher_host.h +++ b/content/browser/appcache/appcache_dispatcher_host.h @@ -10,9 +10,9 @@ #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "base/process/process.h" +#include "content/browser/appcache/appcache_backend_impl.h" #include "content/browser/appcache/appcache_frontend_proxy.h" #include "content/public/browser/browser_message_filter.h" -#include "webkit/browser/appcache/appcache_backend_impl.h" namespace content { class ChromeAppCacheService; @@ -54,19 +54,19 @@ class AppCacheDispatcherHost : public BrowserMessageFilter { void OnSwapCache(int host_id, IPC::Message* reply_msg); void OnGetResourceList( int host_id, - std::vector<appcache::AppCacheResourceInfo>* resource_infos); - void GetStatusCallback(appcache::AppCacheStatus status, void* param); + std::vector<AppCacheResourceInfo>* resource_infos); + void GetStatusCallback(AppCacheStatus status, void* param); void StartUpdateCallback(bool result, void* param); void SwapCacheCallback(bool result, void* param); scoped_refptr<ChromeAppCacheService> appcache_service_; AppCacheFrontendProxy frontend_proxy_; - appcache::AppCacheBackendImpl backend_impl_; + AppCacheBackendImpl backend_impl_; - appcache::GetStatusCallback get_status_callback_; - appcache::StartUpdateCallback start_update_callback_; - appcache::SwapCacheCallback swap_cache_callback_; + content::GetStatusCallback get_status_callback_; + content::StartUpdateCallback start_update_callback_; + content::SwapCacheCallback swap_cache_callback_; scoped_ptr<IPC::Message> pending_reply_msg_; // The corresponding ChildProcessHost object's id(). diff --git a/content/browser/appcache/appcache_entry.h b/content/browser/appcache/appcache_entry.h new file mode 100644 index 0000000..5c4fffd --- /dev/null +++ b/content/browser/appcache/appcache_entry.h @@ -0,0 +1,67 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_APPCACHE_APPCACHE_ENTRY_H_ +#define CONTENT_BROWSER_APPCACHE_APPCACHE_ENTRY_H_ + +#include "content/common/appcache_interfaces.h" + +namespace content { + +// A cached entry is identified by a URL and is classified into one +// (or more) categories. URL is not stored here as this class is stored +// with the URL as a map key or the user of this class already knows the URL. +class AppCacheEntry { + public: + + // An entry can be of more than one type so use a bitmask. + // Note: These bit values are stored on disk. + enum Type { + MASTER = 1 << 0, + MANIFEST = 1 << 1, + EXPLICIT = 1 << 2, + FOREIGN = 1 << 3, + FALLBACK = 1 << 4, + INTERCEPT = 1 << 5, + EXECUTABLE = 1 << 6, + }; + + AppCacheEntry() + : types_(0), response_id_(kAppCacheNoResponseId), response_size_(0) {} + + explicit AppCacheEntry(int type) + : types_(type), response_id_(kAppCacheNoResponseId), response_size_(0) {} + + AppCacheEntry(int type, int64 response_id) + : types_(type), response_id_(response_id), response_size_(0) {} + + AppCacheEntry(int type, int64 response_id, int64 response_size) + : types_(type), response_id_(response_id), response_size_(response_size) {} + + int types() const { return types_; } + void add_types(int added_types) { types_ |= added_types; } + bool IsMaster() const { return (types_ & MASTER) != 0; } + bool IsManifest() const { return (types_ & MANIFEST) != 0; } + bool IsExplicit() const { return (types_ & EXPLICIT) != 0; } + bool IsForeign() const { return (types_ & FOREIGN) != 0; } + bool IsFallback() const { return (types_ & FALLBACK) != 0; } + bool IsIntercept() const { return (types_ & INTERCEPT) != 0; } + bool IsExecutable() const { return (types_ & EXECUTABLE) != 0; } + + int64 response_id() const { return response_id_; } + void set_response_id(int64 id) { response_id_ = id; } + bool has_response_id() const { return response_id_ != kAppCacheNoResponseId; } + + int64 response_size() const { return response_size_; } + void set_response_size(int64 size) { response_size_ = size; } + + private: + int types_; + int64 response_id_; + int64 response_size_; +}; + +} // namespace content + +#endif // WEBKIT_APPCACHE_APPCACHE_RESOURCE_H_ diff --git a/content/browser/appcache/appcache_executable_handler.h b/content/browser/appcache/appcache_executable_handler.h new file mode 100644 index 0000000..445a6cd --- /dev/null +++ b/content/browser/appcache/appcache_executable_handler.h @@ -0,0 +1,54 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_APPCACHE_APPCACHE_EXECUTABLE_HANDLER_H_ +#define CONTENT_BROWSER_APPCACHE_APPCACHE_EXECUTABLE_HANDLER_H_ + +#include "base/callback.h" +#include "base/memory/scoped_ptr.h" +#include "content/common/content_export.h" +#include "url/gurl.h" + +namespace net { +class IOBuffer; +class URLRequest; +} + +namespace content { + +// An interface that must be provided by the embedder to support this feature. +class CONTENT_EXPORT AppCacheExecutableHandler { + public: + // A handler can respond in one of 4 ways, if each of the GURL fields + // in 'Response' are empty and use_network is false, an error response is + // synthesized. + struct Response { + GURL cached_resource_url; + GURL redirect_url; + bool use_network; + // TODO: blob + headers would be a good one to provide as well, as it would + // make templating possible. + }; + typedef base::Callback<void(const Response&)> ResponseCallback; + + // Deletion of the handler cancels all pending callbacks. + virtual ~AppCacheExecutableHandler() {} + + virtual void HandleRequest(net::URLRequest* req, + ResponseCallback callback) = 0; +}; + +// A factory to produce instances. +class CONTENT_EXPORT AppCacheExecutableHandlerFactory { + public: + virtual scoped_ptr<AppCacheExecutableHandler> CreateHandler( + const GURL& handler_url, net::IOBuffer* handler_source) = 0; + + protected: + virtual ~AppCacheExecutableHandlerFactory() {} +}; + +} // namespace content + +#endif // CONTENT_BROWSER_APPCACHE_APPCACHE_EXECUTABLE_HANDLER_H_ diff --git a/content/browser/appcache/appcache_frontend_proxy.cc b/content/browser/appcache/appcache_frontend_proxy.cc index ea693e9d..4242304 100644 --- a/content/browser/appcache/appcache_frontend_proxy.cc +++ b/content/browser/appcache/appcache_frontend_proxy.cc @@ -13,18 +13,18 @@ AppCacheFrontendProxy::AppCacheFrontendProxy(IPC::Sender* sender) } void AppCacheFrontendProxy::OnCacheSelected( - int host_id, const appcache::AppCacheInfo& info) { + int host_id, const AppCacheInfo& info) { sender_->Send(new AppCacheMsg_CacheSelected(host_id, info)); } void AppCacheFrontendProxy::OnStatusChanged(const std::vector<int>& host_ids, - appcache::AppCacheStatus status) { + AppCacheStatus status) { sender_->Send(new AppCacheMsg_StatusChanged(host_ids, status)); } void AppCacheFrontendProxy::OnEventRaised(const std::vector<int>& host_ids, - appcache::AppCacheEventID event_id) { - DCHECK_NE(appcache::APPCACHE_PROGRESS_EVENT, + AppCacheEventID event_id) { + DCHECK_NE(APPCACHE_PROGRESS_EVENT, event_id); // See OnProgressEventRaised. sender_->Send(new AppCacheMsg_EventRaised(host_ids, event_id)); } @@ -38,12 +38,12 @@ void AppCacheFrontendProxy::OnProgressEventRaised( void AppCacheFrontendProxy::OnErrorEventRaised( const std::vector<int>& host_ids, - const appcache::AppCacheErrorDetails& details) { + const AppCacheErrorDetails& details) { sender_->Send(new AppCacheMsg_ErrorEventRaised(host_ids, details)); } void AppCacheFrontendProxy::OnLogMessage(int host_id, - appcache::AppCacheLogLevel log_level, + AppCacheLogLevel log_level, const std::string& message) { sender_->Send(new AppCacheMsg_LogMessage(host_id, log_level, message)); } diff --git a/content/browser/appcache/appcache_frontend_proxy.h b/content/browser/appcache/appcache_frontend_proxy.h index 904c8ae..fdd05cd 100644 --- a/content/browser/appcache/appcache_frontend_proxy.h +++ b/content/browser/appcache/appcache_frontend_proxy.h @@ -8,30 +8,30 @@ #include <string> #include <vector> +#include "content/common/appcache_interfaces.h" #include "ipc/ipc_sender.h" -#include "webkit/common/appcache/appcache_interfaces.h" namespace content { // Sends appcache related messages to a child process. -class AppCacheFrontendProxy : public appcache::AppCacheFrontend { +class AppCacheFrontendProxy : public AppCacheFrontend { public: explicit AppCacheFrontendProxy(IPC::Sender* sender); // AppCacheFrontend methods virtual void OnCacheSelected(int host_id, - const appcache::AppCacheInfo& info) OVERRIDE; + const AppCacheInfo& info) OVERRIDE; virtual void OnStatusChanged(const std::vector<int>& host_ids, - appcache::AppCacheStatus status) OVERRIDE; + AppCacheStatus status) OVERRIDE; virtual void OnEventRaised(const std::vector<int>& host_ids, - appcache::AppCacheEventID event_id) OVERRIDE; + AppCacheEventID event_id) OVERRIDE; virtual void OnProgressEventRaised(const std::vector<int>& host_ids, const GURL& url, int num_total, int num_complete) OVERRIDE; virtual void OnErrorEventRaised(const std::vector<int>& host_ids, - const appcache::AppCacheErrorDetails& details) + const AppCacheErrorDetails& details) OVERRIDE; - virtual void OnLogMessage(int host_id, appcache::AppCacheLogLevel log_level, + virtual void OnLogMessage(int host_id, AppCacheLogLevel log_level, const std::string& message) OVERRIDE; virtual void OnContentBlocked(int host_id, const GURL& manifest_url) OVERRIDE; diff --git a/content/browser/appcache/appcache_group.cc b/content/browser/appcache/appcache_group.cc new file mode 100644 index 0000000..60a1024 --- /dev/null +++ b/content/browser/appcache/appcache_group.cc @@ -0,0 +1,264 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/appcache/appcache_group.h" + +#include <algorithm> + +#include "base/bind.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "content/browser/appcache/appcache.h" +#include "content/browser/appcache/appcache_host.h" +#include "content/browser/appcache/appcache_service_impl.h" +#include "content/browser/appcache/appcache_storage.h" +#include "content/browser/appcache/appcache_update_job.h" + +namespace content { + +class AppCacheGroup; + +// Use this helper class because we cannot make AppCacheGroup a derived class +// of AppCacheHost::Observer as it would create a circular dependency between +// AppCacheHost and AppCacheGroup. +class AppCacheGroup::HostObserver : public AppCacheHost::Observer { + public: + explicit HostObserver(AppCacheGroup* group) : group_(group) {} + + // Methods for AppCacheHost::Observer. + virtual void OnCacheSelectionComplete(AppCacheHost* host) OVERRIDE {} // N/A + virtual void OnDestructionImminent(AppCacheHost* host) OVERRIDE { + group_->HostDestructionImminent(host); + } + private: + AppCacheGroup* group_; +}; + +AppCacheGroup::AppCacheGroup(AppCacheStorage* storage, + const GURL& manifest_url, + int64 group_id) + : group_id_(group_id), + manifest_url_(manifest_url), + update_status_(IDLE), + is_obsolete_(false), + is_being_deleted_(false), + newest_complete_cache_(NULL), + update_job_(NULL), + storage_(storage), + is_in_dtor_(false) { + storage_->working_set()->AddGroup(this); + host_observer_.reset(new HostObserver(this)); +} + +AppCacheGroup::~AppCacheGroup() { + DCHECK(old_caches_.empty()); + DCHECK(!newest_complete_cache_); + DCHECK(restart_update_task_.IsCancelled()); + DCHECK(queued_updates_.empty()); + + is_in_dtor_ = true; + + if (update_job_) + delete update_job_; + DCHECK_EQ(IDLE, update_status_); + + storage_->working_set()->RemoveGroup(this); + storage_->DeleteResponses(manifest_url_, newly_deletable_response_ids_); +} + +void AppCacheGroup::AddUpdateObserver(UpdateObserver* observer) { + // If observer being added is a host that has been queued for later update, + // add observer to a different observer list. + AppCacheHost* host = static_cast<AppCacheHost*>(observer); + if (queued_updates_.find(host) != queued_updates_.end()) + queued_observers_.AddObserver(observer); + else + observers_.AddObserver(observer); +} + +void AppCacheGroup::RemoveUpdateObserver(UpdateObserver* observer) { + observers_.RemoveObserver(observer); + queued_observers_.RemoveObserver(observer); +} + +void AppCacheGroup::AddCache(AppCache* complete_cache) { + DCHECK(complete_cache->is_complete()); + complete_cache->set_owning_group(this); + + if (!newest_complete_cache_) { + newest_complete_cache_ = complete_cache; + return; + } + + if (complete_cache->IsNewerThan(newest_complete_cache_)) { + old_caches_.push_back(newest_complete_cache_); + newest_complete_cache_ = complete_cache; + + // Update hosts of older caches to add a reference to the newest cache. + for (Caches::iterator it = old_caches_.begin(); + it != old_caches_.end(); ++it) { + AppCache::AppCacheHosts& hosts = (*it)->associated_hosts(); + for (AppCache::AppCacheHosts::iterator host_it = hosts.begin(); + host_it != hosts.end(); ++host_it) { + (*host_it)->SetSwappableCache(this); + } + } + } else { + old_caches_.push_back(complete_cache); + } +} + +void AppCacheGroup::RemoveCache(AppCache* cache) { + DCHECK(cache->associated_hosts().empty()); + if (cache == newest_complete_cache_) { + AppCache* tmp_cache = newest_complete_cache_; + newest_complete_cache_ = NULL; + tmp_cache->set_owning_group(NULL); // may cause this group to be deleted + } else { + scoped_refptr<AppCacheGroup> protect(this); + + Caches::iterator it = + std::find(old_caches_.begin(), old_caches_.end(), cache); + if (it != old_caches_.end()) { + AppCache* tmp_cache = *it; + old_caches_.erase(it); + tmp_cache->set_owning_group(NULL); // may cause group to be released + } + + if (!is_obsolete() && old_caches_.empty() && + !newly_deletable_response_ids_.empty()) { + storage_->DeleteResponses(manifest_url_, newly_deletable_response_ids_); + newly_deletable_response_ids_.clear(); + } + } +} + +void AppCacheGroup::AddNewlyDeletableResponseIds( + std::vector<int64>* response_ids) { + if (is_being_deleted() || (!is_obsolete() && old_caches_.empty())) { + storage_->DeleteResponses(manifest_url_, *response_ids); + response_ids->clear(); + return; + } + + if (newly_deletable_response_ids_.empty()) { + newly_deletable_response_ids_.swap(*response_ids); + return; + } + newly_deletable_response_ids_.insert( + newly_deletable_response_ids_.end(), + response_ids->begin(), response_ids->end()); + response_ids->clear(); +} + +void AppCacheGroup::StartUpdateWithNewMasterEntry( + AppCacheHost* host, const GURL& new_master_resource) { + DCHECK(!is_obsolete() && !is_being_deleted()); + if (is_in_dtor_) + return; + + if (!update_job_) + update_job_ = new AppCacheUpdateJob(storage_->service(), this); + + update_job_->StartUpdate(host, new_master_resource); + + // Run queued update immediately as an update has been started manually. + if (!restart_update_task_.IsCancelled()) { + restart_update_task_.Cancel(); + RunQueuedUpdates(); + } +} + +void AppCacheGroup::CancelUpdate() { + if (update_job_) { + delete update_job_; + DCHECK(!update_job_); + DCHECK_EQ(IDLE, update_status_); + } +} + +void AppCacheGroup::QueueUpdate(AppCacheHost* host, + const GURL& new_master_resource) { + DCHECK(update_job_ && host && !new_master_resource.is_empty()); + queued_updates_.insert(QueuedUpdates::value_type(host, new_master_resource)); + + // Need to know when host is destroyed. + host->AddObserver(host_observer_.get()); + + // If host is already observing for updates, move host to queued observers + // list so that host is not notified when the current update completes. + if (FindObserver(host, observers_)) { + observers_.RemoveObserver(host); + queued_observers_.AddObserver(host); + } +} + +void AppCacheGroup::RunQueuedUpdates() { + if (!restart_update_task_.IsCancelled()) + restart_update_task_.Cancel(); + + if (queued_updates_.empty()) + return; + + QueuedUpdates updates_to_run; + queued_updates_.swap(updates_to_run); + DCHECK(queued_updates_.empty()); + + for (QueuedUpdates::iterator it = updates_to_run.begin(); + it != updates_to_run.end(); ++it) { + AppCacheHost* host = it->first; + host->RemoveObserver(host_observer_.get()); + if (FindObserver(host, queued_observers_)) { + queued_observers_.RemoveObserver(host); + observers_.AddObserver(host); + } + + if (!is_obsolete() && !is_being_deleted()) + StartUpdateWithNewMasterEntry(host, it->second); + } +} + +bool AppCacheGroup::FindObserver(UpdateObserver* find_me, + const ObserverList<UpdateObserver>& observer_list) { + return observer_list.HasObserver(find_me); +} + +void AppCacheGroup::ScheduleUpdateRestart(int delay_ms) { + DCHECK(restart_update_task_.IsCancelled()); + restart_update_task_.Reset( + base::Bind(&AppCacheGroup::RunQueuedUpdates, this)); + base::MessageLoop::current()->PostDelayedTask( + FROM_HERE, + restart_update_task_.callback(), + base::TimeDelta::FromMilliseconds(delay_ms)); +} + +void AppCacheGroup::HostDestructionImminent(AppCacheHost* host) { + queued_updates_.erase(host); + if (queued_updates_.empty() && !restart_update_task_.IsCancelled()) + restart_update_task_.Cancel(); +} + +void AppCacheGroup::SetUpdateAppCacheStatus(UpdateAppCacheStatus status) { + if (status == update_status_) + return; + + update_status_ = status; + + if (status != IDLE) { + DCHECK(update_job_); + } else { + update_job_ = NULL; + + // Observers may release us in these callbacks, so we protect against + // deletion by adding an extra ref in this scope (but only if we're not + // in our destructor). + scoped_refptr<AppCacheGroup> protect(is_in_dtor_ ? NULL : this); + FOR_EACH_OBSERVER(UpdateObserver, observers_, OnUpdateComplete(this)); + if (!queued_updates_.empty()) + ScheduleUpdateRestart(kUpdateRestartDelayMs); + } +} + +} // namespace content diff --git a/content/browser/appcache/appcache_group.h b/content/browser/appcache/appcache_group.h new file mode 100644 index 0000000..c79318f --- /dev/null +++ b/content/browser/appcache/appcache_group.h @@ -0,0 +1,177 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_APPCACHE_APPCACHE_GROUP_H_ +#define CONTENT_BROWSER_APPCACHE_APPCACHE_GROUP_H_ + +#include <map> +#include <vector> + +#include "base/cancelable_callback.h" +#include "base/gtest_prod_util.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/observer_list.h" +#include "base/time/time.h" +#include "content/common/content_export.h" +#include "url/gurl.h" + +namespace content { +FORWARD_DECLARE_TEST(AppCacheGroupTest, StartUpdate); +FORWARD_DECLARE_TEST(AppCacheGroupTest, CancelUpdate); +FORWARD_DECLARE_TEST(AppCacheGroupTest, QueueUpdate); +FORWARD_DECLARE_TEST(AppCacheUpdateJobTest, AlreadyChecking); +FORWARD_DECLARE_TEST(AppCacheUpdateJobTest, AlreadyDownloading); +class AppCacheUpdateJobTest; +class MockAppCacheStorage; +} + +namespace content { + +class AppCache; +class AppCacheHost; +class AppCacheStorage; +class AppCacheUpdateJob; +class HostObserver; + +// Collection of application caches identified by the same manifest URL. +// A group exists as long as it is in use by a host or is being updated. +class CONTENT_EXPORT AppCacheGroup + : public base::RefCounted<AppCacheGroup> { + public: + + class CONTENT_EXPORT UpdateObserver { + public: + // Called just after an appcache update has completed. + virtual void OnUpdateComplete(AppCacheGroup* group) = 0; + virtual ~UpdateObserver() {} + }; + + enum UpdateAppCacheStatus { + IDLE, + CHECKING, + DOWNLOADING, + }; + + AppCacheGroup(AppCacheStorage* storage, const GURL& manifest_url, + int64 group_id); + + // Adds/removes an update observer, the AppCacheGroup does not take + // ownership of the observer. + void AddUpdateObserver(UpdateObserver* observer); + void RemoveUpdateObserver(UpdateObserver* observer); + + int64 group_id() const { return group_id_; } + const GURL& manifest_url() const { return manifest_url_; } + const base::Time& creation_time() const { return creation_time_; } + void set_creation_time(const base::Time& time) { creation_time_ = time; } + bool is_obsolete() const { return is_obsolete_; } + void set_obsolete(bool value) { is_obsolete_ = value; } + + bool is_being_deleted() const { return is_being_deleted_; } + void set_being_deleted(bool value) { is_being_deleted_ = value; } + + AppCache* newest_complete_cache() const { return newest_complete_cache_; } + + void AddCache(AppCache* complete_cache); + void RemoveCache(AppCache* cache); + bool HasCache() const { return newest_complete_cache_ != NULL; } + + void AddNewlyDeletableResponseIds(std::vector<int64>* response_ids); + + UpdateAppCacheStatus update_status() const { return update_status_; } + + // Starts an update via update() javascript API. + void StartUpdate() { + StartUpdateWithHost(NULL); + } + + // Starts an update for a doc loaded from an application cache. + void StartUpdateWithHost(AppCacheHost* host) { + StartUpdateWithNewMasterEntry(host, GURL()); + } + + // Starts an update for a doc loaded using HTTP GET or equivalent with + // an <html> tag manifest attribute value that matches this group's + // manifest url. + void StartUpdateWithNewMasterEntry(AppCacheHost* host, + const GURL& new_master_resource); + + // Cancels an update if one is running. + void CancelUpdate(); + + private: + class HostObserver; + + friend class base::RefCounted<AppCacheGroup>; + friend class content::AppCacheUpdateJobTest; + friend class content::MockAppCacheStorage; // for old_caches() + friend class AppCacheUpdateJob; + + ~AppCacheGroup(); + + typedef std::vector<AppCache*> Caches; + typedef std::map<AppCacheHost*, GURL> QueuedUpdates; + + static const int kUpdateRestartDelayMs = 1000; + + AppCacheUpdateJob* update_job() { return update_job_; } + void SetUpdateAppCacheStatus(UpdateAppCacheStatus status); + + void NotifyContentBlocked(); + + const Caches& old_caches() const { return old_caches_; } + + // Update cannot be processed at this time. Queue it for a later run. + void QueueUpdate(AppCacheHost* host, const GURL& new_master_resource); + void RunQueuedUpdates(); + bool FindObserver(UpdateObserver* find_me, + const ObserverList<UpdateObserver>& observer_list); + void ScheduleUpdateRestart(int delay_ms); + void HostDestructionImminent(AppCacheHost* host); + + const int64 group_id_; + const GURL manifest_url_; + base::Time creation_time_; + UpdateAppCacheStatus update_status_; + bool is_obsolete_; + bool is_being_deleted_; + std::vector<int64> newly_deletable_response_ids_; + + // Old complete app caches. + Caches old_caches_; + + // Newest cache in this group to be complete, aka relevant cache. + AppCache* newest_complete_cache_; + + // Current update job for this group, if any. + AppCacheUpdateJob* update_job_; + + // Central storage object. + AppCacheStorage* storage_; + + // List of objects observing this group. + ObserverList<UpdateObserver> observers_; + + // Updates that have been queued for the next run. + QueuedUpdates queued_updates_; + ObserverList<UpdateObserver> queued_observers_; + base::CancelableClosure restart_update_task_; + scoped_ptr<HostObserver> host_observer_; + + // True if we're in our destructor. + bool is_in_dtor_; + + FRIEND_TEST_ALL_PREFIXES(content::AppCacheGroupTest, StartUpdate); + FRIEND_TEST_ALL_PREFIXES(content::AppCacheGroupTest, CancelUpdate); + FRIEND_TEST_ALL_PREFIXES(content::AppCacheGroupTest, QueueUpdate); + FRIEND_TEST_ALL_PREFIXES(content::AppCacheUpdateJobTest, AlreadyChecking); + FRIEND_TEST_ALL_PREFIXES(content::AppCacheUpdateJobTest, AlreadyDownloading); + + DISALLOW_COPY_AND_ASSIGN(AppCacheGroup); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_APPCACHE_APPCACHE_GROUP_H_ diff --git a/content/browser/appcache/appcache_group_unittest.cc b/content/browser/appcache/appcache_group_unittest.cc index cec20fa..ad45b3b 100644 --- a/content/browser/appcache/appcache_group_unittest.cc +++ b/content/browser/appcache/appcache_group_unittest.cc @@ -5,47 +5,40 @@ #include <string> #include "base/message_loop/message_loop.h" +#include "content/browser/appcache/appcache.h" +#include "content/browser/appcache/appcache_group.h" +#include "content/browser/appcache/appcache_host.h" +#include "content/browser/appcache/appcache_update_job.h" #include "content/browser/appcache/mock_appcache_service.h" +#include "content/common/appcache_interfaces.h" #include "testing/gtest/include/gtest/gtest.h" -#include "webkit/browser/appcache/appcache.h" -#include "webkit/browser/appcache/appcache_group.h" -#include "webkit/browser/appcache/appcache_host.h" -#include "webkit/browser/appcache/appcache_update_job.h" -#include "webkit/common/appcache/appcache_interfaces.h" - -using appcache::AppCache; -using appcache::AppCacheFrontend; -using appcache::AppCacheGroup; -using appcache::AppCacheHost; -using appcache::AppCacheServiceImpl; -using appcache::AppCacheUpdateJob; namespace { -class TestAppCacheFrontend : public appcache::AppCacheFrontend { +class TestAppCacheFrontend : public content::AppCacheFrontend { public: TestAppCacheFrontend() : last_host_id_(-1), last_cache_id_(-1), - last_status_(appcache::APPCACHE_STATUS_OBSOLETE) { + last_status_(content::APPCACHE_STATUS_OBSOLETE) { } virtual void OnCacheSelected( - int host_id, const appcache::AppCacheInfo& info) OVERRIDE { + int host_id, const content::AppCacheInfo& info) OVERRIDE { last_host_id_ = host_id; last_cache_id_ = info.cache_id; last_status_ = info.status; } virtual void OnStatusChanged(const std::vector<int>& host_ids, - appcache::AppCacheStatus status) OVERRIDE { + content::AppCacheStatus status) OVERRIDE { } virtual void OnEventRaised(const std::vector<int>& host_ids, - appcache::AppCacheEventID event_id) OVERRIDE { + content::AppCacheEventID event_id) OVERRIDE { } virtual void OnErrorEventRaised(const std::vector<int>& host_ids, - const appcache::AppCacheErrorDetails& details) + const content::AppCacheErrorDetails& details) OVERRIDE {} virtual void OnProgressEventRaised(const std::vector<int>& host_ids, @@ -53,7 +46,7 @@ class TestAppCacheFrontend : public appcache::AppCacheFrontend { int num_total, int num_complete) OVERRIDE { } - virtual void OnLogMessage(int host_id, appcache::AppCacheLogLevel log_level, + virtual void OnLogMessage(int host_id, content::AppCacheLogLevel log_level, const std::string& message) OVERRIDE { } @@ -63,7 +56,7 @@ class TestAppCacheFrontend : public appcache::AppCacheFrontend { int last_host_id_; int64 last_cache_id_; - appcache::AppCacheStatus last_status_; + content::AppCacheStatus last_status_; }; } // namespace anon @@ -197,12 +190,12 @@ TEST_F(AppCacheGroupTest, CleanupUnusedGroup) { host1.AssociateCompleteCache(cache1); EXPECT_EQ(frontend.last_host_id_, host1.host_id()); EXPECT_EQ(frontend.last_cache_id_, cache1->cache_id()); - EXPECT_EQ(frontend.last_status_, appcache::APPCACHE_STATUS_IDLE); + EXPECT_EQ(frontend.last_status_, APPCACHE_STATUS_IDLE); host2.AssociateCompleteCache(cache1); EXPECT_EQ(frontend.last_host_id_, host2.host_id()); EXPECT_EQ(frontend.last_cache_id_, cache1->cache_id()); - EXPECT_EQ(frontend.last_status_, appcache::APPCACHE_STATUS_IDLE); + EXPECT_EQ(frontend.last_status_, APPCACHE_STATUS_IDLE); AppCache* cache2 = new AppCache(service.storage(), 222); cache2->set_complete(true); @@ -214,8 +207,8 @@ TEST_F(AppCacheGroupTest, CleanupUnusedGroup) { host1.AssociateNoCache(GURL()); host2.AssociateNoCache(GURL()); EXPECT_EQ(frontend.last_host_id_, host2.host_id()); - EXPECT_EQ(frontend.last_cache_id_, appcache::kAppCacheNoCacheId); - EXPECT_EQ(frontend.last_status_, appcache::APPCACHE_STATUS_UNCACHED); + EXPECT_EQ(frontend.last_cache_id_, kAppCacheNoCacheId); + EXPECT_EQ(frontend.last_status_, APPCACHE_STATUS_UNCACHED); } TEST_F(AppCacheGroupTest, StartUpdate) { diff --git a/content/browser/appcache/appcache_histograms.cc b/content/browser/appcache/appcache_histograms.cc new file mode 100644 index 0000000..ff221b0 --- /dev/null +++ b/content/browser/appcache/appcache_histograms.cc @@ -0,0 +1,154 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/appcache/appcache_histograms.h" + +#include "base/metrics/histogram.h" + +namespace content { + +static std::string OriginToCustomHistogramSuffix(const GURL& origin_url) { + if (origin_url.host() == "docs.google.com") + return ".Docs"; + return std::string(); +} + +void AppCacheHistograms::CountInitResult(InitResultType init_result) { + UMA_HISTOGRAM_ENUMERATION( + "appcache.InitResult", + init_result, NUM_INIT_RESULT_TYPES); +} + +void AppCacheHistograms::CountReinitAttempt(bool repeated_attempt) { + UMA_HISTOGRAM_BOOLEAN("appcache.ReinitAttempt", repeated_attempt); +} + +void AppCacheHistograms::CountCorruptionDetected() { + UMA_HISTOGRAM_BOOLEAN("appcache.CorruptionDetected", true); +} + +void AppCacheHistograms::CountUpdateJobResult( + AppCacheUpdateJob::ResultType result, + const GURL& origin_url) { + UMA_HISTOGRAM_ENUMERATION( + "appcache.UpdateJobResult", + result, AppCacheUpdateJob::NUM_UPDATE_JOB_RESULT_TYPES); + + const std::string suffix = OriginToCustomHistogramSuffix(origin_url); + if (!suffix.empty()) { + base::LinearHistogram::FactoryGet( + "appcache.UpdateJobResult" + suffix, + 1, + AppCacheUpdateJob::NUM_UPDATE_JOB_RESULT_TYPES, + AppCacheUpdateJob::NUM_UPDATE_JOB_RESULT_TYPES + 1, + base::HistogramBase::kUmaTargetedHistogramFlag)->Add(result); + } +} + +void AppCacheHistograms::CountCheckResponseResult( + CheckResponseResultType result) { + UMA_HISTOGRAM_ENUMERATION( + "appcache.CheckResponseResult", + result, NUM_CHECK_RESPONSE_RESULT_TYPES); +} + +void AppCacheHistograms::CountResponseRetrieval( + bool success, bool is_main_resource, const GURL& origin_url) { + std::string label; + if (is_main_resource) { + label = "appcache.MainResourceResponseRetrieval"; + UMA_HISTOGRAM_BOOLEAN(label, success); + } else { + label = "appcache.SubResourceResponseRetrieval"; + UMA_HISTOGRAM_BOOLEAN(label, success); + } + const std::string suffix = OriginToCustomHistogramSuffix(origin_url); + if (!suffix.empty()) { + base::BooleanHistogram::FactoryGet( + label + suffix, + base::HistogramBase::kUmaTargetedHistogramFlag)->Add(success); + } +} + +void AppCacheHistograms::LogUpdateFailureStats( + const GURL& origin_url, + int percent_complete, + bool was_stalled, + bool was_off_origin_resource_failure) { + const std::string suffix = OriginToCustomHistogramSuffix(origin_url); + + std::string label = "appcache.UpdateProgressAtPointOfFaliure"; + UMA_HISTOGRAM_PERCENTAGE(label, percent_complete); + if (!suffix.empty()) { + base::LinearHistogram::FactoryGet( + label + suffix, + 1, 101, 102, + base::HistogramBase::kUmaTargetedHistogramFlag)->Add(percent_complete); + } + + label = "appcache.UpdateWasStalledAtPointOfFailure"; + UMA_HISTOGRAM_BOOLEAN(label, was_stalled); + if (!suffix.empty()) { + base::BooleanHistogram::FactoryGet( + label + suffix, + base::HistogramBase::kUmaTargetedHistogramFlag)->Add(was_stalled); + } + + label = "appcache.UpdateWasOffOriginAtPointOfFailure"; + UMA_HISTOGRAM_BOOLEAN(label, was_off_origin_resource_failure); + if (!suffix.empty()) { + base::BooleanHistogram::FactoryGet( + label + suffix, + base::HistogramBase::kUmaTargetedHistogramFlag)->Add( + was_off_origin_resource_failure); + } +} + +void AppCacheHistograms::AddTaskQueueTimeSample( + const base::TimeDelta& duration) { + UMA_HISTOGRAM_TIMES("appcache.TaskQueueTime", duration); +} + +void AppCacheHistograms::AddTaskRunTimeSample( + const base::TimeDelta& duration) { + UMA_HISTOGRAM_TIMES("appcache.TaskRunTime", duration); +} + +void AppCacheHistograms::AddCompletionQueueTimeSample( + const base::TimeDelta& duration) { + UMA_HISTOGRAM_TIMES("appcache.CompletionQueueTime", duration); +} + +void AppCacheHistograms::AddCompletionRunTimeSample( + const base::TimeDelta& duration) { + UMA_HISTOGRAM_TIMES("appcache.CompletionRunTime", duration); +} + +void AppCacheHistograms::AddNetworkJobStartDelaySample( + const base::TimeDelta& duration) { + UMA_HISTOGRAM_TIMES("appcache.JobStartDelay.Network", duration); +} + +void AppCacheHistograms::AddErrorJobStartDelaySample( + const base::TimeDelta& duration) { + UMA_HISTOGRAM_TIMES("appcache.JobStartDelay.Error", duration); +} + +void AppCacheHistograms::AddAppCacheJobStartDelaySample( + const base::TimeDelta& duration) { + UMA_HISTOGRAM_TIMES("appcache.JobStartDelay.AppCache", duration); +} + +void AppCacheHistograms::AddMissingManifestEntrySample() { + UMA_HISTOGRAM_BOOLEAN("appcache.MissingManifestEntry", true); +} + +void AppCacheHistograms::AddMissingManifestDetectedAtCallsite( + MissingManifestCallsiteType callsite) { + UMA_HISTOGRAM_ENUMERATION( + "appcache.MissingManifestDetectedAtCallsite", + callsite, NUM_MISSING_MANIFEST_CALLSITE_TYPES); +} + +} // namespace content diff --git a/content/browser/appcache/appcache_histograms.h b/content/browser/appcache/appcache_histograms.h new file mode 100644 index 0000000..0fcc54c --- /dev/null +++ b/content/browser/appcache/appcache_histograms.h @@ -0,0 +1,63 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_APPCACHE_APPCACHE_HISTOGRAMS_H_ +#define CONTENT_BROWSER_APPCACHE_APPCACHE_HISTOGRAMS_H_ + +#include "base/basictypes.h" +#include "content/browser/appcache/appcache_update_job.h" + +namespace base { +class TimeDelta; +} + +namespace content { + +class AppCacheHistograms { + public: + enum InitResultType { + INIT_OK, SQL_DATABASE_ERROR, DISK_CACHE_ERROR, + NUM_INIT_RESULT_TYPES + }; + static void CountInitResult(InitResultType init_result); + static void CountReinitAttempt(bool repeated_attempt); + static void CountCorruptionDetected(); + static void CountUpdateJobResult(AppCacheUpdateJob::ResultType result, + const GURL& origin_url); + enum CheckResponseResultType { + RESPONSE_OK, MANIFEST_OUT_OF_DATE, RESPONSE_OUT_OF_DATE, ENTRY_NOT_FOUND, + READ_HEADERS_ERROR, READ_DATA_ERROR, UNEXPECTED_DATA_SIZE, CHECK_CANCELED, + NUM_CHECK_RESPONSE_RESULT_TYPES + }; + static void CountCheckResponseResult(CheckResponseResultType result); + static void CountResponseRetrieval( + bool success, bool is_main_resource, const GURL& origin_url); + static void LogUpdateFailureStats( + const GURL& origin_url, + int percent_complete, + bool was_making_progress, + bool off_origin_resource_failure); + static void AddTaskQueueTimeSample(const base::TimeDelta& duration); + static void AddTaskRunTimeSample(const base::TimeDelta& duration); + static void AddCompletionQueueTimeSample(const base::TimeDelta& duration); + static void AddCompletionRunTimeSample(const base::TimeDelta& duration); + static void AddNetworkJobStartDelaySample(const base::TimeDelta& duration); + static void AddErrorJobStartDelaySample(const base::TimeDelta& duration); + static void AddAppCacheJobStartDelaySample(const base::TimeDelta& duration); + static void AddMissingManifestEntrySample(); + + enum MissingManifestCallsiteType { + CALLSITE_0, CALLSITE_1, CALLSITE_2, CALLSITE_3, + NUM_MISSING_MANIFEST_CALLSITE_TYPES + }; + static void AddMissingManifestDetectedAtCallsite( + MissingManifestCallsiteType type); + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(AppCacheHistograms); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_APPCACHE_APPCACHE_HISTOGRAMS_H_ diff --git a/content/browser/appcache/appcache_host.cc b/content/browser/appcache/appcache_host.cc new file mode 100644 index 0000000..db04dad --- /dev/null +++ b/content/browser/appcache/appcache_host.cc @@ -0,0 +1,546 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/appcache/appcache_host.h" + +#include "base/logging.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "content/browser/appcache/appcache.h" +#include "content/browser/appcache/appcache_backend_impl.h" +#include "content/browser/appcache/appcache_policy.h" +#include "content/browser/appcache/appcache_request_handler.h" +#include "net/url_request/url_request.h" +#include "webkit/browser/quota/quota_manager_proxy.h" + +namespace content { + +namespace { + +void FillCacheInfo(const AppCache* cache, + const GURL& manifest_url, + AppCacheStatus status, AppCacheInfo* info) { + info->manifest_url = manifest_url; + info->status = status; + + if (!cache) + return; + + info->cache_id = cache->cache_id(); + + if (!cache->is_complete()) + return; + + DCHECK(cache->owning_group()); + info->is_complete = true; + info->group_id = cache->owning_group()->group_id(); + info->last_update_time = cache->update_time(); + info->creation_time = cache->owning_group()->creation_time(); + info->size = cache->cache_size(); +} + +} // Anonymous namespace + +AppCacheHost::AppCacheHost(int host_id, AppCacheFrontend* frontend, + AppCacheServiceImpl* service) + : host_id_(host_id), + spawning_host_id_(kAppCacheNoHostId), spawning_process_id_(0), + parent_host_id_(kAppCacheNoHostId), parent_process_id_(0), + pending_main_resource_cache_id_(kAppCacheNoCacheId), + pending_selected_cache_id_(kAppCacheNoCacheId), + frontend_(frontend), service_(service), + storage_(service->storage()), + pending_callback_param_(NULL), + main_resource_was_namespace_entry_(false), + main_resource_blocked_(false), + associated_cache_info_pending_(false) { + service_->AddObserver(this); +} + +AppCacheHost::~AppCacheHost() { + service_->RemoveObserver(this); + FOR_EACH_OBSERVER(Observer, observers_, OnDestructionImminent(this)); + if (associated_cache_.get()) + associated_cache_->UnassociateHost(this); + if (group_being_updated_.get()) + group_being_updated_->RemoveUpdateObserver(this); + storage()->CancelDelegateCallbacks(this); + if (service()->quota_manager_proxy() && !origin_in_use_.is_empty()) + service()->quota_manager_proxy()->NotifyOriginNoLongerInUse(origin_in_use_); +} + +void AppCacheHost::AddObserver(Observer* observer) { + observers_.AddObserver(observer); +} + +void AppCacheHost::RemoveObserver(Observer* observer) { + observers_.RemoveObserver(observer); +} + +void AppCacheHost::SelectCache(const GURL& document_url, + const int64 cache_document_was_loaded_from, + const GURL& manifest_url) { + DCHECK(pending_start_update_callback_.is_null() && + pending_swap_cache_callback_.is_null() && + pending_get_status_callback_.is_null() && + !is_selection_pending()); + + origin_in_use_ = document_url.GetOrigin(); + if (service()->quota_manager_proxy() && !origin_in_use_.is_empty()) + service()->quota_manager_proxy()->NotifyOriginInUse(origin_in_use_); + + if (main_resource_blocked_) + frontend_->OnContentBlocked(host_id_, + blocked_manifest_url_); + + // 6.9.6 The application cache selection algorithm. + // The algorithm is started here and continues in FinishCacheSelection, + // after cache or group loading is complete. + // Note: Foreign entries are detected on the client side and + // MarkAsForeignEntry is called in that case, so that detection + // step is skipped here. See WebApplicationCacheHostImpl.cc + + if (cache_document_was_loaded_from != kAppCacheNoCacheId) { + LoadSelectedCache(cache_document_was_loaded_from); + return; + } + + if (!manifest_url.is_empty() && + (manifest_url.GetOrigin() == document_url.GetOrigin())) { + DCHECK(!first_party_url_.is_empty()); + AppCachePolicy* policy = service()->appcache_policy(); + if (policy && + !policy->CanCreateAppCache(manifest_url, first_party_url_)) { + FinishCacheSelection(NULL, NULL); + std::vector<int> host_ids(1, host_id_); + frontend_->OnEventRaised(host_ids, APPCACHE_CHECKING_EVENT); + frontend_->OnErrorEventRaised( + host_ids, + AppCacheErrorDetails( + "Cache creation was blocked by the content policy", + APPCACHE_POLICY_ERROR, + GURL(), + 0, + false /*is_cross_origin*/)); + frontend_->OnContentBlocked(host_id_, manifest_url); + return; + } + + // Note: The client detects if the document was not loaded using HTTP GET + // and invokes SelectCache without a manifest url, so that detection step + // is also skipped here. See WebApplicationCacheHostImpl.cc + set_preferred_manifest_url(manifest_url); + new_master_entry_url_ = document_url; + LoadOrCreateGroup(manifest_url); + return; + } + + // TODO(michaeln): If there was a manifest URL, the user agent may report + // to the user that it was ignored, to aid in application development. + FinishCacheSelection(NULL, NULL); +} + +void AppCacheHost::SelectCacheForWorker(int parent_process_id, + int parent_host_id) { + DCHECK(pending_start_update_callback_.is_null() && + pending_swap_cache_callback_.is_null() && + pending_get_status_callback_.is_null() && + !is_selection_pending()); + + parent_process_id_ = parent_process_id; + parent_host_id_ = parent_host_id; + FinishCacheSelection(NULL, NULL); +} + +void AppCacheHost::SelectCacheForSharedWorker(int64 appcache_id) { + DCHECK(pending_start_update_callback_.is_null() && + pending_swap_cache_callback_.is_null() && + pending_get_status_callback_.is_null() && + !is_selection_pending()); + + if (appcache_id != kAppCacheNoCacheId) { + LoadSelectedCache(appcache_id); + return; + } + FinishCacheSelection(NULL, NULL); +} + +// TODO(michaeln): change method name to MarkEntryAsForeign for consistency +void AppCacheHost::MarkAsForeignEntry(const GURL& document_url, + int64 cache_document_was_loaded_from) { + // The document url is not the resource url in the fallback case. + storage()->MarkEntryAsForeign( + main_resource_was_namespace_entry_ ? namespace_entry_url_ : document_url, + cache_document_was_loaded_from); + SelectCache(document_url, kAppCacheNoCacheId, GURL()); +} + +void AppCacheHost::GetStatusWithCallback(const GetStatusCallback& callback, + void* callback_param) { + DCHECK(pending_start_update_callback_.is_null() && + pending_swap_cache_callback_.is_null() && + pending_get_status_callback_.is_null()); + + pending_get_status_callback_ = callback; + pending_callback_param_ = callback_param; + if (is_selection_pending()) + return; + + DoPendingGetStatus(); +} + +void AppCacheHost::DoPendingGetStatus() { + DCHECK_EQ(false, pending_get_status_callback_.is_null()); + + pending_get_status_callback_.Run(GetStatus(), pending_callback_param_); + pending_get_status_callback_.Reset(); + pending_callback_param_ = NULL; +} + +void AppCacheHost::StartUpdateWithCallback(const StartUpdateCallback& callback, + void* callback_param) { + DCHECK(pending_start_update_callback_.is_null() && + pending_swap_cache_callback_.is_null() && + pending_get_status_callback_.is_null()); + + pending_start_update_callback_ = callback; + pending_callback_param_ = callback_param; + if (is_selection_pending()) + return; + + DoPendingStartUpdate(); +} + +void AppCacheHost::DoPendingStartUpdate() { + DCHECK_EQ(false, pending_start_update_callback_.is_null()); + + // 6.9.8 Application cache API + bool success = false; + if (associated_cache_.get() && associated_cache_->owning_group()) { + AppCacheGroup* group = associated_cache_->owning_group(); + if (!group->is_obsolete() && !group->is_being_deleted()) { + success = true; + group->StartUpdate(); + } + } + + pending_start_update_callback_.Run(success, pending_callback_param_); + pending_start_update_callback_.Reset(); + pending_callback_param_ = NULL; +} + +void AppCacheHost::SwapCacheWithCallback(const SwapCacheCallback& callback, + void* callback_param) { + DCHECK(pending_start_update_callback_.is_null() && + pending_swap_cache_callback_.is_null() && + pending_get_status_callback_.is_null()); + + pending_swap_cache_callback_ = callback; + pending_callback_param_ = callback_param; + if (is_selection_pending()) + return; + + DoPendingSwapCache(); +} + +void AppCacheHost::DoPendingSwapCache() { + DCHECK_EQ(false, pending_swap_cache_callback_.is_null()); + + // 6.9.8 Application cache API + bool success = false; + if (associated_cache_.get() && associated_cache_->owning_group()) { + if (associated_cache_->owning_group()->is_obsolete()) { + success = true; + AssociateNoCache(GURL()); + } else if (swappable_cache_.get()) { + DCHECK(swappable_cache_.get() == + swappable_cache_->owning_group()->newest_complete_cache()); + success = true; + AssociateCompleteCache(swappable_cache_.get()); + } + } + + pending_swap_cache_callback_.Run(success, pending_callback_param_); + pending_swap_cache_callback_.Reset(); + pending_callback_param_ = NULL; +} + +void AppCacheHost::SetSpawningHostId( + int spawning_process_id, int spawning_host_id) { + spawning_process_id_ = spawning_process_id; + spawning_host_id_ = spawning_host_id; +} + +const AppCacheHost* AppCacheHost::GetSpawningHost() const { + AppCacheBackendImpl* backend = service_->GetBackend(spawning_process_id_); + return backend ? backend->GetHost(spawning_host_id_) : NULL; +} + +AppCacheHost* AppCacheHost::GetParentAppCacheHost() const { + DCHECK(is_for_dedicated_worker()); + AppCacheBackendImpl* backend = service_->GetBackend(parent_process_id_); + return backend ? backend->GetHost(parent_host_id_) : NULL; +} + +AppCacheRequestHandler* AppCacheHost::CreateRequestHandler( + net::URLRequest* request, + ResourceType::Type resource_type) { + if (is_for_dedicated_worker()) { + AppCacheHost* parent_host = GetParentAppCacheHost(); + if (parent_host) + return parent_host->CreateRequestHandler(request, resource_type); + return NULL; + } + + if (AppCacheRequestHandler::IsMainResourceType(resource_type)) { + // Store the first party origin so that it can be used later in SelectCache + // for checking whether the creation of the appcache is allowed. + first_party_url_ = request->first_party_for_cookies(); + return new AppCacheRequestHandler(this, resource_type); + } + + if ((associated_cache() && associated_cache()->is_complete()) || + is_selection_pending()) { + return new AppCacheRequestHandler(this, resource_type); + } + return NULL; +} + +void AppCacheHost::GetResourceList( + AppCacheResourceInfoVector* resource_infos) { + if (associated_cache_.get() && associated_cache_->is_complete()) + associated_cache_->ToResourceInfoVector(resource_infos); +} + +AppCacheStatus AppCacheHost::GetStatus() { + // 6.9.8 Application cache API + AppCache* cache = associated_cache(); + if (!cache) + return APPCACHE_STATUS_UNCACHED; + + // A cache without an owning group represents the cache being constructed + // during the application cache update process. + if (!cache->owning_group()) + return APPCACHE_STATUS_DOWNLOADING; + + if (cache->owning_group()->is_obsolete()) + return APPCACHE_STATUS_OBSOLETE; + if (cache->owning_group()->update_status() == AppCacheGroup::CHECKING) + return APPCACHE_STATUS_CHECKING; + if (cache->owning_group()->update_status() == AppCacheGroup::DOWNLOADING) + return APPCACHE_STATUS_DOWNLOADING; + if (swappable_cache_.get()) + return APPCACHE_STATUS_UPDATE_READY; + return APPCACHE_STATUS_IDLE; +} + +void AppCacheHost::LoadOrCreateGroup(const GURL& manifest_url) { + DCHECK(manifest_url.is_valid()); + pending_selected_manifest_url_ = manifest_url; + storage()->LoadOrCreateGroup(manifest_url, this); +} + +void AppCacheHost::OnGroupLoaded(AppCacheGroup* group, + const GURL& manifest_url) { + DCHECK(manifest_url == pending_selected_manifest_url_); + pending_selected_manifest_url_ = GURL(); + FinishCacheSelection(NULL, group); +} + +void AppCacheHost::LoadSelectedCache(int64 cache_id) { + DCHECK(cache_id != kAppCacheNoCacheId); + pending_selected_cache_id_ = cache_id; + storage()->LoadCache(cache_id, this); +} + +void AppCacheHost::OnCacheLoaded(AppCache* cache, int64 cache_id) { + if (cache_id == pending_main_resource_cache_id_) { + pending_main_resource_cache_id_ = kAppCacheNoCacheId; + main_resource_cache_ = cache; + } else if (cache_id == pending_selected_cache_id_) { + pending_selected_cache_id_ = kAppCacheNoCacheId; + FinishCacheSelection(cache, NULL); + } +} + +void AppCacheHost::FinishCacheSelection( + AppCache *cache, AppCacheGroup* group) { + DCHECK(!associated_cache()); + + // 6.9.6 The application cache selection algorithm + if (cache) { + // If document was loaded from an application cache, Associate document + // with the application cache from which it was loaded. Invoke the + // application cache update process for that cache and with the browsing + // context being navigated. + DCHECK(cache->owning_group()); + DCHECK(new_master_entry_url_.is_empty()); + DCHECK_EQ(cache->owning_group()->manifest_url(), preferred_manifest_url_); + AppCacheGroup* owing_group = cache->owning_group(); + const char* kFormatString = + "Document was loaded from Application Cache with manifest %s"; + frontend_->OnLogMessage( + host_id_, APPCACHE_LOG_INFO, + base::StringPrintf( + kFormatString, owing_group->manifest_url().spec().c_str())); + AssociateCompleteCache(cache); + if (!owing_group->is_obsolete() && !owing_group->is_being_deleted()) { + owing_group->StartUpdateWithHost(this); + ObserveGroupBeingUpdated(owing_group); + } + } else if (group && !group->is_being_deleted()) { + // If document was loaded using HTTP GET or equivalent, and, there is a + // manifest URL, and manifest URL has the same origin as document. + // Invoke the application cache update process for manifest URL, with + // the browsing context being navigated, and with document and the + // resource from which document was loaded as the new master resourse. + DCHECK(!group->is_obsolete()); + DCHECK(new_master_entry_url_.is_valid()); + DCHECK_EQ(group->manifest_url(), preferred_manifest_url_); + const char* kFormatString = group->HasCache() ? + "Adding master entry to Application Cache with manifest %s" : + "Creating Application Cache with manifest %s"; + frontend_->OnLogMessage( + host_id_, APPCACHE_LOG_INFO, + base::StringPrintf(kFormatString, + group->manifest_url().spec().c_str())); + // The UpdateJob may produce one for us later. + AssociateNoCache(preferred_manifest_url_); + group->StartUpdateWithNewMasterEntry(this, new_master_entry_url_); + ObserveGroupBeingUpdated(group); + } else { + // Otherwise, the Document is not associated with any application cache. + new_master_entry_url_ = GURL(); + AssociateNoCache(GURL()); + } + + // Respond to pending callbacks now that we have a selection. + if (!pending_get_status_callback_.is_null()) + DoPendingGetStatus(); + else if (!pending_start_update_callback_.is_null()) + DoPendingStartUpdate(); + else if (!pending_swap_cache_callback_.is_null()) + DoPendingSwapCache(); + + FOR_EACH_OBSERVER(Observer, observers_, OnCacheSelectionComplete(this)); +} + +void AppCacheHost::OnServiceReinitialized( + AppCacheStorageReference* old_storage_ref) { + // We continue to use the disabled instance, but arrange for its + // deletion when its no longer needed. + if (old_storage_ref->storage() == storage()) + disabled_storage_reference_ = old_storage_ref; +} + +void AppCacheHost::ObserveGroupBeingUpdated(AppCacheGroup* group) { + DCHECK(!group_being_updated_.get()); + group_being_updated_ = group; + newest_cache_of_group_being_updated_ = group->newest_complete_cache(); + group->AddUpdateObserver(this); +} + +void AppCacheHost::OnUpdateComplete(AppCacheGroup* group) { + DCHECK_EQ(group, group_being_updated_); + group->RemoveUpdateObserver(this); + + // Add a reference to the newest complete cache. + SetSwappableCache(group); + + group_being_updated_ = NULL; + newest_cache_of_group_being_updated_ = NULL; + + if (associated_cache_info_pending_ && associated_cache_.get() && + associated_cache_->is_complete()) { + AppCacheInfo info; + FillCacheInfo( + associated_cache_.get(), preferred_manifest_url_, GetStatus(), &info); + associated_cache_info_pending_ = false; + frontend_->OnCacheSelected(host_id_, info); + } +} + +void AppCacheHost::SetSwappableCache(AppCacheGroup* group) { + if (!group) { + swappable_cache_ = NULL; + } else { + AppCache* new_cache = group->newest_complete_cache(); + if (new_cache != associated_cache_.get()) + swappable_cache_ = new_cache; + else + swappable_cache_ = NULL; + } +} + +void AppCacheHost::LoadMainResourceCache(int64 cache_id) { + DCHECK(cache_id != kAppCacheNoCacheId); + if (pending_main_resource_cache_id_ == cache_id || + (main_resource_cache_.get() && + main_resource_cache_->cache_id() == cache_id)) { + return; + } + pending_main_resource_cache_id_ = cache_id; + storage()->LoadCache(cache_id, this); +} + +void AppCacheHost::NotifyMainResourceIsNamespaceEntry( + const GURL& namespace_entry_url) { + main_resource_was_namespace_entry_ = true; + namespace_entry_url_ = namespace_entry_url; +} + +void AppCacheHost::NotifyMainResourceBlocked(const GURL& manifest_url) { + main_resource_blocked_ = true; + blocked_manifest_url_ = manifest_url; +} + +void AppCacheHost::PrepareForTransfer() { + // This can only happen prior to the document having been loaded. + DCHECK(!associated_cache()); + DCHECK(!is_selection_pending()); + DCHECK(!group_being_updated_); + host_id_ = kAppCacheNoHostId; + frontend_ = NULL; +} + +void AppCacheHost::CompleteTransfer(int host_id, AppCacheFrontend* frontend) { + host_id_ = host_id; + frontend_ = frontend; +} + +void AppCacheHost::AssociateNoCache(const GURL& manifest_url) { + // manifest url can be empty. + AssociateCacheHelper(NULL, manifest_url); +} + +void AppCacheHost::AssociateIncompleteCache(AppCache* cache, + const GURL& manifest_url) { + DCHECK(cache && !cache->is_complete()); + DCHECK(!manifest_url.is_empty()); + AssociateCacheHelper(cache, manifest_url); +} + +void AppCacheHost::AssociateCompleteCache(AppCache* cache) { + DCHECK(cache && cache->is_complete()); + AssociateCacheHelper(cache, cache->owning_group()->manifest_url()); +} + +void AppCacheHost::AssociateCacheHelper(AppCache* cache, + const GURL& manifest_url) { + if (associated_cache_.get()) { + associated_cache_->UnassociateHost(this); + } + + associated_cache_ = cache; + SetSwappableCache(cache ? cache->owning_group() : NULL); + associated_cache_info_pending_ = cache && !cache->is_complete(); + AppCacheInfo info; + if (cache) + cache->AssociateHost(this); + + FillCacheInfo(cache, manifest_url, GetStatus(), &info); + frontend_->OnCacheSelected(host_id_, info); +} + +} // namespace content diff --git a/content/browser/appcache/appcache_host.h b/content/browser/appcache/appcache_host.h new file mode 100644 index 0000000..cef737e --- /dev/null +++ b/content/browser/appcache/appcache_host.h @@ -0,0 +1,338 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_APPCACHE_APPCACHE_HOST_H_ +#define CONTENT_BROWSER_APPCACHE_APPCACHE_HOST_H_ + +#include "base/callback.h" +#include "base/gtest_prod_util.h" +#include "base/memory/ref_counted.h" +#include "base/observer_list.h" +#include "content/browser/appcache/appcache_group.h" +#include "content/browser/appcache/appcache_service_impl.h" +#include "content/browser/appcache/appcache_storage.h" +#include "content/common/appcache_interfaces.h" +#include "content/common/content_export.h" +#include "url/gurl.h" +#include "webkit/common/resource_type.h" + +namespace net { +class URLRequest; +} // namespace net + +namespace content { +FORWARD_DECLARE_TEST(AppCacheGroupTest, CleanupUnusedGroup); +FORWARD_DECLARE_TEST(AppCacheGroupTest, QueueUpdate); +FORWARD_DECLARE_TEST(AppCacheHostTest, Basic); +FORWARD_DECLARE_TEST(AppCacheHostTest, SelectNoCache); +FORWARD_DECLARE_TEST(AppCacheHostTest, ForeignEntry); +FORWARD_DECLARE_TEST(AppCacheHostTest, FailedCacheLoad); +FORWARD_DECLARE_TEST(AppCacheHostTest, FailedGroupLoad); +FORWARD_DECLARE_TEST(AppCacheHostTest, SetSwappableCache); +FORWARD_DECLARE_TEST(AppCacheHostTest, ForDedicatedWorker); +FORWARD_DECLARE_TEST(AppCacheHostTest, SelectCacheAllowed); +FORWARD_DECLARE_TEST(AppCacheHostTest, SelectCacheBlocked); +FORWARD_DECLARE_TEST(AppCacheTest, CleanupUnusedCache); +class AppCacheTest; +class AppCacheHostTest; +class AppCacheGroupTest; +class AppCacheStorageImplTest; +class AppCacheRequestHandlerTest; +class AppCacheUpdateJobTest; +} + +namespace content { + +class AppCache; +class AppCacheFrontend; +class AppCacheRequestHandler; + +typedef base::Callback<void(AppCacheStatus, void*)> GetStatusCallback; +typedef base::Callback<void(bool, void*)> StartUpdateCallback; +typedef base::Callback<void(bool, void*)> SwapCacheCallback; + +// Server-side representation of an application cache host. +class CONTENT_EXPORT AppCacheHost + : public AppCacheStorage::Delegate, + public AppCacheGroup::UpdateObserver, + public AppCacheServiceImpl::Observer { + public: + + class CONTENT_EXPORT Observer { + public: + // Called just after the cache selection algorithm completes. + virtual void OnCacheSelectionComplete(AppCacheHost* host) = 0; + + // Called just prior to the instance being deleted. + virtual void OnDestructionImminent(AppCacheHost* host) = 0; + + virtual ~Observer() {} + }; + + AppCacheHost(int host_id, AppCacheFrontend* frontend, + AppCacheServiceImpl* service); + virtual ~AppCacheHost(); + + // Adds/removes an observer, the AppCacheHost does not take + // ownership of the observer. + void AddObserver(Observer* observer); + void RemoveObserver(Observer* observer); + + // Support for cache selection and scriptable method calls. + void SelectCache(const GURL& document_url, + const int64 cache_document_was_loaded_from, + const GURL& manifest_url); + void SelectCacheForWorker(int parent_process_id, + int parent_host_id); + void SelectCacheForSharedWorker(int64 appcache_id); + void MarkAsForeignEntry(const GURL& document_url, + int64 cache_document_was_loaded_from); + void GetStatusWithCallback(const GetStatusCallback& callback, + void* callback_param); + void StartUpdateWithCallback(const StartUpdateCallback& callback, + void* callback_param); + void SwapCacheWithCallback(const SwapCacheCallback& callback, + void* callback_param); + + // Called prior to the main resource load. When the system contains multiple + // candidates for a main resource load, the appcache preferred by the host + // that created this host is used to break ties. + void SetSpawningHostId(int spawning_process_id, int spawning_host_id); + + // May return NULL if the spawning host context has been closed, or if a + // spawning host context was never identified. + const AppCacheHost* GetSpawningHost() const; + + const GURL& preferred_manifest_url() const { + return preferred_manifest_url_; + } + void set_preferred_manifest_url(const GURL& url) { + preferred_manifest_url_ = url; + } + + // Support for loading resources out of the appcache. + // May return NULL if the request isn't subject to retrieval from an appache. + AppCacheRequestHandler* CreateRequestHandler( + net::URLRequest* request, ResourceType::Type resource_type); + + // Support for devtools inspecting appcache resources. + void GetResourceList(std::vector<AppCacheResourceInfo>* resource_infos); + + // Breaks any existing association between this host and a cache. + // 'manifest_url' is sent to DevTools as the manifest url that could have + // been associated before or could be associated later with this host. + // Associations are broken either thru the cache selection algorithm + // implemented in this class, or by the update algorithm (see + // AppCacheUpdateJob). + void AssociateNoCache(const GURL& manifest_url); + + // Establishes an association between this host and an incomplete cache. + // 'manifest_url' is manifest url of the cache group being updated. + // Associations with incomplete caches are established by the update algorithm + // (see AppCacheUpdateJob). + void AssociateIncompleteCache(AppCache* cache, const GURL& manifest_url); + + // Establishes an association between this host and a complete cache. + // Associations with complete caches are established either thru the cache + // selection algorithm implemented (in this class), or by the update algorithm + // (see AppCacheUpdateJob). + void AssociateCompleteCache(AppCache* cache); + + // Adds a reference to the newest complete cache in a group, unless it's the + // same as the cache that is currently associated with the host. + void SetSwappableCache(AppCacheGroup* group); + + // Used to ensure that a loaded appcache survives a frame navigation. + void LoadMainResourceCache(int64 cache_id); + + // Used to notify the host that a namespace resource is being delivered as + // the main resource of the page and to provide its url. + void NotifyMainResourceIsNamespaceEntry(const GURL& namespace_entry_url); + + // Used to notify the host that the main resource was blocked by a policy. To + // work properly, this method needs to by invoked prior to cache selection. + void NotifyMainResourceBlocked(const GURL& manifest_url); + + // Used by the update job to keep track of which hosts are associated + // with which pending master entries. + const GURL& pending_master_entry_url() const { + return new_master_entry_url_; + } + + int host_id() const { return host_id_; } + AppCacheServiceImpl* service() const { return service_; } + AppCacheStorage* storage() const { return storage_; } + AppCacheFrontend* frontend() const { return frontend_; } + AppCache* associated_cache() const { return associated_cache_.get(); } + + bool is_selection_pending() const { + return pending_selected_cache_id_ != kAppCacheNoCacheId || + !pending_selected_manifest_url_.is_empty(); + } + + const GURL& first_party_url() const { return first_party_url_; } + + // Methods to support cross site navigations. + void PrepareForTransfer(); + void CompleteTransfer(int host_id, AppCacheFrontend* frontend); + + private: + friend class content::AppCacheHostTest; + friend class content::AppCacheStorageImplTest; + friend class content::AppCacheRequestHandlerTest; + friend class content::AppCacheUpdateJobTest; + + AppCacheStatus GetStatus(); + void LoadSelectedCache(int64 cache_id); + void LoadOrCreateGroup(const GURL& manifest_url); + + // See public Associate*Host() methods above. + void AssociateCacheHelper(AppCache* cache, const GURL& manifest_url); + + // AppCacheStorage::Delegate impl + virtual void OnCacheLoaded(AppCache* cache, int64 cache_id) OVERRIDE; + virtual void OnGroupLoaded(AppCacheGroup* group, + const GURL& manifest_url) OVERRIDE; + // AppCacheServiceImpl::Observer impl + virtual void OnServiceReinitialized( + AppCacheStorageReference* old_storage_ref) OVERRIDE; + + void FinishCacheSelection(AppCache* cache, AppCacheGroup* group); + void DoPendingGetStatus(); + void DoPendingStartUpdate(); + void DoPendingSwapCache(); + + void ObserveGroupBeingUpdated(AppCacheGroup* group); + + // AppCacheGroup::UpdateObserver methods. + virtual void OnUpdateComplete(AppCacheGroup* group) OVERRIDE; + + // Returns true if this host is for a dedicated worker context. + bool is_for_dedicated_worker() const { + return parent_host_id_ != kAppCacheNoHostId; + } + + // Returns the parent context's host instance. This is only valid + // to call when this instance is_for_dedicated_worker. + AppCacheHost* GetParentAppCacheHost() const; + + // Identifies the corresponding appcache host in the child process. + int host_id_; + + // Information about the host that created this one; the manifest + // preferred by our creator influences which cache our main resource + // should be loaded from. + int spawning_host_id_; + int spawning_process_id_; + GURL preferred_manifest_url_; + + // Hosts for dedicated workers are special cased to shunt + // request handling off to the dedicated worker's parent. + // The scriptable api is not accessible in dedicated workers + // so the other aspects of this class are not relevant for + // these special case instances. + int parent_host_id_; + int parent_process_id_; + + // Defined prior to refs to AppCaches and Groups because destruction + // order matters, the disabled_storage_reference_ must outlive those + // objects. See additional comments for the storage_ member. + scoped_refptr<AppCacheStorageReference> disabled_storage_reference_; + + // The cache associated with this host, if any. + scoped_refptr<AppCache> associated_cache_; + + // Hold a reference to the newest complete cache (if associated cache is + // not the newest) to keep the newest cache in existence while the app cache + // group is in use. The newest complete cache may have no associated hosts + // holding any references to it and would otherwise be deleted prematurely. + scoped_refptr<AppCache> swappable_cache_; + + // Keep a reference to the group being updated until the update completes. + scoped_refptr<AppCacheGroup> group_being_updated_; + + // Similarly, keep a reference to the newest cache of the group until the + // update completes. When adding a new master entry to a cache that is not + // in use in any other host, this reference keeps the cache in memory. + scoped_refptr<AppCache> newest_cache_of_group_being_updated_; + + // Keep a reference to the cache of the main resource so it survives frame + // navigations. + scoped_refptr<AppCache> main_resource_cache_; + int64 pending_main_resource_cache_id_; + + // Cache loading is async, if we're loading a specific cache or group + // for the purposes of cache selection, one or the other of these will + // indicate which cache or group is being loaded. + int64 pending_selected_cache_id_; + GURL pending_selected_manifest_url_; + + // A new master entry to be added to the cache, may be empty. + GURL new_master_entry_url_; + + // The frontend proxy to deliver notifications to the child process. + AppCacheFrontend* frontend_; + + // Our central service object. + AppCacheServiceImpl* service_; + + // And the equally central storage object, with a twist. In some error + // conditions the storage object gets recreated and reinitialized. The + // disabled_storage_reference_ (defined earlier) allows for cleanup of an + // instance that got disabled after we had latched onto it. In normal + // circumstances, disabled_storage_reference_ is expected to be NULL. + // When non-NULL both storage_ and disabled_storage_reference_ refer to the + // same instance. + AppCacheStorage* storage_; + + // Since these are synchronous scriptable API calls in the client, there can + // only be one type of callback pending. Also, we have to wait until we have a + // cache selection prior to responding to these calls, as cache selection + // involves async loading of a cache or a group from storage. + GetStatusCallback pending_get_status_callback_; + StartUpdateCallback pending_start_update_callback_; + SwapCacheCallback pending_swap_cache_callback_; + void* pending_callback_param_; + + // True if an intercept or fallback namespace resource was + // delivered as the main resource. + bool main_resource_was_namespace_entry_; + GURL namespace_entry_url_; + + // True if requests for this host were blocked by a policy. + bool main_resource_blocked_; + GURL blocked_manifest_url_; + + // Tells if info about associated cache is pending. Info is pending + // when update job has not returned success yet. + bool associated_cache_info_pending_; + + // List of objects observing us. + ObserverList<Observer> observers_; + + // Used to inform the QuotaManager of what origins are currently in use. + GURL origin_in_use_; + + // First party url to be used in policy checks. + GURL first_party_url_; + + FRIEND_TEST_ALL_PREFIXES(content::AppCacheGroupTest, CleanupUnusedGroup); + FRIEND_TEST_ALL_PREFIXES(content::AppCacheGroupTest, QueueUpdate); + FRIEND_TEST_ALL_PREFIXES(content::AppCacheHostTest, Basic); + FRIEND_TEST_ALL_PREFIXES(content::AppCacheHostTest, SelectNoCache); + FRIEND_TEST_ALL_PREFIXES(content::AppCacheHostTest, ForeignEntry); + FRIEND_TEST_ALL_PREFIXES(content::AppCacheHostTest, FailedCacheLoad); + FRIEND_TEST_ALL_PREFIXES(content::AppCacheHostTest, FailedGroupLoad); + FRIEND_TEST_ALL_PREFIXES(content::AppCacheHostTest, SetSwappableCache); + FRIEND_TEST_ALL_PREFIXES(content::AppCacheHostTest, ForDedicatedWorker); + FRIEND_TEST_ALL_PREFIXES(content::AppCacheHostTest, SelectCacheAllowed); + FRIEND_TEST_ALL_PREFIXES(content::AppCacheHostTest, SelectCacheBlocked); + FRIEND_TEST_ALL_PREFIXES(content::AppCacheTest, CleanupUnusedCache); + + DISALLOW_COPY_AND_ASSIGN(AppCacheHost); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_APPCACHE_APPCACHE_HOST_H_ diff --git a/content/browser/appcache/appcache_host_unittest.cc b/content/browser/appcache/appcache_host_unittest.cc index 6b9970d..0959e4f 100644 --- a/content/browser/appcache/appcache_host_unittest.cc +++ b/content/browser/appcache/appcache_host_unittest.cc @@ -6,30 +6,16 @@ #include "base/bind_helpers.h" #include "base/memory/scoped_ptr.h" #include "base/message_loop/message_loop.h" +#include "content/browser/appcache/appcache.h" +#include "content/browser/appcache/appcache_backend_impl.h" +#include "content/browser/appcache/appcache_group.h" +#include "content/browser/appcache/appcache_host.h" #include "content/browser/appcache/mock_appcache_policy.h" #include "content/browser/appcache/mock_appcache_service.h" #include "net/url_request/url_request.h" #include "testing/gtest/include/gtest/gtest.h" -#include "webkit/browser/appcache/appcache.h" -#include "webkit/browser/appcache/appcache_backend_impl.h" -#include "webkit/browser/appcache/appcache_group.h" -#include "webkit/browser/appcache/appcache_host.h" #include "webkit/browser/quota/quota_manager.h" -using appcache::AppCache; -using appcache::AppCacheBackendImpl; -using appcache::AppCacheEntry; -using appcache::AppCacheFrontend; -using appcache::AppCacheGroup; -using appcache::AppCacheHost; -using appcache::kAppCacheNoCacheId; -using appcache::APPCACHE_ERROR_EVENT; -using appcache::APPCACHE_STATUS_OBSOLETE; -using appcache::APPCACHE_OBSOLETE_EVENT; -using appcache::APPCACHE_PROGRESS_EVENT; -using appcache::AppCacheStatus; -using appcache::APPCACHE_STATUS_UNCACHED; - namespace content { class AppCacheHostTest : public testing::Test { @@ -50,32 +36,32 @@ class AppCacheHostTest : public testing::Test { public: MockFrontend() : last_host_id_(-222), last_cache_id_(-222), - last_status_(appcache::APPCACHE_STATUS_OBSOLETE), - last_status_changed_(appcache::APPCACHE_STATUS_OBSOLETE), - last_event_id_(appcache::APPCACHE_OBSOLETE_EVENT), + last_status_(APPCACHE_STATUS_OBSOLETE), + last_status_changed_(APPCACHE_STATUS_OBSOLETE), + last_event_id_(APPCACHE_OBSOLETE_EVENT), content_blocked_(false) { } virtual void OnCacheSelected( - int host_id, const appcache::AppCacheInfo& info) OVERRIDE { + int host_id, const AppCacheInfo& info) OVERRIDE { last_host_id_ = host_id; last_cache_id_ = info.cache_id; last_status_ = info.status; } virtual void OnStatusChanged(const std::vector<int>& host_ids, - appcache::AppCacheStatus status) OVERRIDE { + AppCacheStatus status) OVERRIDE { last_status_changed_ = status; } virtual void OnEventRaised(const std::vector<int>& host_ids, - appcache::AppCacheEventID event_id) OVERRIDE { + AppCacheEventID event_id) OVERRIDE { last_event_id_ = event_id; } virtual void OnErrorEventRaised( const std::vector<int>& host_ids, - const appcache::AppCacheErrorDetails& details) OVERRIDE { + const AppCacheErrorDetails& details) OVERRIDE { last_event_id_ = APPCACHE_ERROR_EVENT; } @@ -87,7 +73,7 @@ class AppCacheHostTest : public testing::Test { } virtual void OnLogMessage(int host_id, - appcache::AppCacheLogLevel log_level, + AppCacheLogLevel log_level, const std::string& message) OVERRIDE { } @@ -98,9 +84,9 @@ class AppCacheHostTest : public testing::Test { int last_host_id_; int64 last_cache_id_; - appcache::AppCacheStatus last_status_; - appcache::AppCacheStatus last_status_changed_; - appcache::AppCacheEventID last_event_id_; + AppCacheStatus last_status_; + AppCacheStatus last_status_changed_; + AppCacheEventID last_event_id_; bool content_blocked_; }; @@ -172,9 +158,9 @@ class AppCacheHostTest : public testing::Test { MockFrontend mock_frontend_; // Mock callbacks we expect to receive from the 'host' - appcache::GetStatusCallback get_status_callback_; - appcache::StartUpdateCallback start_update_callback_; - appcache::SwapCacheCallback swap_cache_callback_; + content::GetStatusCallback get_status_callback_; + content::StartUpdateCallback start_update_callback_; + content::SwapCacheCallback swap_cache_callback_; AppCacheStatus last_status_result_; bool last_swap_result_; @@ -387,11 +373,11 @@ TEST_F(AppCacheHostTest, SetSwappableCache) { host.AssociateCompleteCache(cache1); EXPECT_FALSE(host.swappable_cache_.get()); // was same as associated cache - EXPECT_EQ(appcache::APPCACHE_STATUS_IDLE, host.GetStatus()); + EXPECT_EQ(APPCACHE_STATUS_IDLE, host.GetStatus()); // verify OnCacheSelected was called EXPECT_EQ(host.host_id(), mock_frontend_.last_host_id_); EXPECT_EQ(cache1->cache_id(), mock_frontend_.last_cache_id_); - EXPECT_EQ(appcache::APPCACHE_STATUS_IDLE, mock_frontend_.last_status_); + EXPECT_EQ(APPCACHE_STATUS_IDLE, mock_frontend_.last_status_); AppCache* cache2 = new AppCache(service_.storage(), 222); cache2->set_complete(true); diff --git a/content/browser/appcache/appcache_interceptor.cc b/content/browser/appcache/appcache_interceptor.cc index ac7dbc3..76c5c0d 100644 --- a/content/browser/appcache/appcache_interceptor.cc +++ b/content/browser/appcache/appcache_interceptor.cc @@ -4,19 +4,12 @@ #include "content/browser/appcache/appcache_interceptor.h" -#include "webkit/browser/appcache/appcache_backend_impl.h" -#include "webkit/browser/appcache/appcache_host.h" -#include "webkit/browser/appcache/appcache_request_handler.h" -#include "webkit/browser/appcache/appcache_service_impl.h" -#include "webkit/browser/appcache/appcache_url_request_job.h" -#include "webkit/common/appcache/appcache_interfaces.h" - -using appcache::AppCacheBackendImpl; -using appcache::AppCacheHost; -using appcache::AppCacheRequestHandler; -using appcache::AppCacheServiceImpl; -using appcache::kAppCacheNoCacheId; -using appcache::kAppCacheNoHostId; +#include "content/browser/appcache/appcache_backend_impl.h" +#include "content/browser/appcache/appcache_host.h" +#include "content/browser/appcache/appcache_request_handler.h" +#include "content/browser/appcache/appcache_service_impl.h" +#include "content/browser/appcache/appcache_url_request_job.h" +#include "content/common/appcache_interfaces.h" namespace content { diff --git a/content/browser/appcache/appcache_interceptor.h b/content/browser/appcache/appcache_interceptor.h index d8da181..4f86a89 100644 --- a/content/browser/appcache/appcache_interceptor.h +++ b/content/browser/appcache/appcache_interceptor.h @@ -11,7 +11,7 @@ #include "url/gurl.h" #include "webkit/common/resource_type.h" -namespace appcache { +namespace content { class AppCacheRequestHandler; class AppCacheServiceImpl; } @@ -31,7 +31,7 @@ class CONTENT_EXPORT AppCacheInterceptor // Must be called to make a request eligible for retrieval from an appcache. static void SetExtraRequestInfo(net::URLRequest* request, - appcache::AppCacheServiceImpl* service, + AppCacheServiceImpl* service, int process_id, int host_id, ResourceType::Type resource_type); @@ -71,8 +71,8 @@ class CONTENT_EXPORT AppCacheInterceptor virtual ~AppCacheInterceptor(); static void SetHandler(net::URLRequest* request, - appcache::AppCacheRequestHandler* handler); - static appcache::AppCacheRequestHandler* GetHandler(net::URLRequest* request); + AppCacheRequestHandler* handler); + static AppCacheRequestHandler* GetHandler(net::URLRequest* request); DISALLOW_COPY_AND_ASSIGN(AppCacheInterceptor); }; diff --git a/content/browser/appcache/appcache_policy.h b/content/browser/appcache/appcache_policy.h new file mode 100644 index 0000000..e7ef6f1 --- /dev/null +++ b/content/browser/appcache/appcache_policy.h @@ -0,0 +1,32 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_APPCACHE_APPCACHE_POLICY_H_ +#define CONTENT_BROWSER_APPCACHE_APPCACHE_POLICY_H_ + +class GURL; + +namespace content { + +class AppCachePolicy { + public: + AppCachePolicy() {} + + // Called prior to loading a main resource from the appache. + // Returns true if allowed. This is expected to return immediately + // without any user prompt. + virtual bool CanLoadAppCache(const GURL& manifest_url, + const GURL& first_party) = 0; + + // Called prior to creating a new appcache. Returns true if allowed. + virtual bool CanCreateAppCache(const GURL& manifest_url, + const GURL& first_party) = 0; + + protected: + ~AppCachePolicy() {} +}; + +} // namespace content + +#endif // CONTENT_BROWSER_APPCACHE_APPCACHE_POLICY_H_ diff --git a/content/browser/appcache/appcache_quota_client.cc b/content/browser/appcache/appcache_quota_client.cc new file mode 100644 index 0000000..2e8c2ab --- /dev/null +++ b/content/browser/appcache/appcache_quota_client.cc @@ -0,0 +1,253 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/appcache/appcache_quota_client.h" + +#include <algorithm> +#include <map> +#include <set> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "content/browser/appcache/appcache_service_impl.h" + +using quota::QuotaClient; + +namespace { +quota::QuotaStatusCode NetErrorCodeToQuotaStatus(int code) { + if (code == net::OK) + return quota::kQuotaStatusOk; + else if (code == net::ERR_ABORTED) + return quota::kQuotaErrorAbort; + else + return quota::kQuotaStatusUnknown; +} + +void RunFront(content::AppCacheQuotaClient::RequestQueue* queue) { + base::Closure request = queue->front(); + queue->pop_front(); + request.Run(); +} +} // namespace + +namespace content { + +AppCacheQuotaClient::AppCacheQuotaClient(AppCacheServiceImpl* service) + : service_(service), + appcache_is_ready_(false), + quota_manager_is_destroyed_(false) { +} + +AppCacheQuotaClient::~AppCacheQuotaClient() { + DCHECK(pending_batch_requests_.empty()); + DCHECK(pending_serial_requests_.empty()); + DCHECK(current_delete_request_callback_.is_null()); +} + +QuotaClient::ID AppCacheQuotaClient::id() const { + return kAppcache; +} + +void AppCacheQuotaClient::OnQuotaManagerDestroyed() { + DeletePendingRequests(); + if (!current_delete_request_callback_.is_null()) { + current_delete_request_callback_.Reset(); + GetServiceDeleteCallback()->Cancel(); + } + + quota_manager_is_destroyed_ = true; + if (!service_) + delete this; +} + +void AppCacheQuotaClient::GetOriginUsage( + const GURL& origin, + quota::StorageType type, + const GetUsageCallback& callback) { + DCHECK(!callback.is_null()); + DCHECK(!quota_manager_is_destroyed_); + + if (!service_) { + callback.Run(0); + return; + } + + if (!appcache_is_ready_) { + pending_batch_requests_.push_back( + base::Bind(&AppCacheQuotaClient::GetOriginUsage, + base::Unretained(this), origin, type, callback)); + return; + } + + if (type != quota::kStorageTypeTemporary) { + callback.Run(0); + return; + } + + const AppCacheStorage::UsageMap* map = GetUsageMap(); + AppCacheStorage::UsageMap::const_iterator found = map->find(origin); + if (found == map->end()) { + callback.Run(0); + return; + } + callback.Run(found->second); +} + +void AppCacheQuotaClient::GetOriginsForType( + quota::StorageType type, + const GetOriginsCallback& callback) { + GetOriginsHelper(type, std::string(), callback); +} + +void AppCacheQuotaClient::GetOriginsForHost( + quota::StorageType type, + const std::string& host, + const GetOriginsCallback& callback) { + DCHECK(!callback.is_null()); + if (host.empty()) { + callback.Run(std::set<GURL>()); + return; + } + GetOriginsHelper(type, host, callback); +} + +void AppCacheQuotaClient::DeleteOriginData(const GURL& origin, + quota::StorageType type, + const DeletionCallback& callback) { + DCHECK(!quota_manager_is_destroyed_); + + if (!service_) { + callback.Run(quota::kQuotaErrorAbort); + return; + } + + if (!appcache_is_ready_ || !current_delete_request_callback_.is_null()) { + pending_serial_requests_.push_back( + base::Bind(&AppCacheQuotaClient::DeleteOriginData, + base::Unretained(this), origin, type, callback)); + return; + } + + current_delete_request_callback_ = callback; + if (type != quota::kStorageTypeTemporary) { + DidDeleteAppCachesForOrigin(net::OK); + return; + } + + service_->DeleteAppCachesForOrigin( + origin, GetServiceDeleteCallback()->callback()); +} + +bool AppCacheQuotaClient::DoesSupport(quota::StorageType type) const { + return type == quota::kStorageTypeTemporary; +} + +void AppCacheQuotaClient::DidDeleteAppCachesForOrigin(int rv) { + DCHECK(service_); + if (quota_manager_is_destroyed_) + return; + + // Finish the request by calling our callers callback. + current_delete_request_callback_.Run(NetErrorCodeToQuotaStatus(rv)); + current_delete_request_callback_.Reset(); + if (pending_serial_requests_.empty()) + return; + + // Start the next in the queue. + RunFront(&pending_serial_requests_); +} + +void AppCacheQuotaClient::GetOriginsHelper( + quota::StorageType type, + const std::string& opt_host, + const GetOriginsCallback& callback) { + DCHECK(!callback.is_null()); + DCHECK(!quota_manager_is_destroyed_); + + if (!service_) { + callback.Run(std::set<GURL>()); + return; + } + + if (!appcache_is_ready_) { + pending_batch_requests_.push_back( + base::Bind(&AppCacheQuotaClient::GetOriginsHelper, + base::Unretained(this), type, opt_host, callback)); + return; + } + + if (type != quota::kStorageTypeTemporary) { + callback.Run(std::set<GURL>()); + return; + } + + const AppCacheStorage::UsageMap* map = GetUsageMap(); + std::set<GURL> origins; + for (AppCacheStorage::UsageMap::const_iterator iter = map->begin(); + iter != map->end(); ++iter) { + if (opt_host.empty() || iter->first.host() == opt_host) + origins.insert(iter->first); + } + callback.Run(origins); +} + +void AppCacheQuotaClient::ProcessPendingRequests() { + DCHECK(appcache_is_ready_); + while (!pending_batch_requests_.empty()) + RunFront(&pending_batch_requests_); + + if (!pending_serial_requests_.empty()) + RunFront(&pending_serial_requests_); +} + +void AppCacheQuotaClient::DeletePendingRequests() { + pending_batch_requests_.clear(); + pending_serial_requests_.clear(); +} + +const AppCacheStorage::UsageMap* AppCacheQuotaClient::GetUsageMap() { + DCHECK(service_); + return service_->storage()->usage_map(); +} + +net::CancelableCompletionCallback* +AppCacheQuotaClient::GetServiceDeleteCallback() { + // Lazily created due to CancelableCompletionCallback's threading + // restrictions, there is no way to detach from the thread created on. + if (!service_delete_callback_) { + service_delete_callback_.reset( + new net::CancelableCompletionCallback( + base::Bind(&AppCacheQuotaClient::DidDeleteAppCachesForOrigin, + base::Unretained(this)))); + } + return service_delete_callback_.get(); +} + +void AppCacheQuotaClient::NotifyAppCacheReady() { + // Can reoccur during reinitialization. + if (!appcache_is_ready_) { + appcache_is_ready_ = true; + ProcessPendingRequests(); + } +} + +void AppCacheQuotaClient::NotifyAppCacheDestroyed() { + service_ = NULL; + while (!pending_batch_requests_.empty()) + RunFront(&pending_batch_requests_); + + while (!pending_serial_requests_.empty()) + RunFront(&pending_serial_requests_); + + if (!current_delete_request_callback_.is_null()) { + current_delete_request_callback_.Run(quota::kQuotaErrorAbort); + current_delete_request_callback_.Reset(); + GetServiceDeleteCallback()->Cancel(); + } + + if (quota_manager_is_destroyed_) + delete this; +} + +} // namespace content diff --git a/content/browser/appcache/appcache_quota_client.h b/content/browser/appcache/appcache_quota_client.h new file mode 100644 index 0000000..e0b7aac --- /dev/null +++ b/content/browser/appcache/appcache_quota_client.h @@ -0,0 +1,99 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_APPCACHE_APPCACHE_QUOTA_CLIENT_H_ +#define CONTENT_BROWSER_APPCACHE_APPCACHE_QUOTA_CLIENT_H_ + +#include <deque> +#include <map> +#include <string> + +#include "base/gtest_prod_util.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "content/browser/appcache/appcache_storage.h" +#include "content/common/content_export.h" +#include "net/base/completion_callback.h" +#include "webkit/browser/quota/quota_client.h" +#include "webkit/browser/quota/quota_task.h" +#include "webkit/common/quota/quota_types.h" + +namespace content { +class AppCacheQuotaClientTest; +} + +namespace content { + +class AppCacheServiceImpl; +class AppCacheStorageImpl; +class AppCacheQuotaClientTest; + +// A QuotaClient implementation to integrate the appcache service +// with the quota management system. The QuotaClient interface is +// used on the IO thread by the quota manager. This class deletes +// itself when both the quota manager and the appcache service have +// been destroyed. +class AppCacheQuotaClient : public quota::QuotaClient { + public: + typedef std::deque<base::Closure> RequestQueue; + + virtual ~AppCacheQuotaClient(); + + // QuotaClient method overrides + virtual ID id() const OVERRIDE; + virtual void OnQuotaManagerDestroyed() OVERRIDE; + virtual void GetOriginUsage(const GURL& origin, + quota::StorageType type, + const GetUsageCallback& callback) OVERRIDE; + virtual void GetOriginsForType(quota::StorageType type, + const GetOriginsCallback& callback) OVERRIDE; + virtual void GetOriginsForHost(quota::StorageType type, + const std::string& host, + const GetOriginsCallback& callback) OVERRIDE; + virtual void DeleteOriginData(const GURL& origin, + quota::StorageType type, + const DeletionCallback& callback) OVERRIDE; + virtual bool DoesSupport(quota::StorageType type) const OVERRIDE; + + private: + friend class content::AppCacheQuotaClientTest; + friend class AppCacheServiceImpl; // for NotifyAppCacheIsDestroyed + friend class AppCacheStorageImpl; // for NotifyAppCacheIsReady + + CONTENT_EXPORT + explicit AppCacheQuotaClient(AppCacheServiceImpl* service); + + void DidDeleteAppCachesForOrigin(int rv); + void GetOriginsHelper(quota::StorageType type, + const std::string& opt_host, + const GetOriginsCallback& callback); + void ProcessPendingRequests(); + void DeletePendingRequests(); + const AppCacheStorage::UsageMap* GetUsageMap(); + net::CancelableCompletionCallback* GetServiceDeleteCallback(); + + // For use by appcache internals during initialization and shutdown. + CONTENT_EXPORT void NotifyAppCacheReady(); + CONTENT_EXPORT void NotifyAppCacheDestroyed(); + + // Prior to appcache service being ready, we have to queue + // up reqeusts and defer acting on them until we're ready. + RequestQueue pending_batch_requests_; + RequestQueue pending_serial_requests_; + + // And once it's ready, we can only handle one delete request at a time, + // so we queue up additional requests while one is in already in progress. + DeletionCallback current_delete_request_callback_; + scoped_ptr<net::CancelableCompletionCallback> service_delete_callback_; + + AppCacheServiceImpl* service_; + bool appcache_is_ready_; + bool quota_manager_is_destroyed_; + + DISALLOW_COPY_AND_ASSIGN(AppCacheQuotaClient); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_APPCACHE_APPCACHE_QUOTA_CLIENT_H_ diff --git a/content/browser/appcache/appcache_quota_client_unittest.cc b/content/browser/appcache/appcache_quota_client_unittest.cc index 3a32e95..88346ee 100644 --- a/content/browser/appcache/appcache_quota_client_unittest.cc +++ b/content/browser/appcache/appcache_quota_client_unittest.cc @@ -8,12 +8,10 @@ #include "base/bind.h" #include "base/message_loop/message_loop_proxy.h" #include "base/run_loop.h" +#include "content/browser/appcache/appcache_quota_client.h" #include "content/browser/appcache/mock_appcache_service.h" #include "net/base/net_errors.h" #include "testing/gtest/include/gtest/gtest.h" -#include "webkit/browser/appcache/appcache_quota_client.h" - -using appcache::AppCacheQuotaClient; namespace content { diff --git a/content/browser/appcache/appcache_request_handler.cc b/content/browser/appcache/appcache_request_handler.cc new file mode 100644 index 0000000..2db7a52 --- /dev/null +++ b/content/browser/appcache/appcache_request_handler.cc @@ -0,0 +1,402 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/appcache/appcache_request_handler.h" + +#include "content/browser/appcache/appcache.h" +#include "content/browser/appcache/appcache_backend_impl.h" +#include "content/browser/appcache/appcache_policy.h" +#include "content/browser/appcache/appcache_url_request_job.h" +#include "net/url_request/url_request.h" +#include "net/url_request/url_request_job.h" + +namespace content { + +AppCacheRequestHandler::AppCacheRequestHandler( + AppCacheHost* host, ResourceType::Type resource_type) + : host_(host), resource_type_(resource_type), + is_waiting_for_cache_selection_(false), found_group_id_(0), + found_cache_id_(0), found_network_namespace_(false), + cache_entry_not_found_(false), maybe_load_resource_executed_(false) { + DCHECK(host_); + host_->AddObserver(this); +} + +AppCacheRequestHandler::~AppCacheRequestHandler() { + if (host_) { + storage()->CancelDelegateCallbacks(this); + host_->RemoveObserver(this); + } +} + +AppCacheStorage* AppCacheRequestHandler::storage() const { + DCHECK(host_); + return host_->storage(); +} + +AppCacheURLRequestJob* AppCacheRequestHandler::MaybeLoadResource( + net::URLRequest* request, net::NetworkDelegate* network_delegate) { + maybe_load_resource_executed_ = true; + if (!host_ || !IsSchemeAndMethodSupportedForAppCache(request) || + cache_entry_not_found_) + return NULL; + + // This method can get called multiple times over the life + // of a request. The case we detect here is having scheduled + // delivery of a "network response" using a job setup on an + // earlier call thru this method. To send the request thru + // to the network involves restarting the request altogether, + // which will call thru to our interception layer again. + // This time thru, we return NULL so the request hits the wire. + if (job_.get()) { + DCHECK(job_->is_delivering_network_response() || + job_->cache_entry_not_found()); + if (job_->cache_entry_not_found()) + cache_entry_not_found_ = true; + job_ = NULL; + storage()->CancelDelegateCallbacks(this); + return NULL; + } + + // Clear out our 'found' fields since we're starting a request for a + // new resource, any values in those fields are no longer valid. + found_entry_ = AppCacheEntry(); + found_fallback_entry_ = AppCacheEntry(); + found_cache_id_ = kAppCacheNoCacheId; + found_manifest_url_ = GURL(); + found_network_namespace_ = false; + + if (is_main_resource()) + MaybeLoadMainResource(request, network_delegate); + else + MaybeLoadSubResource(request, network_delegate); + + // If its been setup to deliver a network response, we can just delete + // it now and return NULL instead to achieve that since it couldn't + // have been started yet. + if (job_.get() && job_->is_delivering_network_response()) { + DCHECK(!job_->has_been_started()); + job_ = NULL; + } + + return job_.get(); +} + +AppCacheURLRequestJob* AppCacheRequestHandler::MaybeLoadFallbackForRedirect( + net::URLRequest* request, + net::NetworkDelegate* network_delegate, + const GURL& location) { + if (!host_ || !IsSchemeAndMethodSupportedForAppCache(request) || + cache_entry_not_found_) + return NULL; + if (is_main_resource()) + return NULL; + // TODO(vabr) This is a temporary fix (see crbug/141114). We should get rid of + // it once a more general solution to crbug/121325 is in place. + if (!maybe_load_resource_executed_) + return NULL; + if (request->url().GetOrigin() == location.GetOrigin()) + return NULL; + + DCHECK(!job_.get()); // our jobs never generate redirects + + if (found_fallback_entry_.has_response_id()) { + // 6.9.6, step 4: If this results in a redirect to another origin, + // get the resource of the fallback entry. + job_ = new AppCacheURLRequestJob(request, network_delegate, + storage(), host_, is_main_resource()); + DeliverAppCachedResponse( + found_fallback_entry_, found_cache_id_, found_group_id_, + found_manifest_url_, true, found_namespace_entry_url_); + } else if (!found_network_namespace_) { + // 6.9.6, step 6: Fail the resource load. + job_ = new AppCacheURLRequestJob(request, network_delegate, + storage(), host_, is_main_resource()); + DeliverErrorResponse(); + } else { + // 6.9.6 step 3 and 5: Fetch the resource normally. + } + + return job_.get(); +} + +AppCacheURLRequestJob* AppCacheRequestHandler::MaybeLoadFallbackForResponse( + net::URLRequest* request, net::NetworkDelegate* network_delegate) { + if (!host_ || !IsSchemeAndMethodSupportedForAppCache(request) || + cache_entry_not_found_) + return NULL; + if (!found_fallback_entry_.has_response_id()) + return NULL; + + if (request->status().status() == net::URLRequestStatus::CANCELED) { + // 6.9.6, step 4: But not if the user canceled the download. + return NULL; + } + + // We don't fallback for responses that we delivered. + if (job_.get()) { + DCHECK(!job_->is_delivering_network_response()); + return NULL; + } + + if (request->status().is_success()) { + int code_major = request->GetResponseCode() / 100; + if (code_major !=4 && code_major != 5) + return NULL; + + // Servers can override the fallback behavior with a response header. + const std::string kFallbackOverrideHeader( + "x-chromium-appcache-fallback-override"); + const std::string kFallbackOverrideValue( + "disallow-fallback"); + std::string header_value; + request->GetResponseHeaderByName(kFallbackOverrideHeader, &header_value); + if (header_value == kFallbackOverrideValue) + return NULL; + } + + // 6.9.6, step 4: If this results in a 4xx or 5xx status code + // or there were network errors, get the resource of the fallback entry. + job_ = new AppCacheURLRequestJob(request, network_delegate, + storage(), host_, is_main_resource()); + DeliverAppCachedResponse( + found_fallback_entry_, found_cache_id_, found_group_id_, + found_manifest_url_, true, found_namespace_entry_url_); + return job_.get(); +} + +void AppCacheRequestHandler::GetExtraResponseInfo( + int64* cache_id, GURL* manifest_url) { + if (job_.get() && job_->is_delivering_appcache_response()) { + *cache_id = job_->cache_id(); + *manifest_url = job_->manifest_url(); + } +} + +void AppCacheRequestHandler::PrepareForCrossSiteTransfer(int old_process_id) { + if (!host_) + return; + AppCacheBackendImpl* backend = host_->service()->GetBackend(old_process_id); + host_for_cross_site_transfer_ = backend->TransferHostOut(host_->host_id()); + DCHECK_EQ(host_, host_for_cross_site_transfer_.get()); +} + +void AppCacheRequestHandler::CompleteCrossSiteTransfer( + int new_process_id, int new_host_id) { + if (!host_for_cross_site_transfer_.get()) + return; + DCHECK_EQ(host_, host_for_cross_site_transfer_.get()); + AppCacheBackendImpl* backend = host_->service()->GetBackend(new_process_id); + backend->TransferHostIn(new_host_id, host_for_cross_site_transfer_.Pass()); +} + +void AppCacheRequestHandler::OnDestructionImminent(AppCacheHost* host) { + storage()->CancelDelegateCallbacks(this); + host_ = NULL; // no need to RemoveObserver, the host is being deleted + + // Since the host is being deleted, we don't have to complete any job + // that is current running. It's destined for the bit bucket anyway. + if (job_.get()) { + job_->Kill(); + job_ = NULL; + } +} + +void AppCacheRequestHandler::DeliverAppCachedResponse( + const AppCacheEntry& entry, int64 cache_id, int64 group_id, + const GURL& manifest_url, bool is_fallback, + const GURL& namespace_entry_url) { + DCHECK(host_ && job_.get() && job_->is_waiting()); + DCHECK(entry.has_response_id()); + + if (ResourceType::IsFrame(resource_type_) && !namespace_entry_url.is_empty()) + host_->NotifyMainResourceIsNamespaceEntry(namespace_entry_url); + + job_->DeliverAppCachedResponse(manifest_url, group_id, cache_id, + entry, is_fallback); +} + +void AppCacheRequestHandler::DeliverErrorResponse() { + DCHECK(job_.get() && job_->is_waiting()); + job_->DeliverErrorResponse(); +} + +void AppCacheRequestHandler::DeliverNetworkResponse() { + DCHECK(job_.get() && job_->is_waiting()); + job_->DeliverNetworkResponse(); +} + +// Main-resource handling ---------------------------------------------- + +void AppCacheRequestHandler::MaybeLoadMainResource( + net::URLRequest* request, net::NetworkDelegate* network_delegate) { + DCHECK(!job_.get()); + DCHECK(host_); + + const AppCacheHost* spawning_host = + ResourceType::IsSharedWorker(resource_type_) ? + host_ : host_->GetSpawningHost(); + GURL preferred_manifest_url = spawning_host ? + spawning_host->preferred_manifest_url() : GURL(); + + // We may have to wait for our storage query to complete, but + // this query can also complete syncrhonously. + job_ = new AppCacheURLRequestJob(request, network_delegate, + storage(), host_, is_main_resource()); + storage()->FindResponseForMainRequest( + request->url(), preferred_manifest_url, this); +} + +void AppCacheRequestHandler::OnMainResponseFound( + const GURL& url, const AppCacheEntry& entry, + const GURL& namespace_entry_url, const AppCacheEntry& fallback_entry, + int64 cache_id, int64 group_id, const GURL& manifest_url) { + DCHECK(job_.get()); + DCHECK(host_); + DCHECK(is_main_resource()); + DCHECK(!entry.IsForeign()); + DCHECK(!fallback_entry.IsForeign()); + DCHECK(!(entry.has_response_id() && fallback_entry.has_response_id())); + + if (!job_.get()) + return; + + AppCachePolicy* policy = host_->service()->appcache_policy(); + bool was_blocked_by_policy = !manifest_url.is_empty() && policy && + !policy->CanLoadAppCache(manifest_url, host_->first_party_url()); + + if (was_blocked_by_policy) { + if (ResourceType::IsFrame(resource_type_)) { + host_->NotifyMainResourceBlocked(manifest_url); + } else { + DCHECK(ResourceType::IsSharedWorker(resource_type_)); + host_->frontend()->OnContentBlocked(host_->host_id(), manifest_url); + } + DeliverNetworkResponse(); + return; + } + + if (ResourceType::IsFrame(resource_type_) && cache_id != kAppCacheNoCacheId) { + // AppCacheHost loads and holds a reference to the main resource cache + // for two reasons, firstly to preload the cache into the working set + // in advance of subresource loads happening, secondly to prevent the + // AppCache from falling out of the working set on frame navigations. + host_->LoadMainResourceCache(cache_id); + host_->set_preferred_manifest_url(manifest_url); + } + + // 6.11.1 Navigating across documents, steps 10 and 14. + + found_entry_ = entry; + found_namespace_entry_url_ = namespace_entry_url; + found_fallback_entry_ = fallback_entry; + found_cache_id_ = cache_id; + found_group_id_ = group_id; + found_manifest_url_ = manifest_url; + found_network_namespace_ = false; // not applicable to main requests + + if (found_entry_.has_response_id()) { + DCHECK(!found_fallback_entry_.has_response_id()); + DeliverAppCachedResponse( + found_entry_, found_cache_id_, found_group_id_, found_manifest_url_, + false, found_namespace_entry_url_); + } else { + DeliverNetworkResponse(); + } +} + +// Sub-resource handling ---------------------------------------------- + +void AppCacheRequestHandler::MaybeLoadSubResource( + net::URLRequest* request, net::NetworkDelegate* network_delegate) { + DCHECK(!job_.get()); + + if (host_->is_selection_pending()) { + // We have to wait until cache selection is complete and the + // selected cache is loaded. + is_waiting_for_cache_selection_ = true; + job_ = new AppCacheURLRequestJob(request, network_delegate, + storage(), host_, is_main_resource()); + return; + } + + if (!host_->associated_cache() || + !host_->associated_cache()->is_complete() || + host_->associated_cache()->owning_group()->is_being_deleted()) { + return; + } + + job_ = new AppCacheURLRequestJob(request, network_delegate, + storage(), host_, is_main_resource()); + ContinueMaybeLoadSubResource(); +} + +void AppCacheRequestHandler::ContinueMaybeLoadSubResource() { + // 6.9.6 Changes to the networking model + // If the resource is not to be fetched using the HTTP GET mechanism or + // equivalent ... then fetch the resource normally. + DCHECK(job_.get()); + DCHECK(host_->associated_cache() && host_->associated_cache()->is_complete()); + + const GURL& url = job_->request()->url(); + AppCache* cache = host_->associated_cache(); + storage()->FindResponseForSubRequest( + host_->associated_cache(), url, + &found_entry_, &found_fallback_entry_, &found_network_namespace_); + + if (found_entry_.has_response_id()) { + // Step 2: If there's an entry, get it instead. + DCHECK(!found_network_namespace_ && + !found_fallback_entry_.has_response_id()); + found_cache_id_ = cache->cache_id(); + found_group_id_ = cache->owning_group()->group_id(); + found_manifest_url_ = cache->owning_group()->manifest_url(); + DeliverAppCachedResponse( + found_entry_, found_cache_id_, found_group_id_, found_manifest_url_, + false, GURL()); + return; + } + + if (found_fallback_entry_.has_response_id()) { + // Step 4: Fetch the resource normally, if this results + // in certain conditions, then use the fallback. + DCHECK(!found_network_namespace_ && + !found_entry_.has_response_id()); + found_cache_id_ = cache->cache_id(); + found_manifest_url_ = cache->owning_group()->manifest_url(); + DeliverNetworkResponse(); + return; + } + + if (found_network_namespace_) { + // Step 3 and 5: Fetch the resource normally. + DCHECK(!found_entry_.has_response_id() && + !found_fallback_entry_.has_response_id()); + DeliverNetworkResponse(); + return; + } + + // Step 6: Fail the resource load. + DeliverErrorResponse(); +} + +void AppCacheRequestHandler::OnCacheSelectionComplete(AppCacheHost* host) { + DCHECK(host == host_); + if (is_main_resource()) + return; + if (!is_waiting_for_cache_selection_) + return; + + is_waiting_for_cache_selection_ = false; + + if (!host_->associated_cache() || + !host_->associated_cache()->is_complete()) { + DeliverNetworkResponse(); + return; + } + + ContinueMaybeLoadSubResource(); +} + +} // namespace content diff --git a/content/browser/appcache/appcache_request_handler.h b/content/browser/appcache/appcache_request_handler.h new file mode 100644 index 0000000..c73e1dd --- /dev/null +++ b/content/browser/appcache/appcache_request_handler.h @@ -0,0 +1,152 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_APPCACHE_APPCACHE_REQUEST_HANDLER_H_ +#define CONTENT_BROWSER_APPCACHE_APPCACHE_REQUEST_HANDLER_H_ + +#include "base/compiler_specific.h" +#include "base/supports_user_data.h" +#include "content/browser/appcache/appcache_entry.h" +#include "content/browser/appcache/appcache_host.h" +#include "content/common/content_export.h" +#include "webkit/common/resource_type.h" + +namespace net { +class NetworkDelegate; +class URLRequest; +class URLRequestJob; +} // namespace net + +namespace content { +class AppCacheRequestHandlerTest; +} + +namespace content { + +class AppCacheURLRequestJob; + +// An instance is created for each net::URLRequest. The instance survives all +// http transactions involved in the processing of its net::URLRequest, and is +// given the opportunity to hijack the request along the way. Callers +// should use AppCacheHost::CreateRequestHandler to manufacture instances +// that can retrieve resources for a particular host. +class CONTENT_EXPORT AppCacheRequestHandler + : public base::SupportsUserData::Data, + public AppCacheHost::Observer, + public AppCacheStorage::Delegate { + public: + virtual ~AppCacheRequestHandler(); + + // These are called on each request intercept opportunity. + AppCacheURLRequestJob* MaybeLoadResource( + net::URLRequest* request, net::NetworkDelegate* network_delegate); + AppCacheURLRequestJob* MaybeLoadFallbackForRedirect( + net::URLRequest* request, + net::NetworkDelegate* network_delegate, + const GURL& location); + AppCacheURLRequestJob* MaybeLoadFallbackForResponse( + net::URLRequest* request, net::NetworkDelegate* network_delegate); + + void GetExtraResponseInfo(int64* cache_id, GURL* manifest_url); + + // Methods to support cross site navigations. + void PrepareForCrossSiteTransfer(int old_process_id); + void CompleteCrossSiteTransfer(int new_process_id, int new_host_id); + + static bool IsMainResourceType(ResourceType::Type type) { + return ResourceType::IsFrame(type) || + ResourceType::IsSharedWorker(type); + } + + private: + friend class AppCacheHost; + + // Callers should use AppCacheHost::CreateRequestHandler. + AppCacheRequestHandler(AppCacheHost* host, ResourceType::Type resource_type); + + // AppCacheHost::Observer override + virtual void OnDestructionImminent(AppCacheHost* host) OVERRIDE; + + // Helpers to instruct a waiting job with what response to + // deliver for the request we're handling. + void DeliverAppCachedResponse(const AppCacheEntry& entry, int64 cache_id, + int64 group_id, const GURL& manifest_url, + bool is_fallback, + const GURL& namespace_entry_url); + void DeliverNetworkResponse(); + void DeliverErrorResponse(); + + // Helper to retrieve a pointer to the storage object. + AppCacheStorage* storage() const; + + bool is_main_resource() const { + return IsMainResourceType(resource_type_); + } + + // Main-resource loading ------------------------------------- + // Frame and SharedWorker main resources are handled here. + + void MaybeLoadMainResource(net::URLRequest* request, + net::NetworkDelegate* network_delegate); + + // AppCacheStorage::Delegate methods + virtual void OnMainResponseFound( + const GURL& url, const AppCacheEntry& entry, + const GURL& fallback_url, const AppCacheEntry& fallback_entry, + int64 cache_id, int64 group_id, const GURL& mainfest_url) OVERRIDE; + + // Sub-resource loading ------------------------------------- + // Dedicated worker and all manner of sub-resources are handled here. + + void MaybeLoadSubResource(net::URLRequest* request, + net::NetworkDelegate* network_delegate); + void ContinueMaybeLoadSubResource(); + + // AppCacheHost::Observer override + virtual void OnCacheSelectionComplete(AppCacheHost* host) OVERRIDE; + + // Data members ----------------------------------------------- + + // What host we're servicing a request for. + AppCacheHost* host_; + + // Frame vs subresource vs sharedworker loads are somewhat different. + ResourceType::Type resource_type_; + + // Subresource requests wait until after cache selection completes. + bool is_waiting_for_cache_selection_; + + // Info about the type of response we found for delivery. + // These are relevant for both main and subresource requests. + int64 found_group_id_; + int64 found_cache_id_; + AppCacheEntry found_entry_; + AppCacheEntry found_fallback_entry_; + GURL found_namespace_entry_url_; + GURL found_manifest_url_; + bool found_network_namespace_; + + // True if a cache entry this handler attempted to return was + // not found in the disk cache. Once set, the handler will take + // no action on all subsequent intercept opportunities, so the + // request and any redirects will be handled by the network library. + bool cache_entry_not_found_; + + // True if this->MaybeLoadResource(...) has been called in the past. + bool maybe_load_resource_executed_; + + // The job we use to deliver a response. + scoped_refptr<AppCacheURLRequestJob> job_; + + // During a cross site navigation, we transfer ownership the AppcacheHost + // from the old processes structures over to the new structures. + scoped_ptr<AppCacheHost> host_for_cross_site_transfer_; + + friend class content::AppCacheRequestHandlerTest; + DISALLOW_COPY_AND_ASSIGN(AppCacheRequestHandler); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_APPCACHE_APPCACHE_REQUEST_HANDLER_H_ diff --git a/content/browser/appcache/appcache_request_handler_unittest.cc b/content/browser/appcache/appcache_request_handler_unittest.cc index df6fb33..e8cc8f6 100644 --- a/content/browser/appcache/appcache_request_handler_unittest.cc +++ b/content/browser/appcache/appcache_request_handler_unittest.cc @@ -12,6 +12,10 @@ #include "base/message_loop/message_loop.h" #include "base/synchronization/waitable_event.h" #include "base/threading/thread.h" +#include "content/browser/appcache/appcache.h" +#include "content/browser/appcache/appcache_backend_impl.h" +#include "content/browser/appcache/appcache_request_handler.h" +#include "content/browser/appcache/appcache_url_request_job.h" #include "content/browser/appcache/mock_appcache_policy.h" #include "content/browser/appcache/mock_appcache_service.h" #include "net/base/net_errors.h" @@ -22,21 +26,6 @@ #include "net/url_request/url_request_error_job.h" #include "net/url_request/url_request_job_factory.h" #include "testing/gtest/include/gtest/gtest.h" -#include "webkit/browser/appcache/appcache.h" -#include "webkit/browser/appcache/appcache_backend_impl.h" -#include "webkit/browser/appcache/appcache_request_handler.h" -#include "webkit/browser/appcache/appcache_url_request_job.h" - -using appcache::AppCache; -using appcache::AppCacheBackendImpl; -using appcache::AppCacheEntry; -using appcache::AppCacheFrontend; -using appcache::AppCacheGroup; -using appcache::AppCacheHost; -using appcache::AppCacheInfo; -using appcache::AppCacheRequestHandler; -using appcache::AppCacheURLRequestJob; -using appcache::kAppCacheNoCacheId; namespace content { @@ -47,17 +36,17 @@ class AppCacheRequestHandlerTest : public testing::Test { class MockFrontend : public AppCacheFrontend { public: virtual void OnCacheSelected( - int host_id, const appcache::AppCacheInfo& info) OVERRIDE {} + int host_id, const AppCacheInfo& info) OVERRIDE {} virtual void OnStatusChanged(const std::vector<int>& host_ids, - appcache::AppCacheStatus status) OVERRIDE {} + AppCacheStatus status) OVERRIDE {} virtual void OnEventRaised(const std::vector<int>& host_ids, - appcache::AppCacheEventID event_id) OVERRIDE {} + AppCacheEventID event_id) OVERRIDE {} virtual void OnErrorEventRaised( const std::vector<int>& host_ids, - const appcache::AppCacheErrorDetails& details) OVERRIDE {} + const AppCacheErrorDetails& details) OVERRIDE {} virtual void OnProgressEventRaised(const std::vector<int>& host_ids, const GURL& url, @@ -66,7 +55,7 @@ class AppCacheRequestHandlerTest : public testing::Test { } virtual void OnLogMessage(int host_id, - appcache::AppCacheLogLevel log_level, + AppCacheLogLevel log_level, const std::string& message) OVERRIDE { } diff --git a/content/browser/appcache/appcache_response.cc b/content/browser/appcache/appcache_response.cc new file mode 100644 index 0000000..b1d2366 --- /dev/null +++ b/content/browser/appcache/appcache_response.cc @@ -0,0 +1,423 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/appcache/appcache_response.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "base/pickle.h" +#include "base/strings/string_util.h" +#include "content/browser/appcache/appcache_storage.h" +#include "net/base/completion_callback.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" + +namespace content { + +namespace { + +// Disk cache entry data indices. +enum { + kResponseInfoIndex, + kResponseContentIndex +}; + +// An IOBuffer that wraps a pickle's data. Ownership of the +// pickle is transfered to the WrappedPickleIOBuffer object. +class WrappedPickleIOBuffer : public net::WrappedIOBuffer { + public: + explicit WrappedPickleIOBuffer(const Pickle* pickle) : + net::WrappedIOBuffer(reinterpret_cast<const char*>(pickle->data())), + pickle_(pickle) { + DCHECK(pickle->data()); + } + + private: + virtual ~WrappedPickleIOBuffer() {} + + scoped_ptr<const Pickle> pickle_; +}; + +} // anon namespace + + +// AppCacheResponseInfo ---------------------------------------------- + +AppCacheResponseInfo::AppCacheResponseInfo( + AppCacheStorage* storage, const GURL& manifest_url, + int64 response_id, net::HttpResponseInfo* http_info, + int64 response_data_size) + : manifest_url_(manifest_url), response_id_(response_id), + http_response_info_(http_info), response_data_size_(response_data_size), + storage_(storage) { + DCHECK(http_info); + DCHECK(response_id != kAppCacheNoResponseId); + storage_->working_set()->AddResponseInfo(this); +} + +AppCacheResponseInfo::~AppCacheResponseInfo() { + storage_->working_set()->RemoveResponseInfo(this); +} + +// HttpResponseInfoIOBuffer ------------------------------------------ + +HttpResponseInfoIOBuffer::HttpResponseInfoIOBuffer() + : response_data_size(kUnkownResponseDataSize) {} + +HttpResponseInfoIOBuffer::HttpResponseInfoIOBuffer(net::HttpResponseInfo* info) + : http_info(info), response_data_size(kUnkownResponseDataSize) {} + +HttpResponseInfoIOBuffer::~HttpResponseInfoIOBuffer() {} + +// AppCacheResponseIO ---------------------------------------------- + +AppCacheResponseIO::AppCacheResponseIO( + int64 response_id, int64 group_id, AppCacheDiskCacheInterface* disk_cache) + : response_id_(response_id), + group_id_(group_id), + disk_cache_(disk_cache), + entry_(NULL), + buffer_len_(0), + weak_factory_(this) { +} + +AppCacheResponseIO::~AppCacheResponseIO() { + if (entry_) + entry_->Close(); +} + +void AppCacheResponseIO::ScheduleIOCompletionCallback(int result) { + base::MessageLoop::current()->PostTask( + FROM_HERE, base::Bind(&AppCacheResponseIO::OnIOComplete, + weak_factory_.GetWeakPtr(), result)); +} + +void AppCacheResponseIO::InvokeUserCompletionCallback(int result) { + // Clear the user callback and buffers prior to invoking the callback + // so the caller can schedule additional operations in the callback. + buffer_ = NULL; + info_buffer_ = NULL; + net::CompletionCallback cb = callback_; + callback_.Reset(); + cb.Run(result); +} + +void AppCacheResponseIO::ReadRaw(int index, int offset, + net::IOBuffer* buf, int buf_len) { + DCHECK(entry_); + int rv = entry_->Read( + index, offset, buf, buf_len, + base::Bind(&AppCacheResponseIO::OnRawIOComplete, + weak_factory_.GetWeakPtr())); + if (rv != net::ERR_IO_PENDING) + ScheduleIOCompletionCallback(rv); +} + +void AppCacheResponseIO::WriteRaw(int index, int offset, + net::IOBuffer* buf, int buf_len) { + DCHECK(entry_); + int rv = entry_->Write( + index, offset, buf, buf_len, + base::Bind(&AppCacheResponseIO::OnRawIOComplete, + weak_factory_.GetWeakPtr())); + if (rv != net::ERR_IO_PENDING) + ScheduleIOCompletionCallback(rv); +} + +void AppCacheResponseIO::OnRawIOComplete(int result) { + DCHECK_NE(net::ERR_IO_PENDING, result); + OnIOComplete(result); +} + + +// AppCacheResponseReader ---------------------------------------------- + +AppCacheResponseReader::AppCacheResponseReader( + int64 response_id, int64 group_id, AppCacheDiskCacheInterface* disk_cache) + : AppCacheResponseIO(response_id, group_id, disk_cache), + range_offset_(0), + range_length_(kint32max), + read_position_(0), + weak_factory_(this) { +} + +AppCacheResponseReader::~AppCacheResponseReader() { +} + +void AppCacheResponseReader::ReadInfo(HttpResponseInfoIOBuffer* info_buf, + const net::CompletionCallback& callback) { + DCHECK(!callback.is_null()); + DCHECK(!IsReadPending()); + DCHECK(info_buf); + DCHECK(!info_buf->http_info.get()); + DCHECK(!buffer_.get()); + DCHECK(!info_buffer_.get()); + + info_buffer_ = info_buf; + callback_ = callback; // cleared on completion + OpenEntryIfNeededAndContinue(); +} + +void AppCacheResponseReader::ContinueReadInfo() { + if (!entry_) { + ScheduleIOCompletionCallback(net::ERR_CACHE_MISS); + return; + } + + int size = entry_->GetSize(kResponseInfoIndex); + if (size <= 0) { + ScheduleIOCompletionCallback(net::ERR_CACHE_MISS); + return; + } + + buffer_ = new net::IOBuffer(size); + ReadRaw(kResponseInfoIndex, 0, buffer_.get(), size); +} + +void AppCacheResponseReader::ReadData(net::IOBuffer* buf, int buf_len, + const net::CompletionCallback& callback) { + DCHECK(!callback.is_null()); + DCHECK(!IsReadPending()); + DCHECK(buf); + DCHECK(buf_len >= 0); + DCHECK(!buffer_.get()); + DCHECK(!info_buffer_.get()); + + buffer_ = buf; + buffer_len_ = buf_len; + callback_ = callback; // cleared on completion + OpenEntryIfNeededAndContinue(); +} + +void AppCacheResponseReader::ContinueReadData() { + if (!entry_) { + ScheduleIOCompletionCallback(net::ERR_CACHE_MISS); + return; + } + + if (read_position_ + buffer_len_ > range_length_) { + // TODO(michaeln): What about integer overflows? + DCHECK(range_length_ >= read_position_); + buffer_len_ = range_length_ - read_position_; + } + ReadRaw(kResponseContentIndex, + range_offset_ + read_position_, + buffer_.get(), + buffer_len_); +} + +void AppCacheResponseReader::SetReadRange(int offset, int length) { + DCHECK(!IsReadPending() && !read_position_); + range_offset_ = offset; + range_length_ = length; +} + +void AppCacheResponseReader::OnIOComplete(int result) { + if (result >= 0) { + if (info_buffer_.get()) { + // Deserialize the http info structure, ensuring we got headers. + Pickle pickle(buffer_->data(), result); + scoped_ptr<net::HttpResponseInfo> info(new net::HttpResponseInfo); + bool response_truncated = false; + if (!info->InitFromPickle(pickle, &response_truncated) || + !info->headers.get()) { + InvokeUserCompletionCallback(net::ERR_FAILED); + return; + } + DCHECK(!response_truncated); + info_buffer_->http_info.reset(info.release()); + + // Also return the size of the response body + DCHECK(entry_); + info_buffer_->response_data_size = + entry_->GetSize(kResponseContentIndex); + } else { + read_position_ += result; + } + } + InvokeUserCompletionCallback(result); +} + +void AppCacheResponseReader::OpenEntryIfNeededAndContinue() { + int rv; + AppCacheDiskCacheInterface::Entry** entry_ptr = NULL; + if (entry_) { + rv = net::OK; + } else if (!disk_cache_) { + rv = net::ERR_FAILED; + } else { + entry_ptr = new AppCacheDiskCacheInterface::Entry*; + open_callback_ = + base::Bind(&AppCacheResponseReader::OnOpenEntryComplete, + weak_factory_.GetWeakPtr(), base::Owned(entry_ptr)); + rv = disk_cache_->OpenEntry(response_id_, entry_ptr, open_callback_); + } + + if (rv != net::ERR_IO_PENDING) + OnOpenEntryComplete(entry_ptr, rv); +} + +void AppCacheResponseReader::OnOpenEntryComplete( + AppCacheDiskCacheInterface::Entry** entry, int rv) { + DCHECK(info_buffer_.get() || buffer_.get()); + + if (!open_callback_.is_null()) { + if (rv == net::OK) { + DCHECK(entry); + entry_ = *entry; + } + open_callback_.Reset(); + } + + if (info_buffer_.get()) + ContinueReadInfo(); + else + ContinueReadData(); +} + +// AppCacheResponseWriter ---------------------------------------------- + +AppCacheResponseWriter::AppCacheResponseWriter( + int64 response_id, int64 group_id, AppCacheDiskCacheInterface* disk_cache) + : AppCacheResponseIO(response_id, group_id, disk_cache), + info_size_(0), + write_position_(0), + write_amount_(0), + creation_phase_(INITIAL_ATTEMPT), + weak_factory_(this) { +} + +AppCacheResponseWriter::~AppCacheResponseWriter() { +} + +void AppCacheResponseWriter::WriteInfo( + HttpResponseInfoIOBuffer* info_buf, + const net::CompletionCallback& callback) { + DCHECK(!callback.is_null()); + DCHECK(!IsWritePending()); + DCHECK(info_buf); + DCHECK(info_buf->http_info.get()); + DCHECK(!buffer_.get()); + DCHECK(!info_buffer_.get()); + DCHECK(info_buf->http_info->headers.get()); + + info_buffer_ = info_buf; + callback_ = callback; // cleared on completion + CreateEntryIfNeededAndContinue(); +} + +void AppCacheResponseWriter::ContinueWriteInfo() { + if (!entry_) { + ScheduleIOCompletionCallback(net::ERR_FAILED); + return; + } + + const bool kSkipTransientHeaders = true; + const bool kTruncated = false; + Pickle* pickle = new Pickle; + info_buffer_->http_info->Persist(pickle, kSkipTransientHeaders, kTruncated); + write_amount_ = static_cast<int>(pickle->size()); + buffer_ = new WrappedPickleIOBuffer(pickle); // takes ownership of pickle + WriteRaw(kResponseInfoIndex, 0, buffer_.get(), write_amount_); +} + +void AppCacheResponseWriter::WriteData( + net::IOBuffer* buf, int buf_len, const net::CompletionCallback& callback) { + DCHECK(!callback.is_null()); + DCHECK(!IsWritePending()); + DCHECK(buf); + DCHECK(buf_len >= 0); + DCHECK(!buffer_.get()); + DCHECK(!info_buffer_.get()); + + buffer_ = buf; + write_amount_ = buf_len; + callback_ = callback; // cleared on completion + CreateEntryIfNeededAndContinue(); +} + +void AppCacheResponseWriter::ContinueWriteData() { + if (!entry_) { + ScheduleIOCompletionCallback(net::ERR_FAILED); + return; + } + WriteRaw( + kResponseContentIndex, write_position_, buffer_.get(), write_amount_); +} + +void AppCacheResponseWriter::OnIOComplete(int result) { + if (result >= 0) { + DCHECK(write_amount_ == result); + if (!info_buffer_.get()) + write_position_ += result; + else + info_size_ = result; + } + InvokeUserCompletionCallback(result); +} + +void AppCacheResponseWriter::CreateEntryIfNeededAndContinue() { + int rv; + AppCacheDiskCacheInterface::Entry** entry_ptr = NULL; + if (entry_) { + creation_phase_ = NO_ATTEMPT; + rv = net::OK; + } else if (!disk_cache_) { + creation_phase_ = NO_ATTEMPT; + rv = net::ERR_FAILED; + } else { + creation_phase_ = INITIAL_ATTEMPT; + entry_ptr = new AppCacheDiskCacheInterface::Entry*; + create_callback_ = + base::Bind(&AppCacheResponseWriter::OnCreateEntryComplete, + weak_factory_.GetWeakPtr(), base::Owned(entry_ptr)); + rv = disk_cache_->CreateEntry(response_id_, entry_ptr, create_callback_); + } + if (rv != net::ERR_IO_PENDING) + OnCreateEntryComplete(entry_ptr, rv); +} + +void AppCacheResponseWriter::OnCreateEntryComplete( + AppCacheDiskCacheInterface::Entry** entry, int rv) { + DCHECK(info_buffer_.get() || buffer_.get()); + + if (creation_phase_ == INITIAL_ATTEMPT) { + if (rv != net::OK) { + // We may try to overwrite existing entries. + creation_phase_ = DOOM_EXISTING; + rv = disk_cache_->DoomEntry(response_id_, create_callback_); + if (rv != net::ERR_IO_PENDING) + OnCreateEntryComplete(NULL, rv); + return; + } + } else if (creation_phase_ == DOOM_EXISTING) { + creation_phase_ = SECOND_ATTEMPT; + AppCacheDiskCacheInterface::Entry** entry_ptr = + new AppCacheDiskCacheInterface::Entry*; + create_callback_ = + base::Bind(&AppCacheResponseWriter::OnCreateEntryComplete, + weak_factory_.GetWeakPtr(), base::Owned(entry_ptr)); + rv = disk_cache_->CreateEntry(response_id_, entry_ptr, create_callback_); + if (rv != net::ERR_IO_PENDING) + OnCreateEntryComplete(entry_ptr, rv); + return; + } + + if (!create_callback_.is_null()) { + if (rv == net::OK) + entry_ = *entry; + + create_callback_.Reset(); + } + + if (info_buffer_.get()) + ContinueWriteInfo(); + else + ContinueWriteData(); +} + +} // namespace content diff --git a/content/browser/appcache/appcache_response.h b/content/browser/appcache/appcache_response.h new file mode 100644 index 0000000..474b4d0 --- /dev/null +++ b/content/browser/appcache/appcache_response.h @@ -0,0 +1,266 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_APPCACHE_APPCACHE_RESPONSE_H_ +#define CONTENT_BROWSER_APPCACHE_APPCACHE_RESPONSE_H_ + +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "content/common/appcache_interfaces.h" +#include "content/common/content_export.h" +#include "net/base/completion_callback.h" +#include "net/http/http_response_info.h" +#include "url/gurl.h" + +namespace net { +class IOBuffer; +} + +namespace content { +class MockAppCacheStorage; +} + +namespace content { + +class AppCacheStorage; + +static const int kUnkownResponseDataSize = -1; + +// Response info for a particular response id. Instances are tracked in +// the working set. +class CONTENT_EXPORT AppCacheResponseInfo + : public base::RefCounted<AppCacheResponseInfo> { + public: + // AppCacheResponseInfo takes ownership of the http_info. + AppCacheResponseInfo(AppCacheStorage* storage, const GURL& manifest_url, + int64 response_id, net::HttpResponseInfo* http_info, + int64 response_data_size); + + const GURL& manifest_url() const { return manifest_url_; } + int64 response_id() const { return response_id_; } + const net::HttpResponseInfo* http_response_info() const { + return http_response_info_.get(); + } + int64 response_data_size() const { return response_data_size_; } + + private: + friend class base::RefCounted<AppCacheResponseInfo>; + virtual ~AppCacheResponseInfo(); + + const GURL manifest_url_; + const int64 response_id_; + const scoped_ptr<net::HttpResponseInfo> http_response_info_; + const int64 response_data_size_; + AppCacheStorage* storage_; +}; + +// A refcounted wrapper for HttpResponseInfo so we can apply the +// refcounting semantics used with IOBuffer with these structures too. +struct CONTENT_EXPORT HttpResponseInfoIOBuffer + : public base::RefCountedThreadSafe<HttpResponseInfoIOBuffer> { + scoped_ptr<net::HttpResponseInfo> http_info; + int response_data_size; + + HttpResponseInfoIOBuffer(); + explicit HttpResponseInfoIOBuffer(net::HttpResponseInfo* info); + + protected: + friend class base::RefCountedThreadSafe<HttpResponseInfoIOBuffer>; + virtual ~HttpResponseInfoIOBuffer(); +}; + +// Low level storage API used by the response reader and writer. +class CONTENT_EXPORT AppCacheDiskCacheInterface { + public: + class Entry { + public: + virtual int Read(int index, int64 offset, net::IOBuffer* buf, int buf_len, + const net::CompletionCallback& callback) = 0; + virtual int Write(int index, int64 offset, net::IOBuffer* buf, int buf_len, + const net::CompletionCallback& callback) = 0; + virtual int64 GetSize(int index) = 0; + virtual void Close() = 0; + protected: + virtual ~Entry() {} + }; + + virtual int CreateEntry(int64 key, Entry** entry, + const net::CompletionCallback& callback) = 0; + virtual int OpenEntry(int64 key, Entry** entry, + const net::CompletionCallback& callback) = 0; + virtual int DoomEntry(int64 key, const net::CompletionCallback& callback) = 0; + + protected: + friend class base::RefCounted<AppCacheDiskCacheInterface>; + virtual ~AppCacheDiskCacheInterface() {} +}; + +// Common base class for response reader and writer. +class CONTENT_EXPORT AppCacheResponseIO { + public: + virtual ~AppCacheResponseIO(); + int64 response_id() const { return response_id_; } + + protected: + AppCacheResponseIO(int64 response_id, + int64 group_id, + AppCacheDiskCacheInterface* disk_cache); + + virtual void OnIOComplete(int result) = 0; + + bool IsIOPending() { return !callback_.is_null(); } + void ScheduleIOCompletionCallback(int result); + void InvokeUserCompletionCallback(int result); + void ReadRaw(int index, int offset, net::IOBuffer* buf, int buf_len); + void WriteRaw(int index, int offset, net::IOBuffer* buf, int buf_len); + + const int64 response_id_; + const int64 group_id_; + AppCacheDiskCacheInterface* disk_cache_; + AppCacheDiskCacheInterface::Entry* entry_; + scoped_refptr<HttpResponseInfoIOBuffer> info_buffer_; + scoped_refptr<net::IOBuffer> buffer_; + int buffer_len_; + net::CompletionCallback callback_; + base::WeakPtrFactory<AppCacheResponseIO> weak_factory_; + + private: + void OnRawIOComplete(int result); +}; + +// Reads existing response data from storage. If the object is deleted +// and there is a read in progress, the implementation will return +// immediately but will take care of any side effect of cancelling the +// operation. In other words, instances are safe to delete at will. +class CONTENT_EXPORT AppCacheResponseReader + : public AppCacheResponseIO { + public: + virtual ~AppCacheResponseReader(); + + // Reads http info from storage. Always returns the result of the read + // asynchronously through the 'callback'. Returns the number of bytes read + // or a net:: error code. Guaranteed to not perform partial reads of + // the info data. The reader acquires a reference to the 'info_buf' until + // completion at which time the callback is invoked with either a negative + // error code or the number of bytes read. The 'info_buf' argument should + // contain a NULL http_info when ReadInfo is called. The 'callback' is a + // required parameter. + // Should only be called where there is no Read operation in progress. + // (virtual for testing) + virtual void ReadInfo(HttpResponseInfoIOBuffer* info_buf, + const net::CompletionCallback& callback); + + // Reads data from storage. Always returns the result of the read + // asynchronously through the 'callback'. Returns the number of bytes read + // or a net:: error code. EOF is indicated with a return value of zero. + // The reader acquires a reference to the provided 'buf' until completion + // at which time the callback is invoked with either a negative error code + // or the number of bytes read. The 'callback' is a required parameter. + // Should only be called where there is no Read operation in progress. + // (virtual for testing) + virtual void ReadData(net::IOBuffer* buf, int buf_len, + const net::CompletionCallback& callback); + + // Returns true if there is a read operation, for data or info, pending. + bool IsReadPending() { return IsIOPending(); } + + // Used to support range requests. If not called, the reader will + // read the entire response body. If called, this must be called prior + // to the first call to the ReadData method. + void SetReadRange(int offset, int length); + + protected: + friend class AppCacheStorageImpl; + friend class content::MockAppCacheStorage; + + // Should only be constructed by the storage class and derivatives. + AppCacheResponseReader(int64 response_id, + int64 group_id, + AppCacheDiskCacheInterface* disk_cache); + + virtual void OnIOComplete(int result) OVERRIDE; + void ContinueReadInfo(); + void ContinueReadData(); + void OpenEntryIfNeededAndContinue(); + void OnOpenEntryComplete(AppCacheDiskCacheInterface::Entry** entry, int rv); + + int range_offset_; + int range_length_; + int read_position_; + net::CompletionCallback open_callback_; + base::WeakPtrFactory<AppCacheResponseReader> weak_factory_; +}; + +// Writes new response data to storage. If the object is deleted +// and there is a write in progress, the implementation will return +// immediately but will take care of any side effect of cancelling the +// operation. In other words, instances are safe to delete at will. +class CONTENT_EXPORT AppCacheResponseWriter + : public AppCacheResponseIO { + public: + virtual ~AppCacheResponseWriter(); + + // Writes the http info to storage. Always returns the result of the write + // asynchronously through the 'callback'. Returns the number of bytes written + // or a net:: error code. The writer acquires a reference to the 'info_buf' + // until completion at which time the callback is invoked with either a + // negative error code or the number of bytes written. The 'callback' is a + // required parameter. The contents of 'info_buf' are not modified. + // Should only be called where there is no Write operation in progress. + void WriteInfo(HttpResponseInfoIOBuffer* info_buf, + const net::CompletionCallback& callback); + + // Writes data to storage. Always returns the result of the write + // asynchronously through the 'callback'. Returns the number of bytes written + // or a net:: error code. Guaranteed to not perform partial writes. + // The writer acquires a reference to the provided 'buf' until completion at + // which time the callback is invoked with either a negative error code or + // the number of bytes written. The 'callback' is a required parameter. + // The contents of 'buf' are not modified. + // Should only be called where there is no Write operation in progress. + void WriteData(net::IOBuffer* buf, int buf_len, + const net::CompletionCallback& callback); + + // Returns true if there is a write pending. + bool IsWritePending() { return IsIOPending(); } + + // Returns the amount written, info and data. + int64 amount_written() { return info_size_ + write_position_; } + + protected: + // Should only be constructed by the storage class and derivatives. + AppCacheResponseWriter(int64 response_id, + int64 group_id, + AppCacheDiskCacheInterface* disk_cache); + + private: + friend class AppCacheStorageImpl; + friend class content::MockAppCacheStorage; + + enum CreationPhase { + NO_ATTEMPT, + INITIAL_ATTEMPT, + DOOM_EXISTING, + SECOND_ATTEMPT + }; + + virtual void OnIOComplete(int result) OVERRIDE; + void ContinueWriteInfo(); + void ContinueWriteData(); + void CreateEntryIfNeededAndContinue(); + void OnCreateEntryComplete(AppCacheDiskCacheInterface::Entry** entry, int rv); + + int info_size_; + int write_position_; + int write_amount_; + CreationPhase creation_phase_; + net::CompletionCallback create_callback_; + base::WeakPtrFactory<AppCacheResponseWriter> weak_factory_; +}; + +} // namespace content + +#endif // CONTENT_BROWSER_APPCACHE_APPCACHE_RESPONSE_H_ diff --git a/content/browser/appcache/appcache_response_unittest.cc b/content/browser/appcache/appcache_response_unittest.cc index 935fd7c..49a3be8 100644 --- a/content/browser/appcache/appcache_response_unittest.cc +++ b/content/browser/appcache/appcache_response_unittest.cc @@ -13,18 +13,13 @@ #include "base/pickle.h" #include "base/synchronization/waitable_event.h" #include "base/threading/thread.h" +#include "content/browser/appcache/appcache_response.h" #include "content/browser/appcache/mock_appcache_service.h" #include "net/base/io_buffer.h" #include "net/base/net_errors.h" #include "net/http/http_response_headers.h" #include "testing/gtest/include/gtest/gtest.h" -#include "webkit/browser/appcache/appcache_response.h" -using appcache::AppCacheStorage; -using appcache::AppCacheResponseInfo; -using appcache::AppCacheResponseReader; -using appcache::AppCacheResponseWriter; -using appcache::HttpResponseInfoIOBuffer; using net::IOBuffer; using net::WrappedIOBuffer; diff --git a/content/browser/appcache/appcache_service_impl.cc b/content/browser/appcache/appcache_service_impl.cc new file mode 100644 index 0000000..6c3f0fd --- /dev/null +++ b/content/browser/appcache/appcache_service_impl.cc @@ -0,0 +1,578 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/appcache/appcache_service_impl.h" + +#include <functional> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "base/stl_util.h" +#include "content/browser/appcache/appcache.h" +#include "content/browser/appcache/appcache_backend_impl.h" +#include "content/browser/appcache/appcache_entry.h" +#include "content/browser/appcache/appcache_executable_handler.h" +#include "content/browser/appcache/appcache_histograms.h" +#include "content/browser/appcache/appcache_policy.h" +#include "content/browser/appcache/appcache_quota_client.h" +#include "content/browser/appcache/appcache_response.h" +#include "content/browser/appcache/appcache_service_impl.h" +#include "content/browser/appcache/appcache_storage_impl.h" +#include "net/base/completion_callback.h" +#include "net/base/io_buffer.h" +#include "webkit/browser/quota/special_storage_policy.h" + +namespace content { + +namespace { + +void DeferredCallback(const net::CompletionCallback& callback, int rv) { + callback.Run(rv); +} + +} // namespace + +AppCacheInfoCollection::AppCacheInfoCollection() {} + +AppCacheInfoCollection::~AppCacheInfoCollection() {} + +// AsyncHelper ------- + +class AppCacheServiceImpl::AsyncHelper + : public AppCacheStorage::Delegate { + public: + AsyncHelper(AppCacheServiceImpl* service, + const net::CompletionCallback& callback) + : service_(service), callback_(callback) { + service_->pending_helpers_.insert(this); + } + + virtual ~AsyncHelper() { + if (service_) + service_->pending_helpers_.erase(this); + } + + virtual void Start() = 0; + virtual void Cancel(); + + protected: + void CallCallback(int rv) { + if (!callback_.is_null()) { + // Defer to guarantee async completion. + base::MessageLoop::current()->PostTask( + FROM_HERE, base::Bind(&DeferredCallback, callback_, rv)); + } + callback_.Reset(); + } + + AppCacheServiceImpl* service_; + net::CompletionCallback callback_; +}; + +void AppCacheServiceImpl::AsyncHelper::Cancel() { + if (!callback_.is_null()) { + callback_.Run(net::ERR_ABORTED); + callback_.Reset(); + } + service_->storage()->CancelDelegateCallbacks(this); + service_ = NULL; +} + +// CanHandleOfflineHelper ------- + +class AppCacheServiceImpl::CanHandleOfflineHelper : AsyncHelper { + public: + CanHandleOfflineHelper( + AppCacheServiceImpl* service, const GURL& url, + const GURL& first_party, const net::CompletionCallback& callback) + : AsyncHelper(service, callback), + url_(url), + first_party_(first_party) { + } + + virtual void Start() OVERRIDE { + AppCachePolicy* policy = service_->appcache_policy(); + if (policy && !policy->CanLoadAppCache(url_, first_party_)) { + CallCallback(net::ERR_FAILED); + delete this; + return; + } + + service_->storage()->FindResponseForMainRequest(url_, GURL(), this); + } + + private: + // AppCacheStorage::Delegate implementation. + virtual void OnMainResponseFound( + const GURL& url, const AppCacheEntry& entry, + const GURL& fallback_url, const AppCacheEntry& fallback_entry, + int64 cache_id, int64 group_id, const GURL& mainfest_url) OVERRIDE; + + GURL url_; + GURL first_party_; + + DISALLOW_COPY_AND_ASSIGN(CanHandleOfflineHelper); +}; + +void AppCacheServiceImpl::CanHandleOfflineHelper::OnMainResponseFound( + const GURL& url, const AppCacheEntry& entry, + const GURL& fallback_url, const AppCacheEntry& fallback_entry, + int64 cache_id, int64 group_id, const GURL& manifest_url) { + bool can = (entry.has_response_id() || fallback_entry.has_response_id()); + CallCallback(can ? net::OK : net::ERR_FAILED); + delete this; +} + +// DeleteHelper ------- + +class AppCacheServiceImpl::DeleteHelper : public AsyncHelper { + public: + DeleteHelper( + AppCacheServiceImpl* service, const GURL& manifest_url, + const net::CompletionCallback& callback) + : AsyncHelper(service, callback), manifest_url_(manifest_url) { + } + + virtual void Start() OVERRIDE { + service_->storage()->LoadOrCreateGroup(manifest_url_, this); + } + + private: + // AppCacheStorage::Delegate implementation. + virtual void OnGroupLoaded( + AppCacheGroup* group, const GURL& manifest_url) OVERRIDE; + virtual void OnGroupMadeObsolete(AppCacheGroup* group, + bool success, + int response_code) OVERRIDE; + + GURL manifest_url_; + DISALLOW_COPY_AND_ASSIGN(DeleteHelper); +}; + +void AppCacheServiceImpl::DeleteHelper::OnGroupLoaded( + AppCacheGroup* group, const GURL& manifest_url) { + if (group) { + group->set_being_deleted(true); + group->CancelUpdate(); + service_->storage()->MakeGroupObsolete(group, this, 0); + } else { + CallCallback(net::ERR_FAILED); + delete this; + } +} + +void AppCacheServiceImpl::DeleteHelper::OnGroupMadeObsolete( + AppCacheGroup* group, + bool success, + int response_code) { + CallCallback(success ? net::OK : net::ERR_FAILED); + delete this; +} + +// DeleteOriginHelper ------- + +class AppCacheServiceImpl::DeleteOriginHelper : public AsyncHelper { + public: + DeleteOriginHelper( + AppCacheServiceImpl* service, const GURL& origin, + const net::CompletionCallback& callback) + : AsyncHelper(service, callback), origin_(origin), + num_caches_to_delete_(0), successes_(0), failures_(0) { + } + + virtual void Start() OVERRIDE { + // We start by listing all caches, continues in OnAllInfo(). + service_->storage()->GetAllInfo(this); + } + + private: + // AppCacheStorage::Delegate implementation. + virtual void OnAllInfo(AppCacheInfoCollection* collection) OVERRIDE; + virtual void OnGroupLoaded( + AppCacheGroup* group, const GURL& manifest_url) OVERRIDE; + virtual void OnGroupMadeObsolete(AppCacheGroup* group, + bool success, + int response_code) OVERRIDE; + + void CacheCompleted(bool success); + + GURL origin_; + int num_caches_to_delete_; + int successes_; + int failures_; + + DISALLOW_COPY_AND_ASSIGN(DeleteOriginHelper); +}; + +void AppCacheServiceImpl::DeleteOriginHelper::OnAllInfo( + AppCacheInfoCollection* collection) { + if (!collection) { + // Failed to get a listing. + CallCallback(net::ERR_FAILED); + delete this; + return; + } + + std::map<GURL, AppCacheInfoVector>::iterator found = + collection->infos_by_origin.find(origin_); + if (found == collection->infos_by_origin.end() || found->second.empty()) { + // No caches for this origin. + CallCallback(net::OK); + delete this; + return; + } + + // We have some caches to delete. + const AppCacheInfoVector& caches_to_delete = found->second; + successes_ = 0; + failures_ = 0; + num_caches_to_delete_ = static_cast<int>(caches_to_delete.size()); + for (AppCacheInfoVector::const_iterator iter = caches_to_delete.begin(); + iter != caches_to_delete.end(); ++iter) { + service_->storage()->LoadOrCreateGroup(iter->manifest_url, this); + } +} + +void AppCacheServiceImpl::DeleteOriginHelper::OnGroupLoaded( + AppCacheGroup* group, const GURL& manifest_url) { + if (group) { + group->set_being_deleted(true); + group->CancelUpdate(); + service_->storage()->MakeGroupObsolete(group, this, 0); + } else { + CacheCompleted(false); + } +} + +void AppCacheServiceImpl::DeleteOriginHelper::OnGroupMadeObsolete( + AppCacheGroup* group, + bool success, + int response_code) { + CacheCompleted(success); +} + +void AppCacheServiceImpl::DeleteOriginHelper::CacheCompleted(bool success) { + if (success) + ++successes_; + else + ++failures_; + if ((successes_ + failures_) < num_caches_to_delete_) + return; + + CallCallback(!failures_ ? net::OK : net::ERR_FAILED); + delete this; +} + + +// GetInfoHelper ------- + +class AppCacheServiceImpl::GetInfoHelper : AsyncHelper { + public: + GetInfoHelper( + AppCacheServiceImpl* service, AppCacheInfoCollection* collection, + const net::CompletionCallback& callback) + : AsyncHelper(service, callback), collection_(collection) { + } + + virtual void Start() OVERRIDE { + service_->storage()->GetAllInfo(this); + } + + private: + // AppCacheStorage::Delegate implementation. + virtual void OnAllInfo(AppCacheInfoCollection* collection) OVERRIDE; + + scoped_refptr<AppCacheInfoCollection> collection_; + + DISALLOW_COPY_AND_ASSIGN(GetInfoHelper); +}; + +void AppCacheServiceImpl::GetInfoHelper::OnAllInfo( + AppCacheInfoCollection* collection) { + if (collection) + collection->infos_by_origin.swap(collection_->infos_by_origin); + CallCallback(collection ? net::OK : net::ERR_FAILED); + delete this; +} + +// CheckResponseHelper ------- + +class AppCacheServiceImpl::CheckResponseHelper : AsyncHelper { + public: + CheckResponseHelper( + AppCacheServiceImpl* service, const GURL& manifest_url, int64 cache_id, + int64 response_id) + : AsyncHelper(service, net::CompletionCallback()), + manifest_url_(manifest_url), + cache_id_(cache_id), + response_id_(response_id), + kIOBufferSize(32 * 1024), + expected_total_size_(0), + amount_headers_read_(0), + amount_data_read_(0) { + } + + virtual void Start() OVERRIDE { + service_->storage()->LoadOrCreateGroup(manifest_url_, this); + } + + virtual void Cancel() OVERRIDE { + AppCacheHistograms::CountCheckResponseResult( + AppCacheHistograms::CHECK_CANCELED); + response_reader_.reset(); + AsyncHelper::Cancel(); + } + + private: + virtual void OnGroupLoaded(AppCacheGroup* group, + const GURL& manifest_url) OVERRIDE; + void OnReadInfoComplete(int result); + void OnReadDataComplete(int result); + + // Inputs describing what to check. + GURL manifest_url_; + int64 cache_id_; + int64 response_id_; + + // Internals used to perform the checks. + const int kIOBufferSize; + scoped_refptr<AppCache> cache_; + scoped_ptr<AppCacheResponseReader> response_reader_; + scoped_refptr<HttpResponseInfoIOBuffer> info_buffer_; + scoped_refptr<net::IOBuffer> data_buffer_; + int64 expected_total_size_; + int amount_headers_read_; + int amount_data_read_; + DISALLOW_COPY_AND_ASSIGN(CheckResponseHelper); +}; + +void AppCacheServiceImpl::CheckResponseHelper::OnGroupLoaded( + AppCacheGroup* group, const GURL& manifest_url) { + DCHECK_EQ(manifest_url_, manifest_url); + if (!group || !group->newest_complete_cache() || group->is_being_deleted() || + group->is_obsolete()) { + AppCacheHistograms::CountCheckResponseResult( + AppCacheHistograms::MANIFEST_OUT_OF_DATE); + delete this; + return; + } + + cache_ = group->newest_complete_cache(); + const AppCacheEntry* entry = cache_->GetEntryWithResponseId(response_id_); + if (!entry) { + if (cache_->cache_id() == cache_id_) { + AppCacheHistograms::CountCheckResponseResult( + AppCacheHistograms::ENTRY_NOT_FOUND); + service_->DeleteAppCacheGroup(manifest_url_, net::CompletionCallback()); + } else { + AppCacheHistograms::CountCheckResponseResult( + AppCacheHistograms::RESPONSE_OUT_OF_DATE); + } + delete this; + return; + } + + // Verify that we can read the response info and data. + expected_total_size_ = entry->response_size(); + response_reader_.reset(service_->storage()->CreateResponseReader( + manifest_url_, group->group_id(), response_id_)); + info_buffer_ = new HttpResponseInfoIOBuffer(); + response_reader_->ReadInfo( + info_buffer_.get(), + base::Bind(&CheckResponseHelper::OnReadInfoComplete, + base::Unretained(this))); +} + +void AppCacheServiceImpl::CheckResponseHelper::OnReadInfoComplete(int result) { + if (result < 0) { + AppCacheHistograms::CountCheckResponseResult( + AppCacheHistograms::READ_HEADERS_ERROR); + service_->DeleteAppCacheGroup(manifest_url_, net::CompletionCallback()); + delete this; + return; + } + amount_headers_read_ = result; + + // Start reading the data. + data_buffer_ = new net::IOBuffer(kIOBufferSize); + response_reader_->ReadData( + data_buffer_.get(), + kIOBufferSize, + base::Bind(&CheckResponseHelper::OnReadDataComplete, + base::Unretained(this))); +} + +void AppCacheServiceImpl::CheckResponseHelper::OnReadDataComplete(int result) { + if (result > 0) { + // Keep reading until we've read thru everything or failed to read. + amount_data_read_ += result; + response_reader_->ReadData( + data_buffer_.get(), + kIOBufferSize, + base::Bind(&CheckResponseHelper::OnReadDataComplete, + base::Unretained(this))); + return; + } + + AppCacheHistograms::CheckResponseResultType check_result; + if (result < 0) + check_result = AppCacheHistograms::READ_DATA_ERROR; + else if (info_buffer_->response_data_size != amount_data_read_ || + expected_total_size_ != amount_data_read_ + amount_headers_read_) + check_result = AppCacheHistograms::UNEXPECTED_DATA_SIZE; + else + check_result = AppCacheHistograms::RESPONSE_OK; + AppCacheHistograms::CountCheckResponseResult(check_result); + + if (check_result != AppCacheHistograms::RESPONSE_OK) + service_->DeleteAppCacheGroup(manifest_url_, net::CompletionCallback()); + delete this; +} + +// AppCacheStorageReference ------ + +AppCacheStorageReference::AppCacheStorageReference( + scoped_ptr<AppCacheStorage> storage) + : storage_(storage.Pass()) {} +AppCacheStorageReference::~AppCacheStorageReference() {} + +// AppCacheServiceImpl ------- + +AppCacheServiceImpl::AppCacheServiceImpl(quota::QuotaManagerProxy* + quota_manager_proxy) + : appcache_policy_(NULL), quota_client_(NULL), handler_factory_(NULL), + quota_manager_proxy_(quota_manager_proxy), + request_context_(NULL), + force_keep_session_state_(false) { + if (quota_manager_proxy_.get()) { + quota_client_ = new AppCacheQuotaClient(this); + quota_manager_proxy_->RegisterClient(quota_client_); + } +} + +AppCacheServiceImpl::~AppCacheServiceImpl() { + DCHECK(backends_.empty()); + std::for_each(pending_helpers_.begin(), + pending_helpers_.end(), + std::mem_fun(&AsyncHelper::Cancel)); + STLDeleteElements(&pending_helpers_); + if (quota_client_) + quota_client_->NotifyAppCacheDestroyed(); + + // Destroy storage_ first; ~AppCacheStorageImpl accesses other data members + // (special_storage_policy_). + storage_.reset(); +} + +void AppCacheServiceImpl::Initialize(const base::FilePath& cache_directory, + base::MessageLoopProxy* db_thread, + base::MessageLoopProxy* cache_thread) { + DCHECK(!storage_.get()); + cache_directory_ = cache_directory; + db_thread_ = db_thread; + cache_thread_ = cache_thread; + AppCacheStorageImpl* storage = new AppCacheStorageImpl(this); + storage->Initialize(cache_directory, db_thread, cache_thread); + storage_.reset(storage); +} + +void AppCacheServiceImpl::ScheduleReinitialize() { + if (reinit_timer_.IsRunning()) + return; + + // Reinitialization only happens when corruption has been noticed. + // We don't want to thrash the disk but we also don't want to + // leave the appcache disabled for an indefinite period of time. Some + // users never shutdown the browser. + + const base::TimeDelta kZeroDelta; + const base::TimeDelta kOneHour(base::TimeDelta::FromHours(1)); + const base::TimeDelta k30Seconds(base::TimeDelta::FromSeconds(30)); + + // If the system managed to stay up for long enough, reset the + // delay so a new failure won't incur a long wait to get going again. + base::TimeDelta up_time = base::Time::Now() - last_reinit_time_; + if (next_reinit_delay_ != kZeroDelta && up_time > kOneHour) + next_reinit_delay_ = kZeroDelta; + + reinit_timer_.Start(FROM_HERE, next_reinit_delay_, + this, &AppCacheServiceImpl::Reinitialize); + + // Adjust the delay for next time. + base::TimeDelta increment = std::max(k30Seconds, next_reinit_delay_); + next_reinit_delay_ = std::min(next_reinit_delay_ + increment, kOneHour); +} + +void AppCacheServiceImpl::Reinitialize() { + AppCacheHistograms::CountReinitAttempt(!last_reinit_time_.is_null()); + last_reinit_time_ = base::Time::Now(); + + // Inform observers of about this and give them a chance to + // defer deletion of the old storage object. + scoped_refptr<AppCacheStorageReference> + old_storage_ref(new AppCacheStorageReference(storage_.Pass())); + FOR_EACH_OBSERVER(Observer, observers_, + OnServiceReinitialized(old_storage_ref.get())); + + Initialize(cache_directory_, db_thread_, cache_thread_); +} + +void AppCacheServiceImpl::CanHandleMainResourceOffline( + const GURL& url, + const GURL& first_party, + const net::CompletionCallback& callback) { + CanHandleOfflineHelper* helper = + new CanHandleOfflineHelper(this, url, first_party, callback); + helper->Start(); +} + +void AppCacheServiceImpl::GetAllAppCacheInfo( + AppCacheInfoCollection* collection, + const net::CompletionCallback& callback) { + DCHECK(collection); + GetInfoHelper* helper = new GetInfoHelper(this, collection, callback); + helper->Start(); +} + +void AppCacheServiceImpl::DeleteAppCacheGroup( + const GURL& manifest_url, + const net::CompletionCallback& callback) { + DeleteHelper* helper = new DeleteHelper(this, manifest_url, callback); + helper->Start(); +} + +void AppCacheServiceImpl::DeleteAppCachesForOrigin( + const GURL& origin, const net::CompletionCallback& callback) { + DeleteOriginHelper* helper = new DeleteOriginHelper(this, origin, callback); + helper->Start(); +} + +void AppCacheServiceImpl::CheckAppCacheResponse(const GURL& manifest_url, + int64 cache_id, + int64 response_id) { + CheckResponseHelper* helper = new CheckResponseHelper( + this, manifest_url, cache_id, response_id); + helper->Start(); +} + +void AppCacheServiceImpl::set_special_storage_policy( + quota::SpecialStoragePolicy* policy) { + special_storage_policy_ = policy; +} + +void AppCacheServiceImpl::RegisterBackend( + AppCacheBackendImpl* backend_impl) { + DCHECK(backends_.find(backend_impl->process_id()) == backends_.end()); + backends_.insert( + BackendMap::value_type(backend_impl->process_id(), backend_impl)); +} + +void AppCacheServiceImpl::UnregisterBackend( + AppCacheBackendImpl* backend_impl) { + backends_.erase(backend_impl->process_id()); +} + +} // namespace content diff --git a/content/browser/appcache/appcache_service_impl.h b/content/browser/appcache/appcache_service_impl.h new file mode 100644 index 0000000..ef4f060 --- /dev/null +++ b/content/browser/appcache/appcache_service_impl.h @@ -0,0 +1,227 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_APPCACHE_APPCACHE_SERVICE_IMPL_H_ +#define CONTENT_BROWSER_APPCACHE_APPCACHE_SERVICE_IMPL_H_ + +#include <map> +#include <set> + +#include "base/gtest_prod_util.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/observer_list.h" +#include "base/time/time.h" +#include "base/timer/timer.h" +#include "content/common/appcache_interfaces.h" +#include "content/common/content_export.h" +#include "content/public/browser/appcache_service.h" +#include "net/base/completion_callback.h" +#include "net/base/net_errors.h" +#include "webkit/browser/quota/quota_manager_proxy.h" + +namespace net { +class URLRequestContext; +} // namespace net + +namespace base { +class FilePath; +class MessageLoopProxy; +} + +namespace content { +FORWARD_DECLARE_TEST(AppCacheServiceImplTest, ScheduleReinitialize); +class AppCacheServiceImplTest; +class AppCacheStorageImplTest; +} + +namespace quota { +class SpecialStoragePolicy; +} + +namespace content { + +class AppCacheBackendImpl; +class AppCacheExecutableHandlerFactory; +class AppCacheQuotaClient; +class AppCachePolicy; +class AppCacheStorage; + +// Refcounted container to manage the lifetime of the old storage instance +// during Reinitialization. +class CONTENT_EXPORT AppCacheStorageReference + : public base::RefCounted<AppCacheStorageReference> { +public: + AppCacheStorage* storage() const { return storage_.get(); } +private: + friend class AppCacheServiceImpl; + friend class base::RefCounted<AppCacheStorageReference>; + AppCacheStorageReference(scoped_ptr<AppCacheStorage> storage); + ~AppCacheStorageReference(); + + scoped_ptr<AppCacheStorage> storage_; +}; + +// Class that manages the application cache service. Sends notifications +// to many frontends. One instance per user-profile. Each instance has +// exclusive access to its cache_directory on disk. +class CONTENT_EXPORT AppCacheServiceImpl + : public AppCacheService { + public: + + class CONTENT_EXPORT Observer { + public: + // An observer method to inform consumers of reinitialzation. Managing + // the lifetime of the old storage instance is a delicate process. + // Consumers can keep the old disabled instance alive by hanging on to the + // ref provided. + virtual void OnServiceReinitialized( + AppCacheStorageReference* old_storage_ref) = 0; + virtual ~Observer() {} + }; + + // If not using quota management, the proxy may be NULL. + explicit AppCacheServiceImpl(quota::QuotaManagerProxy* quota_manager_proxy); + virtual ~AppCacheServiceImpl(); + + void Initialize(const base::FilePath& cache_directory, + base::MessageLoopProxy* db_thread, + base::MessageLoopProxy* cache_thread); + + void AddObserver(Observer* observer) { + observers_.AddObserver(observer); + } + + void RemoveObserver(Observer* observer) { + observers_.RemoveObserver(observer); + } + + // For use in catastrophic failure modes to reboot the appcache system + // without relaunching the browser. + void ScheduleReinitialize(); + + // AppCacheService implementation: + virtual void CanHandleMainResourceOffline( + const GURL& url, + const GURL& first_party, + const net::CompletionCallback& callback) OVERRIDE; + virtual void GetAllAppCacheInfo( + AppCacheInfoCollection* collection, + const net::CompletionCallback& callback) OVERRIDE; + virtual void DeleteAppCacheGroup( + const GURL& manifest_url, + const net::CompletionCallback& callback) OVERRIDE; + + // Deletes all appcaches for the origin, 'callback' is invoked upon + // completion. This method always completes asynchronously. + // (virtual for unit testing) + virtual void DeleteAppCachesForOrigin( + const GURL& origin, const net::CompletionCallback& callback); + + // Checks the integrity of 'response_id' by reading the headers and data. + // If it cannot be read, the cache group for 'manifest_url' is deleted. + void CheckAppCacheResponse(const GURL& manifest_url, int64 cache_id, + int64 response_id); + + // Context for use during cache updates, should only be accessed + // on the IO thread. We do NOT add a reference to the request context, + // it is the callers responsibility to ensure that the pointer + // remains valid while set. + net::URLRequestContext* request_context() const { return request_context_; } + void set_request_context(net::URLRequestContext* context) { + request_context_ = context; + } + + // The appcache policy, may be null, in which case access is always allowed. + // The service does NOT assume ownership of the policy, it is the callers + // responsibility to ensure that the pointer remains valid while set. + AppCachePolicy* appcache_policy() const { return appcache_policy_; } + void set_appcache_policy(AppCachePolicy* policy) { + appcache_policy_ = policy; + } + + // The factory may be null, in which case invocations of exe handlers + // will result in an error response. + // The service does NOT assume ownership of the factory, it is the callers + // responsibility to ensure that the pointer remains valid while set. + AppCacheExecutableHandlerFactory* handler_factory() const { + return handler_factory_; + } + void set_handler_factory( + AppCacheExecutableHandlerFactory* factory) { + handler_factory_ = factory; + } + + quota::SpecialStoragePolicy* special_storage_policy() const { + return special_storage_policy_.get(); + } + void set_special_storage_policy(quota::SpecialStoragePolicy* policy); + + quota::QuotaManagerProxy* quota_manager_proxy() const { + return quota_manager_proxy_.get(); + } + + AppCacheQuotaClient* quota_client() const { + return quota_client_; + } + + // Each child process in chrome uses a distinct backend instance. + // See chrome/browser/AppCacheDispatcherHost. + void RegisterBackend(AppCacheBackendImpl* backend_impl); + void UnregisterBackend(AppCacheBackendImpl* backend_impl); + AppCacheBackendImpl* GetBackend(int id) const { + BackendMap::const_iterator it = backends_.find(id); + return (it != backends_.end()) ? it->second : NULL; + } + + AppCacheStorage* storage() const { return storage_.get(); } + + // Disables the exit-time deletion of session-only data. + void set_force_keep_session_state() { force_keep_session_state_ = true; } + bool force_keep_session_state() const { return force_keep_session_state_; } + + protected: + friend class content::AppCacheServiceImplTest; + friend class content::AppCacheStorageImplTest; + FRIEND_TEST_ALL_PREFIXES(content::AppCacheServiceImplTest, + ScheduleReinitialize); + + class AsyncHelper; + class CanHandleOfflineHelper; + class DeleteHelper; + class DeleteOriginHelper; + class GetInfoHelper; + class CheckResponseHelper; + + typedef std::set<AsyncHelper*> PendingAsyncHelpers; + typedef std::map<int, AppCacheBackendImpl*> BackendMap; + + void Reinitialize(); + + base::FilePath cache_directory_; + scoped_refptr<base::MessageLoopProxy> db_thread_; + scoped_refptr<base::MessageLoopProxy> cache_thread_; + AppCachePolicy* appcache_policy_; + AppCacheQuotaClient* quota_client_; + AppCacheExecutableHandlerFactory* handler_factory_; + scoped_ptr<AppCacheStorage> storage_; + scoped_refptr<quota::SpecialStoragePolicy> special_storage_policy_; + scoped_refptr<quota::QuotaManagerProxy> quota_manager_proxy_; + PendingAsyncHelpers pending_helpers_; + BackendMap backends_; // One 'backend' per child process. + // Context for use during cache updates. + net::URLRequestContext* request_context_; + // If true, nothing (not even session-only data) should be deleted on exit. + bool force_keep_session_state_; + base::Time last_reinit_time_; + base::TimeDelta next_reinit_delay_; + base::OneShotTimer<AppCacheServiceImpl> reinit_timer_; + ObserverList<Observer> observers_; + + DISALLOW_COPY_AND_ASSIGN(AppCacheServiceImpl); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_APPCACHE_APPCACHE_SERVICE_IMPL_H_ diff --git a/content/browser/appcache/appcache_service_unittest.cc b/content/browser/appcache/appcache_service_unittest.cc index 80016a9..d22fb041 100644 --- a/content/browser/appcache/appcache_service_unittest.cc +++ b/content/browser/appcache/appcache_service_unittest.cc @@ -8,23 +8,13 @@ #include "base/bind_helpers.h" #include "base/pickle.h" #include "base/run_loop.h" +#include "content/browser/appcache/appcache_response.h" +#include "content/browser/appcache/appcache_service_impl.h" #include "content/browser/appcache/mock_appcache_storage.h" #include "net/base/completion_callback.h" #include "net/base/io_buffer.h" #include "net/http/http_response_headers.h" #include "testing/gtest/include/gtest/gtest.h" -#include "webkit/browser/appcache/appcache_response.h" -#include "webkit/browser/appcache/appcache_service_impl.h" - -using appcache::AppCache; -using appcache::AppCacheEntry; -using appcache::AppCacheGroup; -using appcache::AppCacheInfo; -using appcache::AppCacheInfoCollection; -using appcache::AppCacheInfoVector; -using appcache::AppCacheResponseReader; -using appcache::AppCacheServiceImpl; -using appcache::HttpResponseInfoIOBuffer; namespace content { namespace { @@ -199,7 +189,7 @@ TEST_F(AppCacheServiceImplTest, DeleteAppCachesForOrigin) { delete_completion_count_ = 0; // Should succeed given an empty info collection. - mock_storage()->SimulateGetAllInfo(new AppCacheInfoCollection); + mock_storage()->SimulateGetAllInfo(new content::AppCacheInfoCollection); service_->DeleteAppCachesForOrigin(kOrigin, deletion_callback_); EXPECT_EQ(0, delete_completion_count_); base::RunLoop().RunUntilIdle(); diff --git a/content/browser/appcache/appcache_storage.cc b/content/browser/appcache/appcache_storage.cc new file mode 100644 index 0000000..d78339d --- /dev/null +++ b/content/browser/appcache/appcache_storage.cc @@ -0,0 +1,136 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/appcache/appcache_storage.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/stl_util.h" +#include "content/browser/appcache/appcache_response.h" +#include "content/browser/appcache/appcache_service_impl.h" +#include "webkit/browser/quota/quota_client.h" +#include "webkit/browser/quota/quota_manager_proxy.h" + +namespace content { + +// static +const int64 AppCacheStorage::kUnitializedId = -1; + +AppCacheStorage::AppCacheStorage(AppCacheServiceImpl* service) + : last_cache_id_(kUnitializedId), last_group_id_(kUnitializedId), + last_response_id_(kUnitializedId), service_(service) { +} + +AppCacheStorage::~AppCacheStorage() { + STLDeleteValues(&pending_info_loads_); + DCHECK(delegate_references_.empty()); +} + +AppCacheStorage::DelegateReference::DelegateReference( + Delegate* delegate, AppCacheStorage* storage) + : delegate(delegate), storage(storage) { + storage->delegate_references_.insert( + DelegateReferenceMap::value_type(delegate, this)); +} + +AppCacheStorage::DelegateReference::~DelegateReference() { + if (delegate) + storage->delegate_references_.erase(delegate); +} + +AppCacheStorage::ResponseInfoLoadTask::ResponseInfoLoadTask( + const GURL& manifest_url, + int64 group_id, + int64 response_id, + AppCacheStorage* storage) + : storage_(storage), + manifest_url_(manifest_url), + group_id_(group_id), + response_id_(response_id), + info_buffer_(new HttpResponseInfoIOBuffer) { + storage_->pending_info_loads_.insert( + PendingResponseInfoLoads::value_type(response_id, this)); +} + +AppCacheStorage::ResponseInfoLoadTask::~ResponseInfoLoadTask() { +} + +void AppCacheStorage::ResponseInfoLoadTask::StartIfNeeded() { + if (reader_) + return; + reader_.reset( + storage_->CreateResponseReader(manifest_url_, group_id_, response_id_)); + reader_->ReadInfo(info_buffer_.get(), + base::Bind(&ResponseInfoLoadTask::OnReadComplete, + base::Unretained(this))); +} + +void AppCacheStorage::ResponseInfoLoadTask::OnReadComplete(int result) { + storage_->pending_info_loads_.erase(response_id_); + scoped_refptr<AppCacheResponseInfo> info; + if (result >= 0) { + info = new AppCacheResponseInfo(storage_, manifest_url_, + response_id_, + info_buffer_->http_info.release(), + info_buffer_->response_data_size); + } + FOR_EACH_DELEGATE(delegates_, OnResponseInfoLoaded(info.get(), response_id_)); + delete this; +} + +void AppCacheStorage::LoadResponseInfo( + const GURL& manifest_url, int64 group_id, int64 id, Delegate* delegate) { + AppCacheResponseInfo* info = working_set_.GetResponseInfo(id); + if (info) { + delegate->OnResponseInfoLoaded(info, id); + return; + } + ResponseInfoLoadTask* info_load = + GetOrCreateResponseInfoLoadTask(manifest_url, group_id, id); + DCHECK(manifest_url == info_load->manifest_url()); + DCHECK(group_id == info_load->group_id()); + DCHECK(id == info_load->response_id()); + info_load->AddDelegate(GetOrCreateDelegateReference(delegate)); + info_load->StartIfNeeded(); +} + +void AppCacheStorage::UpdateUsageMapAndNotify( + const GURL& origin, int64 new_usage) { + DCHECK_GE(new_usage, 0); + int64 old_usage = usage_map_[origin]; + if (new_usage > 0) + usage_map_[origin] = new_usage; + else + usage_map_.erase(origin); + if (new_usage != old_usage && service()->quota_manager_proxy()) { + service()->quota_manager_proxy()->NotifyStorageModified( + quota::QuotaClient::kAppcache, + origin, quota::kStorageTypeTemporary, + new_usage - old_usage); + } +} + +void AppCacheStorage::ClearUsageMapAndNotify() { + if (service()->quota_manager_proxy()) { + for (UsageMap::const_iterator iter = usage_map_.begin(); + iter != usage_map_.end(); ++iter) { + service()->quota_manager_proxy()->NotifyStorageModified( + quota::QuotaClient::kAppcache, + iter->first, quota::kStorageTypeTemporary, + -(iter->second)); + } + } + usage_map_.clear(); +} + +void AppCacheStorage::NotifyStorageAccessed(const GURL& origin) { + if (service()->quota_manager_proxy() && + usage_map_.find(origin) != usage_map_.end()) + service()->quota_manager_proxy()->NotifyStorageAccessed( + quota::QuotaClient::kAppcache, + origin, quota::kStorageTypeTemporary); +} + +} // namespace content + diff --git a/content/browser/appcache/appcache_storage.h b/content/browser/appcache/appcache_storage.h new file mode 100644 index 0000000..871110c --- /dev/null +++ b/content/browser/appcache/appcache_storage.h @@ -0,0 +1,330 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_APPCACHE_APPCACHE_STORAGE_H_ +#define CONTENT_BROWSER_APPCACHE_APPCACHE_STORAGE_H_ + +#include <map> +#include <vector> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/gtest_prod_util.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "content/browser/appcache/appcache_working_set.h" +#include "content/common/content_export.h" +#include "net/base/completion_callback.h" + +class GURL; + +namespace content { +FORWARD_DECLARE_TEST(AppCacheStorageTest, DelegateReferences); +FORWARD_DECLARE_TEST(AppCacheStorageTest, UsageMap); +class AppCacheQuotaClientTest; +class AppCacheResponseTest; +class AppCacheStorageTest; +} + +namespace content { + +class AppCache; +class AppCacheEntry; +class AppCacheGroup; +class AppCacheResponseReader; +class AppCacheResponseWriter; +class AppCacheServiceImpl; +struct AppCacheInfoCollection; +struct HttpResponseInfoIOBuffer; + +class CONTENT_EXPORT AppCacheStorage { + public: + typedef std::map<GURL, int64> UsageMap; + + class CONTENT_EXPORT Delegate { + public: + // If retrieval fails, 'collection' will be NULL. + virtual void OnAllInfo(AppCacheInfoCollection* collection) {} + + // If a load fails the 'cache' will be NULL. + virtual void OnCacheLoaded(AppCache* cache, int64 cache_id) {} + + // If a load fails the 'group' will be NULL. + virtual void OnGroupLoaded( + AppCacheGroup* group, const GURL& manifest_url) {} + + // If successfully stored 'success' will be true. + virtual void OnGroupAndNewestCacheStored( + AppCacheGroup* group, AppCache* newest_cache, bool success, + bool would_exceed_quota) {} + + // If the operation fails, success will be false. + virtual void OnGroupMadeObsolete(AppCacheGroup* group, + bool success, + int response_code) {} + + // If a load fails the 'response_info' will be NULL. + virtual void OnResponseInfoLoaded( + AppCacheResponseInfo* response_info, int64 response_id) {} + + // If no response is found, entry.response_id() and + // fallback_entry.response_id() will be kAppCacheNoResponseId. + // If the response is the entry for an intercept or fallback + // namespace, the url of the namespece entry is returned. + // If a response is found, the cache id and manifest url of the + // containing cache and group are also returned. + virtual void OnMainResponseFound( + const GURL& url, const AppCacheEntry& entry, + const GURL& namespace_entry_url, const AppCacheEntry& fallback_entry, + int64 cache_id, int64 group_id, const GURL& mainfest_url) {} + + protected: + virtual ~Delegate() {} + }; + + explicit AppCacheStorage(AppCacheServiceImpl* service); + virtual ~AppCacheStorage(); + + // Schedules a task to retrieve basic info about all groups and caches + // stored in the system. Upon completion the delegate will be called + // with the results. + virtual void GetAllInfo(Delegate* delegate) = 0; + + // Schedules a cache to be loaded from storage. Upon load completion + // the delegate will be called back. If the cache already resides in + // memory, the delegate will be called back immediately without returning + // to the message loop. If the load fails, the delegate will be called + // back with a NULL cache pointer. + virtual void LoadCache(int64 id, Delegate* delegate) = 0; + + // Schedules a group and its newest cache, if any, to be loaded from storage. + // Upon load completion the delegate will be called back. If the group + // and newest cache already reside in memory, the delegate will be called + // back immediately without returning to the message loop. If the load fails, + // the delegate will be called back with a NULL group pointer. + virtual void LoadOrCreateGroup( + const GURL& manifest_url, Delegate* delegate) = 0; + + // Schedules response info to be loaded from storage. + // Upon load completion the delegate will be called back. If the data + // already resides in memory, the delegate will be called back + // immediately without returning to the message loop. If the load fails, + // the delegate will be called back with a NULL pointer. + virtual void LoadResponseInfo( + const GURL& manifest_url, int64 group_id, int64 response_id, + Delegate* delegate); + + // Schedules a group and its newest complete cache to be initially stored or + // incrementally updated with new changes. Upon completion the delegate + // will be called back. A group without a newest cache cannot be stored. + // It's a programming error to call this method without a newest cache. A + // side effect of storing a new newest cache is the removal of the group's + // old caches and responses from persistent storage (although they may still + // linger in the in-memory working set until no longer needed). The new + // cache will be added as the group's newest complete cache only if storage + // succeeds. + virtual void StoreGroupAndNewestCache( + AppCacheGroup* group, AppCache* newest_cache, Delegate* delegate) = 0; + + // Schedules a query to identify a response for a main request. Upon + // completion the delegate will be called back. + virtual void FindResponseForMainRequest( + const GURL& url, + const GURL& preferred_manifest_url, + Delegate* delegate) = 0; + + // Performs an immediate lookup of the in-memory cache to + // identify a response for a sub resource request. + virtual void FindResponseForSubRequest( + AppCache* cache, const GURL& url, + AppCacheEntry* found_entry, AppCacheEntry* found_fallback_entry, + bool* found_network_namespace) = 0; + + // Immediately updates in-memory storage, if the cache is in memory, + // and schedules a task to update persistent storage. If the cache is + // already scheduled to be loaded, upon loading completion the entry + // will be marked. There is no delegate completion callback. + virtual void MarkEntryAsForeign(const GURL& entry_url, int64 cache_id) = 0; + + // Schedules a task to update persistent storage and doom the group and all + // related caches and responses for deletion. Upon completion the in-memory + // instance is marked as obsolete and the delegate callback is called. + virtual void MakeGroupObsolete(AppCacheGroup* group, + Delegate* delegate, + int response_code) = 0; + + // Cancels all pending callbacks for the delegate. The delegate callbacks + // will not be invoked after, however any scheduled operations will still + // take place. The callbacks for subsequently scheduled operations are + // unaffected. + void CancelDelegateCallbacks(Delegate* delegate) { + DelegateReference* delegate_reference = GetDelegateReference(delegate); + if (delegate_reference) + delegate_reference->CancelReference(); + } + + // Creates a reader to read a response from storage. + virtual AppCacheResponseReader* CreateResponseReader( + const GURL& manifest_url, int64 group_id, int64 response_id) = 0; + + // Creates a writer to write a new response to storage. This call + // establishes a new response id. + virtual AppCacheResponseWriter* CreateResponseWriter( + const GURL& manifest_url, int64 group_id) = 0; + + // Schedules the lazy deletion of responses and saves the ids + // persistently such that the responses will be deleted upon restart + // if they aren't deleted prior to shutdown. + virtual void DoomResponses( + const GURL& manifest_url, const std::vector<int64>& response_ids) = 0; + + // Schedules the lazy deletion of responses without persistently saving + // the response ids. + virtual void DeleteResponses( + const GURL& manifest_url, const std::vector<int64>& response_ids) = 0; + + // Generates unique storage ids for different object types. + int64 NewCacheId() { + return ++last_cache_id_; + } + int64 NewGroupId() { + return ++last_group_id_; + } + + // The working set of object instances currently in memory. + AppCacheWorkingSet* working_set() { return &working_set_; } + + // A map of origins to usage. + const UsageMap* usage_map() { return &usage_map_; } + + // Simple ptr back to the service object that owns us. + AppCacheServiceImpl* service() { return service_; } + + protected: + friend class content::AppCacheQuotaClientTest; + friend class content::AppCacheResponseTest; + friend class content::AppCacheStorageTest; + + // Helper to call a collection of delegates. + #define FOR_EACH_DELEGATE(delegates, func_and_args) \ + do { \ + for (DelegateReferenceVector::iterator it = delegates.begin(); \ + it != delegates.end(); ++it) { \ + if (it->get()->delegate) \ + it->get()->delegate->func_and_args; \ + } \ + } while (0) + + // Helper used to manage multiple references to a 'delegate' and to + // allow all pending callbacks to that delegate to be easily cancelled. + struct DelegateReference : public base::RefCounted<DelegateReference> { + Delegate* delegate; + AppCacheStorage* storage; + + DelegateReference(Delegate* delegate, AppCacheStorage* storage); + + void CancelReference() { + storage->delegate_references_.erase(delegate); + storage = NULL; + delegate = NULL; + } + + private: + friend class base::RefCounted<DelegateReference>; + + virtual ~DelegateReference(); + }; + typedef std::map<Delegate*, DelegateReference*> DelegateReferenceMap; + typedef std::vector<scoped_refptr<DelegateReference> > + DelegateReferenceVector; + + // Helper used to manage an async LoadResponseInfo calls on behalf of + // multiple callers. + class ResponseInfoLoadTask { + public: + ResponseInfoLoadTask(const GURL& manifest_url, int64 group_id, + int64 response_id, AppCacheStorage* storage); + ~ResponseInfoLoadTask(); + + int64 response_id() const { return response_id_; } + const GURL& manifest_url() const { return manifest_url_; } + int64 group_id() const { return group_id_; } + + void AddDelegate(DelegateReference* delegate_reference) { + delegates_.push_back(delegate_reference); + } + + void StartIfNeeded(); + + private: + void OnReadComplete(int result); + + AppCacheStorage* storage_; + GURL manifest_url_; + int64 group_id_; + int64 response_id_; + scoped_ptr<AppCacheResponseReader> reader_; + DelegateReferenceVector delegates_; + scoped_refptr<HttpResponseInfoIOBuffer> info_buffer_; + }; + + typedef std::map<int64, ResponseInfoLoadTask*> PendingResponseInfoLoads; + + DelegateReference* GetDelegateReference(Delegate* delegate) { + DelegateReferenceMap::iterator iter = + delegate_references_.find(delegate); + if (iter != delegate_references_.end()) + return iter->second; + return NULL; + } + + DelegateReference* GetOrCreateDelegateReference(Delegate* delegate) { + DelegateReference* reference = GetDelegateReference(delegate); + if (reference) + return reference; + return new DelegateReference(delegate, this); + } + + ResponseInfoLoadTask* GetOrCreateResponseInfoLoadTask( + const GURL& manifest_url, int64 group_id, int64 response_id) { + PendingResponseInfoLoads::iterator iter = + pending_info_loads_.find(response_id); + if (iter != pending_info_loads_.end()) + return iter->second; + return new ResponseInfoLoadTask(manifest_url, group_id, response_id, this); + } + + // Should only be called when creating a new response writer. + int64 NewResponseId() { + return ++last_response_id_; + } + + // Helpers to query and notify the QuotaManager. + void UpdateUsageMapAndNotify(const GURL& origin, int64 new_usage); + void ClearUsageMapAndNotify(); + void NotifyStorageAccessed(const GURL& origin); + + // The last storage id used for different object types. + int64 last_cache_id_; + int64 last_group_id_; + int64 last_response_id_; + + UsageMap usage_map_; // maps origin to usage + AppCacheWorkingSet working_set_; + AppCacheServiceImpl* service_; + DelegateReferenceMap delegate_references_; + PendingResponseInfoLoads pending_info_loads_; + + // The set of last ids must be retrieved from storage prior to being used. + static const int64 kUnitializedId; + + FRIEND_TEST_ALL_PREFIXES(content::AppCacheStorageTest, DelegateReferences); + FRIEND_TEST_ALL_PREFIXES(content::AppCacheStorageTest, UsageMap); + + DISALLOW_COPY_AND_ASSIGN(AppCacheStorage); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_APPCACHE_APPCACHE_STORAGE_H_ diff --git a/content/browser/appcache/appcache_storage_impl.cc b/content/browser/appcache/appcache_storage_impl.cc new file mode 100644 index 0000000..dee1f9c --- /dev/null +++ b/content/browser/appcache/appcache_storage_impl.cc @@ -0,0 +1,1860 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/appcache/appcache_storage_impl.h" + +#include <algorithm> +#include <functional> +#include <set> +#include <vector> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "base/stl_util.h" +#include "base/strings/string_util.h" +#include "content/browser/appcache/appcache.h" +#include "content/browser/appcache/appcache_database.h" +#include "content/browser/appcache/appcache_entry.h" +#include "content/browser/appcache/appcache_group.h" +#include "content/browser/appcache/appcache_histograms.h" +#include "content/browser/appcache/appcache_quota_client.h" +#include "content/browser/appcache/appcache_response.h" +#include "content/browser/appcache/appcache_service_impl.h" +#include "net/base/cache_type.h" +#include "net/base/net_errors.h" +#include "sql/connection.h" +#include "sql/transaction.h" +#include "webkit/browser/quota/quota_client.h" +#include "webkit/browser/quota/quota_manager.h" +#include "webkit/browser/quota/quota_manager_proxy.h" +#include "webkit/browser/quota/special_storage_policy.h" + +namespace content { + +// Hard coded default when not using quota management. +static const int kDefaultQuota = 5 * 1024 * 1024; + +static const int kMaxDiskCacheSize = 250 * 1024 * 1024; +static const int kMaxMemDiskCacheSize = 10 * 1024 * 1024; +static const base::FilePath::CharType kDiskCacheDirectoryName[] = + FILE_PATH_LITERAL("Cache"); + +namespace { + +// Helpers for clearing data from the AppCacheDatabase. +bool DeleteGroupAndRelatedRecords(AppCacheDatabase* database, + int64 group_id, + std::vector<int64>* deletable_response_ids) { + AppCacheDatabase::CacheRecord cache_record; + bool success = false; + if (database->FindCacheForGroup(group_id, &cache_record)) { + database->FindResponseIdsForCacheAsVector(cache_record.cache_id, + deletable_response_ids); + success = + database->DeleteGroup(group_id) && + database->DeleteCache(cache_record.cache_id) && + database->DeleteEntriesForCache(cache_record.cache_id) && + database->DeleteNamespacesForCache(cache_record.cache_id) && + database->DeleteOnlineWhiteListForCache(cache_record.cache_id) && + database->InsertDeletableResponseIds(*deletable_response_ids); + } else { + NOTREACHED() << "A existing group without a cache is unexpected"; + success = database->DeleteGroup(group_id); + } + return success; +} + +// Destroys |database|. If there is appcache data to be deleted +// (|force_keep_session_state| is false), deletes session-only appcache data. +void ClearSessionOnlyOrigins( + AppCacheDatabase* database, + scoped_refptr<quota::SpecialStoragePolicy> special_storage_policy, + bool force_keep_session_state) { + scoped_ptr<AppCacheDatabase> database_to_delete(database); + + // If saving session state, only delete the database. + if (force_keep_session_state) + return; + + bool has_session_only_appcaches = + special_storage_policy.get() && + special_storage_policy->HasSessionOnlyOrigins(); + + // Clearning only session-only databases, and there are none. + if (!has_session_only_appcaches) + return; + + std::set<GURL> origins; + database->FindOriginsWithGroups(&origins); + if (origins.empty()) + return; // nothing to delete + + sql::Connection* connection = database->db_connection(); + if (!connection) { + NOTREACHED() << "Missing database connection."; + return; + } + + std::set<GURL>::const_iterator origin; + for (origin = origins.begin(); origin != origins.end(); ++origin) { + if (!special_storage_policy->IsStorageSessionOnly(*origin)) + continue; + if (special_storage_policy.get() && + special_storage_policy->IsStorageProtected(*origin)) + continue; + + std::vector<AppCacheDatabase::GroupRecord> groups; + database->FindGroupsForOrigin(*origin, &groups); + std::vector<AppCacheDatabase::GroupRecord>::const_iterator group; + for (group = groups.begin(); group != groups.end(); ++group) { + sql::Transaction transaction(connection); + if (!transaction.Begin()) { + NOTREACHED() << "Failed to start transaction"; + return; + } + std::vector<int64> deletable_response_ids; + bool success = DeleteGroupAndRelatedRecords(database, + group->group_id, + &deletable_response_ids); + success = success && transaction.Commit(); + DCHECK(success); + } // for each group + } // for each origin +} + +} // namespace + +// DatabaseTask ----------------------------------------- + +class AppCacheStorageImpl::DatabaseTask + : public base::RefCountedThreadSafe<DatabaseTask> { + public: + explicit DatabaseTask(AppCacheStorageImpl* storage) + : storage_(storage), database_(storage->database_), + io_thread_(base::MessageLoopProxy::current()) { + DCHECK(io_thread_.get()); + } + + void AddDelegate(DelegateReference* delegate_reference) { + delegates_.push_back(make_scoped_refptr(delegate_reference)); + } + + // Schedules a task to be Run() on the DB thread. Tasks + // are run in the order in which they are scheduled. + void Schedule(); + + // Called on the DB thread. + virtual void Run() = 0; + + // Called on the IO thread after Run() has completed. + virtual void RunCompleted() {} + + // Once scheduled a task cannot be cancelled, but the + // call to RunCompleted may be. This method should only be + // called on the IO thread. This is used by AppCacheStorageImpl + // to cancel the completion calls when AppCacheStorageImpl is + // destructed. This method may be overriden to release or delete + // additional data associated with the task that is not DB thread + // safe. If overriden, this base class method must be called from + // within the override. + virtual void CancelCompletion(); + + protected: + friend class base::RefCountedThreadSafe<DatabaseTask>; + virtual ~DatabaseTask() {} + + AppCacheStorageImpl* storage_; + AppCacheDatabase* database_; + DelegateReferenceVector delegates_; + + private: + void CallRun(base::TimeTicks schedule_time); + void CallRunCompleted(base::TimeTicks schedule_time); + void OnFatalError(); + + scoped_refptr<base::MessageLoopProxy> io_thread_; +}; + +void AppCacheStorageImpl::DatabaseTask::Schedule() { + DCHECK(storage_); + DCHECK(io_thread_->BelongsToCurrentThread()); + if (!storage_->database_) + return; + + if (storage_->db_thread_->PostTask( + FROM_HERE, + base::Bind(&DatabaseTask::CallRun, this, base::TimeTicks::Now()))) { + storage_->scheduled_database_tasks_.push_back(this); + } else { + NOTREACHED() << "Thread for database tasks is not running."; + } +} + +void AppCacheStorageImpl::DatabaseTask::CancelCompletion() { + DCHECK(io_thread_->BelongsToCurrentThread()); + delegates_.clear(); + storage_ = NULL; +} + +void AppCacheStorageImpl::DatabaseTask::CallRun( + base::TimeTicks schedule_time) { + AppCacheHistograms::AddTaskQueueTimeSample( + base::TimeTicks::Now() - schedule_time); + if (!database_->is_disabled()) { + base::TimeTicks run_time = base::TimeTicks::Now(); + Run(); + AppCacheHistograms::AddTaskRunTimeSample( + base::TimeTicks::Now() - run_time); + + if (database_->was_corruption_detected()) { + AppCacheHistograms::CountCorruptionDetected(); + database_->Disable(); + } + if (database_->is_disabled()) { + io_thread_->PostTask( + FROM_HERE, + base::Bind(&DatabaseTask::OnFatalError, this)); + } + } + io_thread_->PostTask( + FROM_HERE, + base::Bind(&DatabaseTask::CallRunCompleted, this, + base::TimeTicks::Now())); +} + +void AppCacheStorageImpl::DatabaseTask::CallRunCompleted( + base::TimeTicks schedule_time) { + AppCacheHistograms::AddCompletionQueueTimeSample( + base::TimeTicks::Now() - schedule_time); + if (storage_) { + DCHECK(io_thread_->BelongsToCurrentThread()); + DCHECK(storage_->scheduled_database_tasks_.front() == this); + storage_->scheduled_database_tasks_.pop_front(); + base::TimeTicks run_time = base::TimeTicks::Now(); + RunCompleted(); + AppCacheHistograms::AddCompletionRunTimeSample( + base::TimeTicks::Now() - run_time); + delegates_.clear(); + } +} + +void AppCacheStorageImpl::DatabaseTask::OnFatalError() { + if (storage_) { + DCHECK(io_thread_->BelongsToCurrentThread()); + storage_->Disable(); + storage_->DeleteAndStartOver(); + } +} + +// InitTask ------- + +class AppCacheStorageImpl::InitTask : public DatabaseTask { + public: + explicit InitTask(AppCacheStorageImpl* storage) + : DatabaseTask(storage), last_group_id_(0), + last_cache_id_(0), last_response_id_(0), + last_deletable_response_rowid_(0) { + if (!storage->is_incognito_) { + db_file_path_ = + storage->cache_directory_.Append(kAppCacheDatabaseName); + disk_cache_directory_ = + storage->cache_directory_.Append(kDiskCacheDirectoryName); + } + } + + // DatabaseTask: + virtual void Run() OVERRIDE; + virtual void RunCompleted() OVERRIDE; + + protected: + virtual ~InitTask() {} + + private: + base::FilePath db_file_path_; + base::FilePath disk_cache_directory_; + int64 last_group_id_; + int64 last_cache_id_; + int64 last_response_id_; + int64 last_deletable_response_rowid_; + std::map<GURL, int64> usage_map_; +}; + +void AppCacheStorageImpl::InitTask::Run() { + // If there is no sql database, ensure there is no disk cache either. + if (!db_file_path_.empty() && + !base::PathExists(db_file_path_) && + base::DirectoryExists(disk_cache_directory_)) { + base::DeleteFile(disk_cache_directory_, true); + if (base::DirectoryExists(disk_cache_directory_)) { + database_->Disable(); // This triggers OnFatalError handling. + return; + } + } + + database_->FindLastStorageIds( + &last_group_id_, &last_cache_id_, &last_response_id_, + &last_deletable_response_rowid_); + database_->GetAllOriginUsage(&usage_map_); +} + +void AppCacheStorageImpl::InitTask::RunCompleted() { + storage_->last_group_id_ = last_group_id_; + storage_->last_cache_id_ = last_cache_id_; + storage_->last_response_id_ = last_response_id_; + storage_->last_deletable_response_rowid_ = last_deletable_response_rowid_; + + if (!storage_->is_disabled()) { + storage_->usage_map_.swap(usage_map_); + const base::TimeDelta kDelay = base::TimeDelta::FromMinutes(5); + base::MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&AppCacheStorageImpl::DelayedStartDeletingUnusedResponses, + storage_->weak_factory_.GetWeakPtr()), + kDelay); + } + + if (storage_->service()->quota_client()) + storage_->service()->quota_client()->NotifyAppCacheReady(); +} + +// DisableDatabaseTask ------- + +class AppCacheStorageImpl::DisableDatabaseTask : public DatabaseTask { + public: + explicit DisableDatabaseTask(AppCacheStorageImpl* storage) + : DatabaseTask(storage) {} + + // DatabaseTask: + virtual void Run() OVERRIDE { database_->Disable(); } + + protected: + virtual ~DisableDatabaseTask() {} +}; + +// GetAllInfoTask ------- + +class AppCacheStorageImpl::GetAllInfoTask : public DatabaseTask { + public: + explicit GetAllInfoTask(AppCacheStorageImpl* storage) + : DatabaseTask(storage), + info_collection_(new AppCacheInfoCollection()) { + } + + // DatabaseTask: + virtual void Run() OVERRIDE; + virtual void RunCompleted() OVERRIDE; + + protected: + virtual ~GetAllInfoTask() {} + + private: + scoped_refptr<AppCacheInfoCollection> info_collection_; +}; + +void AppCacheStorageImpl::GetAllInfoTask::Run() { + std::set<GURL> origins; + database_->FindOriginsWithGroups(&origins); + for (std::set<GURL>::const_iterator origin = origins.begin(); + origin != origins.end(); ++origin) { + AppCacheInfoVector& infos = + info_collection_->infos_by_origin[*origin]; + std::vector<AppCacheDatabase::GroupRecord> groups; + database_->FindGroupsForOrigin(*origin, &groups); + for (std::vector<AppCacheDatabase::GroupRecord>::const_iterator + group = groups.begin(); + group != groups.end(); ++group) { + AppCacheDatabase::CacheRecord cache_record; + database_->FindCacheForGroup(group->group_id, &cache_record); + AppCacheInfo info; + info.manifest_url = group->manifest_url; + info.creation_time = group->creation_time; + info.size = cache_record.cache_size; + info.last_access_time = group->last_access_time; + info.last_update_time = cache_record.update_time; + info.cache_id = cache_record.cache_id; + info.group_id = group->group_id; + info.is_complete = true; + infos.push_back(info); + } + } +} + +void AppCacheStorageImpl::GetAllInfoTask::RunCompleted() { + DCHECK(delegates_.size() == 1); + FOR_EACH_DELEGATE(delegates_, OnAllInfo(info_collection_.get())); +} + +// StoreOrLoadTask ------- + +class AppCacheStorageImpl::StoreOrLoadTask : public DatabaseTask { + protected: + explicit StoreOrLoadTask(AppCacheStorageImpl* storage) + : DatabaseTask(storage) {} + virtual ~StoreOrLoadTask() {} + + bool FindRelatedCacheRecords(int64 cache_id); + void CreateCacheAndGroupFromRecords( + scoped_refptr<AppCache>* cache, scoped_refptr<AppCacheGroup>* group); + + AppCacheDatabase::GroupRecord group_record_; + AppCacheDatabase::CacheRecord cache_record_; + std::vector<AppCacheDatabase::EntryRecord> entry_records_; + std::vector<AppCacheDatabase::NamespaceRecord> + intercept_namespace_records_; + std::vector<AppCacheDatabase::NamespaceRecord> + fallback_namespace_records_; + std::vector<AppCacheDatabase::OnlineWhiteListRecord> + online_whitelist_records_; +}; + +bool AppCacheStorageImpl::StoreOrLoadTask::FindRelatedCacheRecords( + int64 cache_id) { + return database_->FindEntriesForCache(cache_id, &entry_records_) && + database_->FindNamespacesForCache( + cache_id, &intercept_namespace_records_, + &fallback_namespace_records_) && + database_->FindOnlineWhiteListForCache( + cache_id, &online_whitelist_records_); +} + +void AppCacheStorageImpl::StoreOrLoadTask::CreateCacheAndGroupFromRecords( + scoped_refptr<AppCache>* cache, scoped_refptr<AppCacheGroup>* group) { + DCHECK(storage_ && cache && group); + + (*cache) = storage_->working_set_.GetCache(cache_record_.cache_id); + if (cache->get()) { + (*group) = cache->get()->owning_group(); + DCHECK(group->get()); + DCHECK_EQ(group_record_.group_id, group->get()->group_id()); + + // TODO(michaeln): histogram is fishing for clues to crbug/95101 + if (!cache->get()->GetEntry(group_record_.manifest_url)) { + AppCacheHistograms::AddMissingManifestDetectedAtCallsite( + AppCacheHistograms::CALLSITE_0); + } + + storage_->NotifyStorageAccessed(group_record_.origin); + return; + } + + (*cache) = new AppCache(storage_, cache_record_.cache_id); + cache->get()->InitializeWithDatabaseRecords( + cache_record_, entry_records_, + intercept_namespace_records_, + fallback_namespace_records_, + online_whitelist_records_); + cache->get()->set_complete(true); + + (*group) = storage_->working_set_.GetGroup(group_record_.manifest_url); + if (group->get()) { + DCHECK(group_record_.group_id == group->get()->group_id()); + group->get()->AddCache(cache->get()); + + // TODO(michaeln): histogram is fishing for clues to crbug/95101 + if (!cache->get()->GetEntry(group_record_.manifest_url)) { + AppCacheHistograms::AddMissingManifestDetectedAtCallsite( + AppCacheHistograms::CALLSITE_1); + } + } else { + (*group) = new AppCacheGroup( + storage_, group_record_.manifest_url, + group_record_.group_id); + group->get()->set_creation_time(group_record_.creation_time); + group->get()->AddCache(cache->get()); + + // TODO(michaeln): histogram is fishing for clues to crbug/95101 + if (!cache->get()->GetEntry(group_record_.manifest_url)) { + AppCacheHistograms::AddMissingManifestDetectedAtCallsite( + AppCacheHistograms::CALLSITE_2); + } + } + DCHECK(group->get()->newest_complete_cache() == cache->get()); + + // We have to update foriegn entries if MarkEntryAsForeignTasks + // are in flight. + std::vector<GURL> urls; + storage_->GetPendingForeignMarkingsForCache(cache->get()->cache_id(), &urls); + for (std::vector<GURL>::iterator iter = urls.begin(); + iter != urls.end(); ++iter) { + DCHECK(cache->get()->GetEntry(*iter)); + cache->get()->GetEntry(*iter)->add_types(AppCacheEntry::FOREIGN); + } + + storage_->NotifyStorageAccessed(group_record_.origin); + + // TODO(michaeln): Maybe verify that the responses we expect to exist + // do actually exist in the disk_cache (and if not then what?) +} + +// CacheLoadTask ------- + +class AppCacheStorageImpl::CacheLoadTask : public StoreOrLoadTask { + public: + CacheLoadTask(int64 cache_id, AppCacheStorageImpl* storage) + : StoreOrLoadTask(storage), cache_id_(cache_id), + success_(false) {} + + // DatabaseTask: + virtual void Run() OVERRIDE; + virtual void RunCompleted() OVERRIDE; + + protected: + virtual ~CacheLoadTask() {} + + private: + int64 cache_id_; + bool success_; +}; + +void AppCacheStorageImpl::CacheLoadTask::Run() { + success_ = + database_->FindCache(cache_id_, &cache_record_) && + database_->FindGroup(cache_record_.group_id, &group_record_) && + FindRelatedCacheRecords(cache_id_); + + if (success_) + database_->UpdateGroupLastAccessTime(group_record_.group_id, + base::Time::Now()); +} + +void AppCacheStorageImpl::CacheLoadTask::RunCompleted() { + storage_->pending_cache_loads_.erase(cache_id_); + scoped_refptr<AppCache> cache; + scoped_refptr<AppCacheGroup> group; + if (success_ && !storage_->is_disabled()) { + DCHECK(cache_record_.cache_id == cache_id_); + CreateCacheAndGroupFromRecords(&cache, &group); + } + FOR_EACH_DELEGATE(delegates_, OnCacheLoaded(cache.get(), cache_id_)); +} + +// GroupLoadTask ------- + +class AppCacheStorageImpl::GroupLoadTask : public StoreOrLoadTask { + public: + GroupLoadTask(GURL manifest_url, AppCacheStorageImpl* storage) + : StoreOrLoadTask(storage), manifest_url_(manifest_url), + success_(false) {} + + // DatabaseTask: + virtual void Run() OVERRIDE; + virtual void RunCompleted() OVERRIDE; + + protected: + virtual ~GroupLoadTask() {} + + private: + GURL manifest_url_; + bool success_; +}; + +void AppCacheStorageImpl::GroupLoadTask::Run() { + success_ = + database_->FindGroupForManifestUrl(manifest_url_, &group_record_) && + database_->FindCacheForGroup(group_record_.group_id, &cache_record_) && + FindRelatedCacheRecords(cache_record_.cache_id); + + if (success_) + database_->UpdateGroupLastAccessTime(group_record_.group_id, + base::Time::Now()); +} + +void AppCacheStorageImpl::GroupLoadTask::RunCompleted() { + storage_->pending_group_loads_.erase(manifest_url_); + scoped_refptr<AppCacheGroup> group; + scoped_refptr<AppCache> cache; + if (!storage_->is_disabled()) { + if (success_) { + DCHECK(group_record_.manifest_url == manifest_url_); + CreateCacheAndGroupFromRecords(&cache, &group); + } else { + group = storage_->working_set_.GetGroup(manifest_url_); + if (!group.get()) { + group = + new AppCacheGroup(storage_, manifest_url_, storage_->NewGroupId()); + } + } + } + FOR_EACH_DELEGATE(delegates_, OnGroupLoaded(group.get(), manifest_url_)); +} + +// StoreGroupAndCacheTask ------- + +class AppCacheStorageImpl::StoreGroupAndCacheTask : public StoreOrLoadTask { + public: + StoreGroupAndCacheTask(AppCacheStorageImpl* storage, AppCacheGroup* group, + AppCache* newest_cache); + + void GetQuotaThenSchedule(); + void OnQuotaCallback( + quota::QuotaStatusCode status, int64 usage, int64 quota); + + // DatabaseTask: + virtual void Run() OVERRIDE; + virtual void RunCompleted() OVERRIDE; + virtual void CancelCompletion() OVERRIDE; + + protected: + virtual ~StoreGroupAndCacheTask() {} + + private: + scoped_refptr<AppCacheGroup> group_; + scoped_refptr<AppCache> cache_; + bool success_; + bool would_exceed_quota_; + int64 space_available_; + int64 new_origin_usage_; + std::vector<int64> newly_deletable_response_ids_; +}; + +AppCacheStorageImpl::StoreGroupAndCacheTask::StoreGroupAndCacheTask( + AppCacheStorageImpl* storage, AppCacheGroup* group, AppCache* newest_cache) + : StoreOrLoadTask(storage), group_(group), cache_(newest_cache), + success_(false), would_exceed_quota_(false), + space_available_(-1), new_origin_usage_(-1) { + group_record_.group_id = group->group_id(); + group_record_.manifest_url = group->manifest_url(); + group_record_.origin = group_record_.manifest_url.GetOrigin(); + newest_cache->ToDatabaseRecords( + group, + &cache_record_, &entry_records_, + &intercept_namespace_records_, + &fallback_namespace_records_, + &online_whitelist_records_); +} + +void AppCacheStorageImpl::StoreGroupAndCacheTask::GetQuotaThenSchedule() { + quota::QuotaManager* quota_manager = NULL; + if (storage_->service()->quota_manager_proxy()) { + quota_manager = + storage_->service()->quota_manager_proxy()->quota_manager(); + } + + if (!quota_manager) { + if (storage_->service()->special_storage_policy() && + storage_->service()->special_storage_policy()->IsStorageUnlimited( + group_record_.origin)) + space_available_ = kint64max; + Schedule(); + return; + } + + // We have to ask the quota manager for the value. + storage_->pending_quota_queries_.insert(this); + quota_manager->GetUsageAndQuota( + group_record_.origin, quota::kStorageTypeTemporary, + base::Bind(&StoreGroupAndCacheTask::OnQuotaCallback, this)); +} + +void AppCacheStorageImpl::StoreGroupAndCacheTask::OnQuotaCallback( + quota::QuotaStatusCode status, int64 usage, int64 quota) { + if (storage_) { + if (status == quota::kQuotaStatusOk) + space_available_ = std::max(static_cast<int64>(0), quota - usage); + else + space_available_ = 0; + storage_->pending_quota_queries_.erase(this); + Schedule(); + } +} + +void AppCacheStorageImpl::StoreGroupAndCacheTask::Run() { + DCHECK(!success_); + sql::Connection* connection = database_->db_connection(); + if (!connection) + return; + + sql::Transaction transaction(connection); + if (!transaction.Begin()) + return; + + int64 old_origin_usage = database_->GetOriginUsage(group_record_.origin); + + AppCacheDatabase::GroupRecord existing_group; + success_ = database_->FindGroup(group_record_.group_id, &existing_group); + if (!success_) { + group_record_.creation_time = base::Time::Now(); + group_record_.last_access_time = base::Time::Now(); + success_ = database_->InsertGroup(&group_record_); + } else { + DCHECK(group_record_.group_id == existing_group.group_id); + DCHECK(group_record_.manifest_url == existing_group.manifest_url); + DCHECK(group_record_.origin == existing_group.origin); + + database_->UpdateGroupLastAccessTime(group_record_.group_id, + base::Time::Now()); + + AppCacheDatabase::CacheRecord cache; + if (database_->FindCacheForGroup(group_record_.group_id, &cache)) { + // Get the set of response ids in the old cache. + std::set<int64> existing_response_ids; + database_->FindResponseIdsForCacheAsSet(cache.cache_id, + &existing_response_ids); + + // Remove those that remain in the new cache. + std::vector<AppCacheDatabase::EntryRecord>::const_iterator entry_iter = + entry_records_.begin(); + while (entry_iter != entry_records_.end()) { + existing_response_ids.erase(entry_iter->response_id); + ++entry_iter; + } + + // The rest are deletable. + std::set<int64>::const_iterator id_iter = existing_response_ids.begin(); + while (id_iter != existing_response_ids.end()) { + newly_deletable_response_ids_.push_back(*id_iter); + ++id_iter; + } + + success_ = + database_->DeleteCache(cache.cache_id) && + database_->DeleteEntriesForCache(cache.cache_id) && + database_->DeleteNamespacesForCache(cache.cache_id) && + database_->DeleteOnlineWhiteListForCache(cache.cache_id) && + database_->InsertDeletableResponseIds(newly_deletable_response_ids_); + // TODO(michaeln): store group_id too with deletable ids + } else { + NOTREACHED() << "A existing group without a cache is unexpected"; + } + } + + success_ = + success_ && + database_->InsertCache(&cache_record_) && + database_->InsertEntryRecords(entry_records_) && + database_->InsertNamespaceRecords(intercept_namespace_records_) && + database_->InsertNamespaceRecords(fallback_namespace_records_) && + database_->InsertOnlineWhiteListRecords(online_whitelist_records_); + + if (!success_) + return; + + new_origin_usage_ = database_->GetOriginUsage(group_record_.origin); + + // Only check quota when the new usage exceeds the old usage. + if (new_origin_usage_ <= old_origin_usage) { + success_ = transaction.Commit(); + return; + } + + // Use a simple hard-coded value when not using quota management. + if (space_available_ == -1) { + if (new_origin_usage_ > kDefaultQuota) { + would_exceed_quota_ = true; + success_ = false; + return; + } + success_ = transaction.Commit(); + return; + } + + // Check limits based on the space availbable given to us via the + // quota system. + int64 delta = new_origin_usage_ - old_origin_usage; + if (delta > space_available_) { + would_exceed_quota_ = true; + success_ = false; + return; + } + + success_ = transaction.Commit(); +} + +void AppCacheStorageImpl::StoreGroupAndCacheTask::RunCompleted() { + if (success_) { + storage_->UpdateUsageMapAndNotify( + group_->manifest_url().GetOrigin(), new_origin_usage_); + if (cache_.get() != group_->newest_complete_cache()) { + cache_->set_complete(true); + group_->AddCache(cache_.get()); + } + if (group_->creation_time().is_null()) + group_->set_creation_time(group_record_.creation_time); + group_->AddNewlyDeletableResponseIds(&newly_deletable_response_ids_); + } + FOR_EACH_DELEGATE( + delegates_, + OnGroupAndNewestCacheStored( + group_.get(), cache_.get(), success_, would_exceed_quota_)); + group_ = NULL; + cache_ = NULL; + + // TODO(michaeln): if (would_exceed_quota_) what if the current usage + // also exceeds the quota? http://crbug.com/83968 +} + +void AppCacheStorageImpl::StoreGroupAndCacheTask::CancelCompletion() { + // Overriden to safely drop our reference to the group and cache + // which are not thread safe refcounted. + DatabaseTask::CancelCompletion(); + group_ = NULL; + cache_ = NULL; +} + +// FindMainResponseTask ------- + +// Helpers for FindMainResponseTask::Run() +namespace { +class SortByCachePreference + : public std::binary_function< + AppCacheDatabase::EntryRecord, + AppCacheDatabase::EntryRecord, + bool> { + public: + SortByCachePreference(int64 preferred_id, const std::set<int64>& in_use_ids) + : preferred_id_(preferred_id), in_use_ids_(in_use_ids) { + } + bool operator()( + const AppCacheDatabase::EntryRecord& lhs, + const AppCacheDatabase::EntryRecord& rhs) { + return compute_value(lhs) > compute_value(rhs); + } + private: + int compute_value(const AppCacheDatabase::EntryRecord& entry) { + if (entry.cache_id == preferred_id_) + return 100; + else if (in_use_ids_.find(entry.cache_id) != in_use_ids_.end()) + return 50; + return 0; + } + int64 preferred_id_; + const std::set<int64>& in_use_ids_; +}; + +bool SortByLength( + const AppCacheDatabase::NamespaceRecord& lhs, + const AppCacheDatabase::NamespaceRecord& rhs) { + return lhs.namespace_.namespace_url.spec().length() > + rhs.namespace_.namespace_url.spec().length(); +} + +class NetworkNamespaceHelper { + public: + explicit NetworkNamespaceHelper(AppCacheDatabase* database) + : database_(database) { + } + + bool IsInNetworkNamespace(const GURL& url, int64 cache_id) { + typedef std::pair<WhiteListMap::iterator, bool> InsertResult; + InsertResult result = namespaces_map_.insert( + WhiteListMap::value_type(cache_id, AppCacheNamespaceVector())); + if (result.second) + GetOnlineWhiteListForCache(cache_id, &result.first->second); + return AppCache::FindNamespace(result.first->second, url) != NULL; + } + + private: + void GetOnlineWhiteListForCache( + int64 cache_id, AppCacheNamespaceVector* namespaces) { + DCHECK(namespaces && namespaces->empty()); + typedef std::vector<AppCacheDatabase::OnlineWhiteListRecord> + WhiteListVector; + WhiteListVector records; + if (!database_->FindOnlineWhiteListForCache(cache_id, &records)) + return; + WhiteListVector::const_iterator iter = records.begin(); + while (iter != records.end()) { + namespaces->push_back( + AppCacheNamespace(APPCACHE_NETWORK_NAMESPACE, iter->namespace_url, + GURL(), iter->is_pattern)); + ++iter; + } + } + + // Key is cache id + typedef std::map<int64, AppCacheNamespaceVector> WhiteListMap; + WhiteListMap namespaces_map_; + AppCacheDatabase* database_; +}; + +} // namespace + +class AppCacheStorageImpl::FindMainResponseTask : public DatabaseTask { + public: + FindMainResponseTask(AppCacheStorageImpl* storage, + const GURL& url, + const GURL& preferred_manifest_url, + const AppCacheWorkingSet::GroupMap* groups_in_use) + : DatabaseTask(storage), url_(url), + preferred_manifest_url_(preferred_manifest_url), + cache_id_(kAppCacheNoCacheId), group_id_(0) { + if (groups_in_use) { + for (AppCacheWorkingSet::GroupMap::const_iterator it = + groups_in_use->begin(); + it != groups_in_use->end(); ++it) { + AppCacheGroup* group = it->second; + AppCache* cache = group->newest_complete_cache(); + if (group->is_obsolete() || !cache) + continue; + cache_ids_in_use_.insert(cache->cache_id()); + } + } + } + + // DatabaseTask: + virtual void Run() OVERRIDE; + virtual void RunCompleted() OVERRIDE; + + protected: + virtual ~FindMainResponseTask() {} + + private: + typedef std::vector<AppCacheDatabase::NamespaceRecord*> + NamespaceRecordPtrVector; + + bool FindExactMatch(int64 preferred_id); + bool FindNamespaceMatch(int64 preferred_id); + bool FindNamespaceHelper( + int64 preferred_cache_id, + AppCacheDatabase::NamespaceRecordVector* namespaces, + NetworkNamespaceHelper* network_namespace_helper); + bool FindFirstValidNamespace(const NamespaceRecordPtrVector& namespaces); + + GURL url_; + GURL preferred_manifest_url_; + std::set<int64> cache_ids_in_use_; + AppCacheEntry entry_; + AppCacheEntry fallback_entry_; + GURL namespace_entry_url_; + int64 cache_id_; + int64 group_id_; + GURL manifest_url_; +}; + +void AppCacheStorageImpl::FindMainResponseTask::Run() { + // NOTE: The heuristics around choosing amoungst multiple candidates + // is underspecified, and just plain not fully understood. This needs + // to be refined. + + // The 'preferred_manifest_url' is the url of the manifest associated + // with the page that opened or embedded the page being loaded now. + // We have a strong preference to use resources from that cache. + // We also have a lesser bias to use resources from caches that are currently + // being used by other unrelated pages. + // TODO(michaeln): come up with a 'preferred_manifest_url' in more cases + // - when navigating a frame whose current contents are from an appcache + // - when clicking an href in a frame that is appcached + int64 preferred_cache_id = kAppCacheNoCacheId; + if (!preferred_manifest_url_.is_empty()) { + AppCacheDatabase::GroupRecord preferred_group; + AppCacheDatabase::CacheRecord preferred_cache; + if (database_->FindGroupForManifestUrl( + preferred_manifest_url_, &preferred_group) && + database_->FindCacheForGroup( + preferred_group.group_id, &preferred_cache)) { + preferred_cache_id = preferred_cache.cache_id; + } + } + + if (FindExactMatch(preferred_cache_id) || + FindNamespaceMatch(preferred_cache_id)) { + // We found something. + DCHECK(cache_id_ != kAppCacheNoCacheId && !manifest_url_.is_empty() && + group_id_ != 0); + return; + } + + // We didn't find anything. + DCHECK(cache_id_ == kAppCacheNoCacheId && manifest_url_.is_empty() && + group_id_ == 0); +} + +bool AppCacheStorageImpl:: +FindMainResponseTask::FindExactMatch(int64 preferred_cache_id) { + std::vector<AppCacheDatabase::EntryRecord> entries; + if (database_->FindEntriesForUrl(url_, &entries) && !entries.empty()) { + // Sort them in order of preference, from the preferred_cache first, + // followed by hits from caches that are 'in use', then the rest. + std::sort(entries.begin(), entries.end(), + SortByCachePreference(preferred_cache_id, cache_ids_in_use_)); + + // Take the first with a valid, non-foreign entry. + std::vector<AppCacheDatabase::EntryRecord>::iterator iter; + for (iter = entries.begin(); iter < entries.end(); ++iter) { + AppCacheDatabase::GroupRecord group_record; + if ((iter->flags & AppCacheEntry::FOREIGN) || + !database_->FindGroupForCache(iter->cache_id, &group_record)) { + continue; + } + manifest_url_ = group_record.manifest_url; + group_id_ = group_record.group_id; + entry_ = AppCacheEntry(iter->flags, iter->response_id); + cache_id_ = iter->cache_id; + return true; // We found an exact match. + } + } + return false; +} + +bool AppCacheStorageImpl:: +FindMainResponseTask::FindNamespaceMatch(int64 preferred_cache_id) { + AppCacheDatabase::NamespaceRecordVector all_intercepts; + AppCacheDatabase::NamespaceRecordVector all_fallbacks; + if (!database_->FindNamespacesForOrigin( + url_.GetOrigin(), &all_intercepts, &all_fallbacks) + || (all_intercepts.empty() && all_fallbacks.empty())) { + return false; + } + + NetworkNamespaceHelper network_namespace_helper(database_); + if (FindNamespaceHelper(preferred_cache_id, + &all_intercepts, + &network_namespace_helper) || + FindNamespaceHelper(preferred_cache_id, + &all_fallbacks, + &network_namespace_helper)) { + return true; + } + return false; +} + +bool AppCacheStorageImpl:: +FindMainResponseTask::FindNamespaceHelper( + int64 preferred_cache_id, + AppCacheDatabase::NamespaceRecordVector* namespaces, + NetworkNamespaceHelper* network_namespace_helper) { + // Sort them by length, longer matches within the same cache/bucket take + // precedence. + std::sort(namespaces->begin(), namespaces->end(), SortByLength); + + NamespaceRecordPtrVector preferred_namespaces; + NamespaceRecordPtrVector inuse_namespaces; + NamespaceRecordPtrVector other_namespaces; + std::vector<AppCacheDatabase::NamespaceRecord>::iterator iter; + for (iter = namespaces->begin(); iter < namespaces->end(); ++iter) { + // Skip those that aren't a match. + if (!iter->namespace_.IsMatch(url_)) + continue; + + // Skip namespaces where the requested url falls into a network + // namespace of its containing appcache. + if (network_namespace_helper->IsInNetworkNamespace(url_, iter->cache_id)) + continue; + + // Bin them into one of our three buckets. + if (iter->cache_id == preferred_cache_id) + preferred_namespaces.push_back(&(*iter)); + else if (cache_ids_in_use_.find(iter->cache_id) != cache_ids_in_use_.end()) + inuse_namespaces.push_back(&(*iter)); + else + other_namespaces.push_back(&(*iter)); + } + + if (FindFirstValidNamespace(preferred_namespaces) || + FindFirstValidNamespace(inuse_namespaces) || + FindFirstValidNamespace(other_namespaces)) + return true; // We found one. + + // We didn't find anything. + return false; +} + +bool AppCacheStorageImpl:: +FindMainResponseTask::FindFirstValidNamespace( + const NamespaceRecordPtrVector& namespaces) { + // Take the first with a valid, non-foreign entry. + NamespaceRecordPtrVector::const_iterator iter; + for (iter = namespaces.begin(); iter < namespaces.end(); ++iter) { + AppCacheDatabase::EntryRecord entry_record; + if (database_->FindEntry((*iter)->cache_id, (*iter)->namespace_.target_url, + &entry_record)) { + AppCacheDatabase::GroupRecord group_record; + if ((entry_record.flags & AppCacheEntry::FOREIGN) || + !database_->FindGroupForCache(entry_record.cache_id, &group_record)) { + continue; + } + manifest_url_ = group_record.manifest_url; + group_id_ = group_record.group_id; + cache_id_ = (*iter)->cache_id; + namespace_entry_url_ = (*iter)->namespace_.target_url; + if ((*iter)->namespace_.type == APPCACHE_FALLBACK_NAMESPACE) + fallback_entry_ = AppCacheEntry(entry_record.flags, + entry_record.response_id); + else + entry_ = AppCacheEntry(entry_record.flags, entry_record.response_id); + return true; // We found one. + } + } + return false; // We didn't find a match. +} + +void AppCacheStorageImpl::FindMainResponseTask::RunCompleted() { + storage_->CallOnMainResponseFound( + &delegates_, url_, entry_, namespace_entry_url_, fallback_entry_, + cache_id_, group_id_, manifest_url_); +} + +// MarkEntryAsForeignTask ------- + +class AppCacheStorageImpl::MarkEntryAsForeignTask : public DatabaseTask { + public: + MarkEntryAsForeignTask( + AppCacheStorageImpl* storage, const GURL& url, int64 cache_id) + : DatabaseTask(storage), cache_id_(cache_id), entry_url_(url) {} + + // DatabaseTask: + virtual void Run() OVERRIDE; + virtual void RunCompleted() OVERRIDE; + + protected: + virtual ~MarkEntryAsForeignTask() {} + + private: + int64 cache_id_; + GURL entry_url_; +}; + +void AppCacheStorageImpl::MarkEntryAsForeignTask::Run() { + database_->AddEntryFlags(entry_url_, cache_id_, AppCacheEntry::FOREIGN); +} + +void AppCacheStorageImpl::MarkEntryAsForeignTask::RunCompleted() { + DCHECK(storage_->pending_foreign_markings_.front().first == entry_url_ && + storage_->pending_foreign_markings_.front().second == cache_id_); + storage_->pending_foreign_markings_.pop_front(); +} + +// MakeGroupObsoleteTask ------- + +class AppCacheStorageImpl::MakeGroupObsoleteTask : public DatabaseTask { + public: + MakeGroupObsoleteTask(AppCacheStorageImpl* storage, + AppCacheGroup* group, + int response_code); + + // DatabaseTask: + virtual void Run() OVERRIDE; + virtual void RunCompleted() OVERRIDE; + virtual void CancelCompletion() OVERRIDE; + + protected: + virtual ~MakeGroupObsoleteTask() {} + + private: + scoped_refptr<AppCacheGroup> group_; + int64 group_id_; + GURL origin_; + bool success_; + int response_code_; + int64 new_origin_usage_; + std::vector<int64> newly_deletable_response_ids_; +}; + +AppCacheStorageImpl::MakeGroupObsoleteTask::MakeGroupObsoleteTask( + AppCacheStorageImpl* storage, + AppCacheGroup* group, + int response_code) + : DatabaseTask(storage), + group_(group), + group_id_(group->group_id()), + origin_(group->manifest_url().GetOrigin()), + success_(false), + response_code_(response_code), + new_origin_usage_(-1) {} + +void AppCacheStorageImpl::MakeGroupObsoleteTask::Run() { + DCHECK(!success_); + sql::Connection* connection = database_->db_connection(); + if (!connection) + return; + + sql::Transaction transaction(connection); + if (!transaction.Begin()) + return; + + AppCacheDatabase::GroupRecord group_record; + if (!database_->FindGroup(group_id_, &group_record)) { + // This group doesn't exists in the database, nothing todo here. + new_origin_usage_ = database_->GetOriginUsage(origin_); + success_ = true; + return; + } + + DCHECK_EQ(group_record.origin, origin_); + success_ = DeleteGroupAndRelatedRecords(database_, + group_id_, + &newly_deletable_response_ids_); + + new_origin_usage_ = database_->GetOriginUsage(origin_); + success_ = success_ && transaction.Commit(); +} + +void AppCacheStorageImpl::MakeGroupObsoleteTask::RunCompleted() { + if (success_) { + group_->set_obsolete(true); + if (!storage_->is_disabled()) { + storage_->UpdateUsageMapAndNotify(origin_, new_origin_usage_); + group_->AddNewlyDeletableResponseIds(&newly_deletable_response_ids_); + + // Also remove from the working set, caches for an 'obsolete' group + // may linger in use, but the group itself cannot be looked up by + // 'manifest_url' in the working set any longer. + storage_->working_set()->RemoveGroup(group_.get()); + } + } + FOR_EACH_DELEGATE( + delegates_, OnGroupMadeObsolete(group_.get(), success_, response_code_)); + group_ = NULL; +} + +void AppCacheStorageImpl::MakeGroupObsoleteTask::CancelCompletion() { + // Overriden to safely drop our reference to the group + // which is not thread safe refcounted. + DatabaseTask::CancelCompletion(); + group_ = NULL; +} + +// GetDeletableResponseIdsTask ------- + +class AppCacheStorageImpl::GetDeletableResponseIdsTask : public DatabaseTask { + public: + GetDeletableResponseIdsTask(AppCacheStorageImpl* storage, int64 max_rowid) + : DatabaseTask(storage), max_rowid_(max_rowid) {} + + // DatabaseTask: + virtual void Run() OVERRIDE; + virtual void RunCompleted() OVERRIDE; + + protected: + virtual ~GetDeletableResponseIdsTask() {} + + private: + int64 max_rowid_; + std::vector<int64> response_ids_; +}; + +void AppCacheStorageImpl::GetDeletableResponseIdsTask::Run() { + const int kSqlLimit = 1000; + database_->GetDeletableResponseIds(&response_ids_, max_rowid_, kSqlLimit); + // TODO(michaeln): retrieve group_ids too +} + +void AppCacheStorageImpl::GetDeletableResponseIdsTask::RunCompleted() { + if (!response_ids_.empty()) + storage_->StartDeletingResponses(response_ids_); +} + +// InsertDeletableResponseIdsTask ------- + +class AppCacheStorageImpl::InsertDeletableResponseIdsTask + : public DatabaseTask { + public: + explicit InsertDeletableResponseIdsTask(AppCacheStorageImpl* storage) + : DatabaseTask(storage) {} + + // DatabaseTask: + virtual void Run() OVERRIDE; + + std::vector<int64> response_ids_; + + protected: + virtual ~InsertDeletableResponseIdsTask() {} +}; + +void AppCacheStorageImpl::InsertDeletableResponseIdsTask::Run() { + database_->InsertDeletableResponseIds(response_ids_); + // TODO(michaeln): store group_ids too +} + +// DeleteDeletableResponseIdsTask ------- + +class AppCacheStorageImpl::DeleteDeletableResponseIdsTask + : public DatabaseTask { + public: + explicit DeleteDeletableResponseIdsTask(AppCacheStorageImpl* storage) + : DatabaseTask(storage) {} + + // DatabaseTask: + virtual void Run() OVERRIDE; + + std::vector<int64> response_ids_; + + protected: + virtual ~DeleteDeletableResponseIdsTask() {} +}; + +void AppCacheStorageImpl::DeleteDeletableResponseIdsTask::Run() { + database_->DeleteDeletableResponseIds(response_ids_); +} + +// UpdateGroupLastAccessTimeTask ------- + +class AppCacheStorageImpl::UpdateGroupLastAccessTimeTask + : public DatabaseTask { + public: + UpdateGroupLastAccessTimeTask( + AppCacheStorageImpl* storage, AppCacheGroup* group, base::Time time) + : DatabaseTask(storage), group_id_(group->group_id()), + last_access_time_(time) { + storage->NotifyStorageAccessed(group->manifest_url().GetOrigin()); + } + + // DatabaseTask: + virtual void Run() OVERRIDE; + + protected: + virtual ~UpdateGroupLastAccessTimeTask() {} + + private: + int64 group_id_; + base::Time last_access_time_; +}; + +void AppCacheStorageImpl::UpdateGroupLastAccessTimeTask::Run() { + database_->UpdateGroupLastAccessTime(group_id_, last_access_time_); +} + + +// AppCacheStorageImpl --------------------------------------------------- + +AppCacheStorageImpl::AppCacheStorageImpl(AppCacheServiceImpl* service) + : AppCacheStorage(service), + is_incognito_(false), + is_response_deletion_scheduled_(false), + did_start_deleting_responses_(false), + last_deletable_response_rowid_(0), + database_(NULL), + is_disabled_(false), + weak_factory_(this) { +} + +AppCacheStorageImpl::~AppCacheStorageImpl() { + std::for_each(pending_quota_queries_.begin(), + pending_quota_queries_.end(), + std::mem_fun(&DatabaseTask::CancelCompletion)); + std::for_each(scheduled_database_tasks_.begin(), + scheduled_database_tasks_.end(), + std::mem_fun(&DatabaseTask::CancelCompletion)); + + if (database_ && + !db_thread_->PostTask( + FROM_HERE, + base::Bind(&ClearSessionOnlyOrigins, database_, + make_scoped_refptr(service_->special_storage_policy()), + service()->force_keep_session_state()))) { + delete database_; + } + database_ = NULL; // So no further database tasks can be scheduled. +} + +void AppCacheStorageImpl::Initialize(const base::FilePath& cache_directory, + base::MessageLoopProxy* db_thread, + base::MessageLoopProxy* cache_thread) { + DCHECK(db_thread); + + cache_directory_ = cache_directory; + is_incognito_ = cache_directory_.empty(); + + base::FilePath db_file_path; + if (!is_incognito_) + db_file_path = cache_directory_.Append(kAppCacheDatabaseName); + database_ = new AppCacheDatabase(db_file_path); + + db_thread_ = db_thread; + cache_thread_ = cache_thread; + + scoped_refptr<InitTask> task(new InitTask(this)); + task->Schedule(); +} + +void AppCacheStorageImpl::Disable() { + if (is_disabled_) + return; + VLOG(1) << "Disabling appcache storage."; + is_disabled_ = true; + ClearUsageMapAndNotify(); + working_set()->Disable(); + if (disk_cache_) + disk_cache_->Disable(); + scoped_refptr<DisableDatabaseTask> task(new DisableDatabaseTask(this)); + task->Schedule(); +} + +void AppCacheStorageImpl::GetAllInfo(Delegate* delegate) { + DCHECK(delegate); + scoped_refptr<GetAllInfoTask> task(new GetAllInfoTask(this)); + task->AddDelegate(GetOrCreateDelegateReference(delegate)); + task->Schedule(); +} + +void AppCacheStorageImpl::LoadCache(int64 id, Delegate* delegate) { + DCHECK(delegate); + if (is_disabled_) { + delegate->OnCacheLoaded(NULL, id); + return; + } + + AppCache* cache = working_set_.GetCache(id); + if (cache) { + delegate->OnCacheLoaded(cache, id); + if (cache->owning_group()) { + scoped_refptr<DatabaseTask> update_task( + new UpdateGroupLastAccessTimeTask( + this, cache->owning_group(), base::Time::Now())); + update_task->Schedule(); + } + return; + } + scoped_refptr<CacheLoadTask> task(GetPendingCacheLoadTask(id)); + if (task.get()) { + task->AddDelegate(GetOrCreateDelegateReference(delegate)); + return; + } + task = new CacheLoadTask(id, this); + task->AddDelegate(GetOrCreateDelegateReference(delegate)); + task->Schedule(); + pending_cache_loads_[id] = task.get(); +} + +void AppCacheStorageImpl::LoadOrCreateGroup( + const GURL& manifest_url, Delegate* delegate) { + DCHECK(delegate); + if (is_disabled_) { + delegate->OnGroupLoaded(NULL, manifest_url); + return; + } + + AppCacheGroup* group = working_set_.GetGroup(manifest_url); + if (group) { + delegate->OnGroupLoaded(group, manifest_url); + scoped_refptr<DatabaseTask> update_task( + new UpdateGroupLastAccessTimeTask( + this, group, base::Time::Now())); + update_task->Schedule(); + return; + } + + scoped_refptr<GroupLoadTask> task(GetPendingGroupLoadTask(manifest_url)); + if (task.get()) { + task->AddDelegate(GetOrCreateDelegateReference(delegate)); + return; + } + + if (usage_map_.find(manifest_url.GetOrigin()) == usage_map_.end()) { + // No need to query the database, return a new group immediately. + scoped_refptr<AppCacheGroup> group(new AppCacheGroup( + this, manifest_url, NewGroupId())); + delegate->OnGroupLoaded(group.get(), manifest_url); + return; + } + + task = new GroupLoadTask(manifest_url, this); + task->AddDelegate(GetOrCreateDelegateReference(delegate)); + task->Schedule(); + pending_group_loads_[manifest_url] = task.get(); +} + +void AppCacheStorageImpl::StoreGroupAndNewestCache( + AppCacheGroup* group, AppCache* newest_cache, Delegate* delegate) { + // TODO(michaeln): distinguish between a simple update of an existing + // cache that just adds new master entry(s), and the insertion of a + // whole new cache. The StoreGroupAndCacheTask as written will handle + // the simple update case in a very heavy weight way (delete all and + // the reinsert all over again). + DCHECK(group && delegate && newest_cache); + scoped_refptr<StoreGroupAndCacheTask> task( + new StoreGroupAndCacheTask(this, group, newest_cache)); + task->AddDelegate(GetOrCreateDelegateReference(delegate)); + task->GetQuotaThenSchedule(); + + // TODO(michaeln): histogram is fishing for clues to crbug/95101 + if (!newest_cache->GetEntry(group->manifest_url())) { + AppCacheHistograms::AddMissingManifestDetectedAtCallsite( + AppCacheHistograms::CALLSITE_3); + } +} + +void AppCacheStorageImpl::FindResponseForMainRequest( + const GURL& url, const GURL& preferred_manifest_url, + Delegate* delegate) { + DCHECK(delegate); + + const GURL* url_ptr = &url; + GURL url_no_ref; + if (url.has_ref()) { + GURL::Replacements replacements; + replacements.ClearRef(); + url_no_ref = url.ReplaceComponents(replacements); + url_ptr = &url_no_ref; + } + + const GURL origin = url.GetOrigin(); + + // First look in our working set for a direct hit without having to query + // the database. + const AppCacheWorkingSet::GroupMap* groups_in_use = + working_set()->GetGroupsInOrigin(origin); + if (groups_in_use) { + if (!preferred_manifest_url.is_empty()) { + AppCacheWorkingSet::GroupMap::const_iterator found = + groups_in_use->find(preferred_manifest_url); + if (found != groups_in_use->end() && + FindResponseForMainRequestInGroup( + found->second, *url_ptr, delegate)) { + return; + } + } else { + for (AppCacheWorkingSet::GroupMap::const_iterator it = + groups_in_use->begin(); + it != groups_in_use->end(); ++it) { + if (FindResponseForMainRequestInGroup( + it->second, *url_ptr, delegate)) { + return; + } + } + } + } + + if (IsInitTaskComplete() && usage_map_.find(origin) == usage_map_.end()) { + // No need to query the database, return async'ly but without going thru + // the DB thread. + scoped_refptr<AppCacheGroup> no_group; + scoped_refptr<AppCache> no_cache; + ScheduleSimpleTask( + base::Bind(&AppCacheStorageImpl::DeliverShortCircuitedFindMainResponse, + weak_factory_.GetWeakPtr(), url, AppCacheEntry(), no_group, + no_cache, + make_scoped_refptr(GetOrCreateDelegateReference(delegate)))); + return; + } + + // We have to query the database, schedule a database task to do so. + scoped_refptr<FindMainResponseTask> task( + new FindMainResponseTask(this, *url_ptr, preferred_manifest_url, + groups_in_use)); + task->AddDelegate(GetOrCreateDelegateReference(delegate)); + task->Schedule(); +} + +bool AppCacheStorageImpl::FindResponseForMainRequestInGroup( + AppCacheGroup* group, const GURL& url, Delegate* delegate) { + AppCache* cache = group->newest_complete_cache(); + if (group->is_obsolete() || !cache) + return false; + + AppCacheEntry* entry = cache->GetEntry(url); + if (!entry || entry->IsForeign()) + return false; + + ScheduleSimpleTask( + base::Bind(&AppCacheStorageImpl::DeliverShortCircuitedFindMainResponse, + weak_factory_.GetWeakPtr(), url, *entry, + make_scoped_refptr(group), make_scoped_refptr(cache), + make_scoped_refptr(GetOrCreateDelegateReference(delegate)))); + return true; +} + +void AppCacheStorageImpl::DeliverShortCircuitedFindMainResponse( + const GURL& url, + const AppCacheEntry& found_entry, + scoped_refptr<AppCacheGroup> group, + scoped_refptr<AppCache> cache, + scoped_refptr<DelegateReference> delegate_ref) { + if (delegate_ref->delegate) { + DelegateReferenceVector delegates(1, delegate_ref); + CallOnMainResponseFound( + &delegates, url, found_entry, + GURL(), AppCacheEntry(), + cache.get() ? cache->cache_id() : kAppCacheNoCacheId, + group.get() ? group->group_id() : kAppCacheNoCacheId, + group.get() ? group->manifest_url() : GURL()); + } +} + +void AppCacheStorageImpl::CallOnMainResponseFound( + DelegateReferenceVector* delegates, + const GURL& url, const AppCacheEntry& entry, + const GURL& namespace_entry_url, const AppCacheEntry& fallback_entry, + int64 cache_id, int64 group_id, const GURL& manifest_url) { + FOR_EACH_DELEGATE( + (*delegates), + OnMainResponseFound(url, entry, + namespace_entry_url, fallback_entry, + cache_id, group_id, manifest_url)); +} + +void AppCacheStorageImpl::FindResponseForSubRequest( + AppCache* cache, const GURL& url, + AppCacheEntry* found_entry, AppCacheEntry* found_fallback_entry, + bool* found_network_namespace) { + DCHECK(cache && cache->is_complete()); + + // When a group is forcibly deleted, all subresource loads for pages + // using caches in the group will result in a synthesized network errors. + // Forcible deletion is not a function that is covered by the HTML5 spec. + if (cache->owning_group()->is_being_deleted()) { + *found_entry = AppCacheEntry(); + *found_fallback_entry = AppCacheEntry(); + *found_network_namespace = false; + return; + } + + GURL fallback_namespace_not_used; + GURL intercept_namespace_not_used; + cache->FindResponseForRequest( + url, found_entry, &intercept_namespace_not_used, + found_fallback_entry, &fallback_namespace_not_used, + found_network_namespace); +} + +void AppCacheStorageImpl::MarkEntryAsForeign( + const GURL& entry_url, int64 cache_id) { + AppCache* cache = working_set_.GetCache(cache_id); + if (cache) { + AppCacheEntry* entry = cache->GetEntry(entry_url); + DCHECK(entry); + if (entry) + entry->add_types(AppCacheEntry::FOREIGN); + } + scoped_refptr<MarkEntryAsForeignTask> task( + new MarkEntryAsForeignTask(this, entry_url, cache_id)); + task->Schedule(); + pending_foreign_markings_.push_back(std::make_pair(entry_url, cache_id)); +} + +void AppCacheStorageImpl::MakeGroupObsolete(AppCacheGroup* group, + Delegate* delegate, + int response_code) { + DCHECK(group && delegate); + scoped_refptr<MakeGroupObsoleteTask> task( + new MakeGroupObsoleteTask(this, group, response_code)); + task->AddDelegate(GetOrCreateDelegateReference(delegate)); + task->Schedule(); +} + +AppCacheResponseReader* AppCacheStorageImpl::CreateResponseReader( + const GURL& manifest_url, int64 group_id, int64 response_id) { + return new AppCacheResponseReader(response_id, group_id, disk_cache()); +} + +AppCacheResponseWriter* AppCacheStorageImpl::CreateResponseWriter( + const GURL& manifest_url, int64 group_id) { + return new AppCacheResponseWriter(NewResponseId(), group_id, disk_cache()); +} + +void AppCacheStorageImpl::DoomResponses( + const GURL& manifest_url, const std::vector<int64>& response_ids) { + if (response_ids.empty()) + return; + + // Start deleting them from the disk cache lazily. + StartDeletingResponses(response_ids); + + // Also schedule a database task to record these ids in the + // deletable responses table. + // TODO(michaeln): There is a race here. If the browser crashes + // prior to committing these rows to the database and prior to us + // having deleted them from the disk cache, we'll never delete them. + scoped_refptr<InsertDeletableResponseIdsTask> task( + new InsertDeletableResponseIdsTask(this)); + task->response_ids_ = response_ids; + task->Schedule(); +} + +void AppCacheStorageImpl::DeleteResponses( + const GURL& manifest_url, const std::vector<int64>& response_ids) { + if (response_ids.empty()) + return; + StartDeletingResponses(response_ids); +} + +void AppCacheStorageImpl::DelayedStartDeletingUnusedResponses() { + // Only if we haven't already begun. + if (!did_start_deleting_responses_) { + scoped_refptr<GetDeletableResponseIdsTask> task( + new GetDeletableResponseIdsTask(this, last_deletable_response_rowid_)); + task->Schedule(); + } +} + +void AppCacheStorageImpl::StartDeletingResponses( + const std::vector<int64>& response_ids) { + DCHECK(!response_ids.empty()); + did_start_deleting_responses_ = true; + deletable_response_ids_.insert( + deletable_response_ids_.end(), + response_ids.begin(), response_ids.end()); + if (!is_response_deletion_scheduled_) + ScheduleDeleteOneResponse(); +} + +void AppCacheStorageImpl::ScheduleDeleteOneResponse() { + DCHECK(!is_response_deletion_scheduled_); + const base::TimeDelta kDelay = base::TimeDelta::FromMilliseconds(10); + base::MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&AppCacheStorageImpl::DeleteOneResponse, + weak_factory_.GetWeakPtr()), + kDelay); + is_response_deletion_scheduled_ = true; +} + +void AppCacheStorageImpl::DeleteOneResponse() { + DCHECK(is_response_deletion_scheduled_); + DCHECK(!deletable_response_ids_.empty()); + + if (!disk_cache()) { + DCHECK(is_disabled_); + deletable_response_ids_.clear(); + deleted_response_ids_.clear(); + is_response_deletion_scheduled_ = false; + return; + } + + // TODO(michaeln): add group_id to DoomEntry args + int64 id = deletable_response_ids_.front(); + int rv = disk_cache_->DoomEntry( + id, base::Bind(&AppCacheStorageImpl::OnDeletedOneResponse, + base::Unretained(this))); + if (rv != net::ERR_IO_PENDING) + OnDeletedOneResponse(rv); +} + +void AppCacheStorageImpl::OnDeletedOneResponse(int rv) { + is_response_deletion_scheduled_ = false; + if (is_disabled_) + return; + + int64 id = deletable_response_ids_.front(); + deletable_response_ids_.pop_front(); + if (rv != net::ERR_ABORTED) + deleted_response_ids_.push_back(id); + + const size_t kBatchSize = 50U; + if (deleted_response_ids_.size() >= kBatchSize || + deletable_response_ids_.empty()) { + scoped_refptr<DeleteDeletableResponseIdsTask> task( + new DeleteDeletableResponseIdsTask(this)); + task->response_ids_.swap(deleted_response_ids_); + task->Schedule(); + } + + if (deletable_response_ids_.empty()) { + scoped_refptr<GetDeletableResponseIdsTask> task( + new GetDeletableResponseIdsTask(this, last_deletable_response_rowid_)); + task->Schedule(); + return; + } + + ScheduleDeleteOneResponse(); +} + +AppCacheStorageImpl::CacheLoadTask* +AppCacheStorageImpl::GetPendingCacheLoadTask(int64 cache_id) { + PendingCacheLoads::iterator found = pending_cache_loads_.find(cache_id); + if (found != pending_cache_loads_.end()) + return found->second; + return NULL; +} + +AppCacheStorageImpl::GroupLoadTask* +AppCacheStorageImpl::GetPendingGroupLoadTask(const GURL& manifest_url) { + PendingGroupLoads::iterator found = pending_group_loads_.find(manifest_url); + if (found != pending_group_loads_.end()) + return found->second; + return NULL; +} + +void AppCacheStorageImpl::GetPendingForeignMarkingsForCache( + int64 cache_id, std::vector<GURL>* urls) { + PendingForeignMarkings::iterator iter = pending_foreign_markings_.begin(); + while (iter != pending_foreign_markings_.end()) { + if (iter->second == cache_id) + urls->push_back(iter->first); + ++iter; + } +} + +void AppCacheStorageImpl::ScheduleSimpleTask(const base::Closure& task) { + pending_simple_tasks_.push_back(task); + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&AppCacheStorageImpl::RunOnePendingSimpleTask, + weak_factory_.GetWeakPtr())); +} + +void AppCacheStorageImpl::RunOnePendingSimpleTask() { + DCHECK(!pending_simple_tasks_.empty()); + base::Closure task = pending_simple_tasks_.front(); + pending_simple_tasks_.pop_front(); + task.Run(); +} + +AppCacheDiskCache* AppCacheStorageImpl::disk_cache() { + DCHECK(IsInitTaskComplete()); + + if (is_disabled_) + return NULL; + + if (!disk_cache_) { + int rv = net::OK; + disk_cache_.reset(new AppCacheDiskCache); + if (is_incognito_) { + rv = disk_cache_->InitWithMemBackend( + kMaxMemDiskCacheSize, + base::Bind(&AppCacheStorageImpl::OnDiskCacheInitialized, + base::Unretained(this))); + } else { + rv = disk_cache_->InitWithDiskBackend( + cache_directory_.Append(kDiskCacheDirectoryName), + kMaxDiskCacheSize, + false, + cache_thread_.get(), + base::Bind(&AppCacheStorageImpl::OnDiskCacheInitialized, + base::Unretained(this))); + } + + if (rv != net::ERR_IO_PENDING) + OnDiskCacheInitialized(rv); + } + return disk_cache_.get(); +} + +void AppCacheStorageImpl::OnDiskCacheInitialized(int rv) { + if (rv != net::OK) { + LOG(ERROR) << "Failed to open the appcache diskcache."; + AppCacheHistograms::CountInitResult(AppCacheHistograms::DISK_CACHE_ERROR); + + // We're unable to open the disk cache, this is a fatal error that we can't + // really recover from. We handle it by temporarily disabling the appcache + // deleting the directory on disk and reinitializing the appcache system. + Disable(); + if (rv != net::ERR_ABORTED) + DeleteAndStartOver(); + } +} + +void AppCacheStorageImpl::DeleteAndStartOver() { + DCHECK(is_disabled_); + if (!is_incognito_) { + VLOG(1) << "Deleting existing appcache data and starting over."; + // We can have tasks in flight to close file handles on both the db + // and cache threads, we need to allow those tasks to cycle thru + // prior to deleting the files and calling reinit. + cache_thread_->PostTaskAndReply( + FROM_HERE, + base::Bind(&base::DoNothing), + base::Bind(&AppCacheStorageImpl::DeleteAndStartOverPart2, + weak_factory_.GetWeakPtr())); + } +} + +void AppCacheStorageImpl::DeleteAndStartOverPart2() { + db_thread_->PostTaskAndReply( + FROM_HERE, + base::Bind(base::IgnoreResult(&base::DeleteFile), + cache_directory_, true), + base::Bind(&AppCacheStorageImpl::CallScheduleReinitialize, + weak_factory_.GetWeakPtr())); +} + +void AppCacheStorageImpl::CallScheduleReinitialize() { + service_->ScheduleReinitialize(); + // note: 'this' may be deleted at this point. +} + +} // namespace content diff --git a/content/browser/appcache/appcache_storage_impl.h b/content/browser/appcache/appcache_storage_impl.h new file mode 100644 index 0000000..dadf72e --- /dev/null +++ b/content/browser/appcache/appcache_storage_impl.h @@ -0,0 +1,181 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_APPCACHE_APPCACHE_STORAGE_IMPL_H_ +#define CONTENT_BROWSER_APPCACHE_APPCACHE_STORAGE_IMPL_H_ + +#include <deque> +#include <map> +#include <set> +#include <utility> +#include <vector> + +#include "base/callback.h" +#include "base/files/file_path.h" +#include "base/memory/weak_ptr.h" +#include "base/message_loop/message_loop_proxy.h" +#include "content/browser/appcache/appcache_database.h" +#include "content/browser/appcache/appcache_disk_cache.h" +#include "content/browser/appcache/appcache_storage.h" +#include "content/common/content_export.h" + +namespace content { +class AppCacheStorageImplTest; +class ChromeAppCacheServiceTest; +} + +namespace content { + +class AppCacheStorageImpl : public AppCacheStorage { + public: + explicit AppCacheStorageImpl(AppCacheServiceImpl* service); + virtual ~AppCacheStorageImpl(); + + void Initialize(const base::FilePath& cache_directory, + base::MessageLoopProxy* db_thread, + base::MessageLoopProxy* cache_thread); + void Disable(); + bool is_disabled() const { return is_disabled_; } + + // AppCacheStorage methods, see the base class for doc comments. + virtual void GetAllInfo(Delegate* delegate) OVERRIDE; + virtual void LoadCache(int64 id, Delegate* delegate) OVERRIDE; + virtual void LoadOrCreateGroup(const GURL& manifest_url, + Delegate* delegate) OVERRIDE; + virtual void StoreGroupAndNewestCache(AppCacheGroup* group, + AppCache* newest_cache, + Delegate* delegate) OVERRIDE; + virtual void FindResponseForMainRequest(const GURL& url, + const GURL& preferred_manifest_url, + Delegate* delegate) OVERRIDE; + virtual void FindResponseForSubRequest( + AppCache* cache, const GURL& url, + AppCacheEntry* found_entry, AppCacheEntry* found_fallback_entry, + bool* found_network_namespace) OVERRIDE; + virtual void MarkEntryAsForeign(const GURL& entry_url, + int64 cache_id) OVERRIDE; + virtual void MakeGroupObsolete(AppCacheGroup* group, + Delegate* delegate, + int response_code) OVERRIDE; + virtual AppCacheResponseReader* CreateResponseReader( + const GURL& manifest_url, int64 group_id, int64 response_id) OVERRIDE; + virtual AppCacheResponseWriter* CreateResponseWriter( + const GURL& manifest_url, int64 group_id) OVERRIDE; + virtual void DoomResponses(const GURL& manifest_url, + const std::vector<int64>& response_ids) OVERRIDE; + virtual void DeleteResponses(const GURL& manifest_url, + const std::vector<int64>& response_ids) OVERRIDE; + + private: + // The AppCacheStorageImpl class methods and datamembers may only be + // accessed on the IO thread. This class manufactures seperate DatabaseTasks + // which access the DB on a seperate background thread. + class DatabaseTask; + class InitTask; + class DisableDatabaseTask; + class GetAllInfoTask; + class StoreOrLoadTask; + class CacheLoadTask; + class GroupLoadTask; + class StoreGroupAndCacheTask; + class FindMainResponseTask; + class MarkEntryAsForeignTask; + class MakeGroupObsoleteTask; + class GetDeletableResponseIdsTask; + class InsertDeletableResponseIdsTask; + class DeleteDeletableResponseIdsTask; + class UpdateGroupLastAccessTimeTask; + + typedef std::deque<DatabaseTask*> DatabaseTaskQueue; + typedef std::map<int64, CacheLoadTask*> PendingCacheLoads; + typedef std::map<GURL, GroupLoadTask*> PendingGroupLoads; + typedef std::deque<std::pair<GURL, int64> > PendingForeignMarkings; + typedef std::set<StoreGroupAndCacheTask*> PendingQuotaQueries; + + bool IsInitTaskComplete() { + return last_cache_id_ != AppCacheStorage::kUnitializedId; + } + + CacheLoadTask* GetPendingCacheLoadTask(int64 cache_id); + GroupLoadTask* GetPendingGroupLoadTask(const GURL& manifest_url); + void GetPendingForeignMarkingsForCache( + int64 cache_id, std::vector<GURL>* urls); + + void ScheduleSimpleTask(const base::Closure& task); + void RunOnePendingSimpleTask(); + + void DelayedStartDeletingUnusedResponses(); + void StartDeletingResponses(const std::vector<int64>& response_ids); + void ScheduleDeleteOneResponse(); + void DeleteOneResponse(); + + void OnDeletedOneResponse(int rv); + void OnDiskCacheInitialized(int rv); + void DeleteAndStartOver(); + void DeleteAndStartOverPart2(); + void CallScheduleReinitialize(); + + // Sometimes we can respond without having to query the database. + bool FindResponseForMainRequestInGroup( + AppCacheGroup* group, const GURL& url, Delegate* delegate); + void DeliverShortCircuitedFindMainResponse( + const GURL& url, + const AppCacheEntry& found_entry, + scoped_refptr<AppCacheGroup> group, + scoped_refptr<AppCache> newest_cache, + scoped_refptr<DelegateReference> delegate_ref); + + void CallOnMainResponseFound( + DelegateReferenceVector* delegates, + const GURL& url, const AppCacheEntry& entry, + const GURL& namespace_entry_url, const AppCacheEntry& fallback_entry, + int64 cache_id, int64 group_id, const GURL& manifest_url); + + CONTENT_EXPORT AppCacheDiskCache* disk_cache(); + + // The directory in which we place files in the file system. + base::FilePath cache_directory_; + bool is_incognito_; + + // This class operates primarily on the IO thread, but schedules + // its DatabaseTasks on the db thread. Separately, the disk_cache uses + // the cache_thread. + scoped_refptr<base::MessageLoopProxy> db_thread_; + scoped_refptr<base::MessageLoopProxy> cache_thread_; + + // Structures to keep track of DatabaseTasks that are in-flight. + DatabaseTaskQueue scheduled_database_tasks_; + PendingCacheLoads pending_cache_loads_; + PendingGroupLoads pending_group_loads_; + PendingForeignMarkings pending_foreign_markings_; + PendingQuotaQueries pending_quota_queries_; + + // Structures to keep track of lazy response deletion. + std::deque<int64> deletable_response_ids_; + std::vector<int64> deleted_response_ids_; + bool is_response_deletion_scheduled_; + bool did_start_deleting_responses_; + int64 last_deletable_response_rowid_; + + // Created on the IO thread, but only used on the DB thread. + AppCacheDatabase* database_; + + // Set if we discover a fatal error like a corrupt SQL database or + // disk cache and cannot continue. + bool is_disabled_; + + scoped_ptr<AppCacheDiskCache> disk_cache_; + + // Used to short-circuit certain operations without having to schedule + // any tasks on the background database thread. + std::deque<base::Closure> pending_simple_tasks_; + base::WeakPtrFactory<AppCacheStorageImpl> weak_factory_; + + friend class content::AppCacheStorageImplTest; + friend class content::ChromeAppCacheServiceTest; +}; + +} // namespace content + +#endif // CONTENT_BROWSER_APPCACHE_APPCACHE_STORAGE_IMPL_H_ diff --git a/content/browser/appcache/appcache_storage_impl_unittest.cc b/content/browser/appcache/appcache_storage_impl_unittest.cc index 4c467a1..a8489af 100644 --- a/content/browser/appcache/appcache_storage_impl_unittest.cc +++ b/content/browser/appcache/appcache_storage_impl_unittest.cc @@ -13,7 +13,16 @@ #include "base/message_loop/message_loop.h" #include "base/synchronization/waitable_event.h" #include "base/threading/thread.h" +#include "content/browser/appcache/appcache.h" +#include "content/browser/appcache/appcache_backend_impl.h" +#include "content/browser/appcache/appcache_database.h" +#include "content/browser/appcache/appcache_entry.h" +#include "content/browser/appcache/appcache_group.h" +#include "content/browser/appcache/appcache_host.h" #include "content/browser/appcache/appcache_interceptor.h" +#include "content/browser/appcache/appcache_request_handler.h" +#include "content/browser/appcache/appcache_service_impl.h" +#include "content/browser/appcache/appcache_storage_impl.h" #include "net/base/net_errors.h" #include "net/base/request_priority.h" #include "net/http/http_response_headers.h" @@ -23,40 +32,8 @@ #include "net/url_request/url_request_test_util.h" #include "sql/test/test_helpers.h" #include "testing/gtest/include/gtest/gtest.h" -#include "webkit/browser/appcache/appcache.h" -#include "webkit/browser/appcache/appcache_backend_impl.h" -#include "webkit/browser/appcache/appcache_database.h" -#include "webkit/browser/appcache/appcache_entry.h" -#include "webkit/browser/appcache/appcache_group.h" -#include "webkit/browser/appcache/appcache_host.h" -#include "webkit/browser/appcache/appcache_request_handler.h" -#include "webkit/browser/appcache/appcache_service_impl.h" -#include "webkit/browser/appcache/appcache_storage_impl.h" #include "webkit/browser/quota/quota_manager.h" -using appcache::APPCACHE_FALLBACK_NAMESPACE; -using appcache::APPCACHE_NETWORK_NAMESPACE; -using appcache::AppCacheBackendImpl; -using appcache::AppCacheDatabase; -using appcache::AppCacheEntry; -using appcache::AppCacheFrontend; -using appcache::AppCacheHost; -using appcache::AppCacheInfo; -using appcache::AppCacheGroup; -using appcache::AppCacheServiceImpl; -using appcache::AppCacheStorage; -using appcache::AppCacheStorageImpl; -using appcache::AppCacheStorageReference; -using appcache::AppCache; -using appcache::AppCacheErrorDetails; -using appcache::AppCacheEventID; -using appcache::kAppCacheNoCacheId; -using appcache::kAppCacheNoResponseId; -using appcache::APPCACHE_INTERCEPT_NAMESPACE; -using appcache::AppCacheLogLevel; -using appcache::Namespace; -using appcache::AppCacheStatus; - namespace content { namespace { @@ -1054,12 +1031,12 @@ class AppCacheStorageImplTest : public testing::Test { cache_->AddEntry(kEntryUrl, AppCacheEntry(AppCacheEntry::FALLBACK, 1)); cache_->AddEntry(kEntryUrl2, AppCacheEntry(AppCacheEntry::FALLBACK, 2)); cache_->fallback_namespaces_.push_back( - Namespace(APPCACHE_FALLBACK_NAMESPACE, + AppCacheNamespace(APPCACHE_FALLBACK_NAMESPACE, kFallbackNamespace2, kEntryUrl2, false)); cache_->fallback_namespaces_.push_back( - Namespace(APPCACHE_FALLBACK_NAMESPACE, + AppCacheNamespace(APPCACHE_FALLBACK_NAMESPACE, kFallbackNamespace, kEntryUrl, false)); @@ -1132,10 +1109,10 @@ class AppCacheStorageImplTest : public testing::Test { cache_->AddEntry(kEntryUrl, AppCacheEntry(AppCacheEntry::INTERCEPT, 1)); cache_->AddEntry(kEntryUrl2, AppCacheEntry(AppCacheEntry::INTERCEPT, 2)); cache_->intercept_namespaces_.push_back( - Namespace(APPCACHE_INTERCEPT_NAMESPACE, kInterceptNamespace2, + AppCacheNamespace(APPCACHE_INTERCEPT_NAMESPACE, kInterceptNamespace2, kEntryUrl2, false)); cache_->intercept_namespaces_.push_back( - Namespace(APPCACHE_INTERCEPT_NAMESPACE, kInterceptNamespace, + AppCacheNamespace(APPCACHE_INTERCEPT_NAMESPACE, kInterceptNamespace, kEntryUrl, false)); AppCacheDatabase::CacheRecord cache_record; std::vector<AppCacheDatabase::EntryRecord> entries; @@ -1202,8 +1179,8 @@ class AppCacheStorageImplTest : public testing::Test { MakeCacheAndGroup(kManifestUrl, 2, 1, true); cache_->AddEntry(kEntryUrl, AppCacheEntry(AppCacheEntry::INTERCEPT, 1)); cache_->intercept_namespaces_.push_back( - Namespace(APPCACHE_INTERCEPT_NAMESPACE, kInterceptPatternNamespace, - kEntryUrl, true)); + AppCacheNamespace(APPCACHE_INTERCEPT_NAMESPACE, + kInterceptPatternNamespace, kEntryUrl, true)); AppCacheDatabase::CacheRecord cache_record; std::vector<AppCacheDatabase::EntryRecord> entries; std::vector<AppCacheDatabase::NamespaceRecord> intercepts; @@ -1289,8 +1266,8 @@ class AppCacheStorageImplTest : public testing::Test { MakeCacheAndGroup(kManifestUrl, 2, 1, true); cache_->AddEntry(kEntryUrl, AppCacheEntry(AppCacheEntry::FALLBACK, 1)); cache_->fallback_namespaces_.push_back( - Namespace(APPCACHE_FALLBACK_NAMESPACE, kFallbackPatternNamespace, - kEntryUrl, true)); + AppCacheNamespace(APPCACHE_FALLBACK_NAMESPACE, + kFallbackPatternNamespace, kEntryUrl, true)); AppCacheDatabase::CacheRecord cache_record; std::vector<AppCacheDatabase::EntryRecord> entries; std::vector<AppCacheDatabase::NamespaceRecord> intercepts; @@ -1420,7 +1397,7 @@ class AppCacheStorageImplTest : public testing::Test { fallback_namespace_record.origin = manifest_url.GetOrigin(); EXPECT_TRUE(database()->InsertNamespace(&fallback_namespace_record)); cache_->fallback_namespaces_.push_back( - Namespace(APPCACHE_FALLBACK_NAMESPACE, + AppCacheNamespace(APPCACHE_FALLBACK_NAMESPACE, kFallbackNamespace, kEntryUrl2, false)); @@ -1535,16 +1512,16 @@ class AppCacheStorageImplTest : public testing::Test { AppCacheEntry(AppCacheEntry::EXPLICIT | AppCacheEntry::FOREIGN, 1)); cache_->AddEntry(kEntryUrl2, AppCacheEntry(AppCacheEntry::FALLBACK, 2)); cache_->fallback_namespaces_.push_back( - Namespace(APPCACHE_FALLBACK_NAMESPACE, + AppCacheNamespace(APPCACHE_FALLBACK_NAMESPACE, kFallbackNamespace, kEntryUrl2, false)); cache_->online_whitelist_namespaces_.push_back( - Namespace(APPCACHE_NETWORK_NAMESPACE, kOnlineNamespace, + AppCacheNamespace(APPCACHE_NETWORK_NAMESPACE, kOnlineNamespace, GURL(), false)); cache_->online_whitelist_namespaces_.push_back( - Namespace(APPCACHE_NETWORK_NAMESPACE, kOnlineNamespaceWithinFallback, - GURL(), false)); + AppCacheNamespace(APPCACHE_NETWORK_NAMESPACE, + kOnlineNamespaceWithinFallback, GURL(), false)); AppCacheDatabase::EntryRecord entry_record; entry_record.cache_id = 1; diff --git a/content/browser/appcache/appcache_storage_unittest.cc b/content/browser/appcache/appcache_storage_unittest.cc index fa69696..827fce6 100644 --- a/content/browser/appcache/appcache_storage_unittest.cc +++ b/content/browser/appcache/appcache_storage_unittest.cc @@ -3,19 +3,13 @@ // found in the LICENSE file. #include "base/message_loop/message_loop.h" +#include "content/browser/appcache/appcache.h" +#include "content/browser/appcache/appcache_group.h" +#include "content/browser/appcache/appcache_response.h" +#include "content/browser/appcache/appcache_storage.h" #include "content/browser/appcache/mock_appcache_service.h" #include "content/browser/quota/mock_quota_manager_proxy.h" #include "testing/gtest/include/gtest/gtest.h" -#include "webkit/browser/appcache/appcache.h" -#include "webkit/browser/appcache/appcache_group.h" -#include "webkit/browser/appcache/appcache_response.h" -#include "webkit/browser/appcache/appcache_storage.h" - -using appcache::AppCache; -using appcache::AppCacheGroup; -using appcache::AppCacheResponseInfo; -using appcache::AppCacheStorage; -using appcache::kUnkownResponseDataSize; namespace content { diff --git a/content/browser/appcache/appcache_unittest.cc b/content/browser/appcache/appcache_unittest.cc index 32c4e89..a82ab3d 100644 --- a/content/browser/appcache/appcache_unittest.cc +++ b/content/browser/appcache/appcache_unittest.cc @@ -2,30 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "content/browser/appcache/appcache.h" +#include "content/browser/appcache/appcache_host.h" #include "content/browser/appcache/mock_appcache_service.h" #include "testing/gtest/include/gtest/gtest.h" -#include "webkit/browser/appcache/appcache.h" -#include "webkit/browser/appcache/appcache_host.h" - -using appcache::AppCache; -using appcache::AppCacheDatabase; -using appcache::AppCacheEntry; -using appcache::AppCacheFrontend; -using appcache::AppCacheGroup; -using appcache::AppCacheHost; -using appcache::AppCacheInfo; -using appcache::AppCacheErrorDetails; -using appcache::AppCacheEventID; -using appcache::APPCACHE_FALLBACK_NAMESPACE; -using appcache::APPCACHE_INTERCEPT_NAMESPACE; -using appcache::AppCacheLogLevel; -using appcache::Manifest; -using appcache::Namespace; -using appcache::NamespaceVector; -using appcache::APPCACHE_NETWORK_NAMESPACE; -using appcache::PARSE_MANIFEST_ALLOWING_INTERCEPTS; -using appcache::PARSE_MANIFEST_PER_STANDARD; -using appcache::AppCacheStatus; namespace content { @@ -132,23 +112,26 @@ TEST(AppCacheTest, InitializeWithManifest) { manifest.explicit_urls.insert("http://one.com"); manifest.explicit_urls.insert("http://two.com"); manifest.fallback_namespaces.push_back( - Namespace(APPCACHE_FALLBACK_NAMESPACE, GURL("http://fb1.com"), + AppCacheNamespace(APPCACHE_FALLBACK_NAMESPACE, GURL("http://fb1.com"), GURL("http://fbone.com"), true)); manifest.online_whitelist_namespaces.push_back( - Namespace(APPCACHE_NETWORK_NAMESPACE, GURL("http://w1.com"), GURL(), false)); + AppCacheNamespace(APPCACHE_NETWORK_NAMESPACE, GURL("http://w1.com"), + GURL(), false)); manifest.online_whitelist_namespaces.push_back( - Namespace(APPCACHE_NETWORK_NAMESPACE, GURL("http://w2.com"), GURL(), false)); + AppCacheNamespace(APPCACHE_NETWORK_NAMESPACE, GURL("http://w2.com"), + GURL(), false)); manifest.online_whitelist_all = true; cache->InitializeWithManifest(&manifest); - const std::vector<Namespace>& fallbacks = + const std::vector<AppCacheNamespace>& fallbacks = cache->fallback_namespaces_; size_t expected = 1; EXPECT_EQ(expected, fallbacks.size()); EXPECT_EQ(GURL("http://fb1.com"), fallbacks[0].namespace_url); EXPECT_EQ(GURL("http://fbone.com"), fallbacks[0].target_url); EXPECT_TRUE(fallbacks[0].is_pattern); - const NamespaceVector& whitelist = cache->online_whitelist_namespaces_; + const AppCacheNamespaceVector& whitelist = + cache->online_whitelist_namespaces_; expected = 2; EXPECT_EQ(expected, whitelist.size()); EXPECT_EQ(GURL("http://w1.com"), whitelist[0].namespace_url); @@ -193,24 +176,23 @@ TEST(AppCacheTest, FindResponseForRequest) { Manifest manifest; manifest.online_whitelist_namespaces.push_back( - Namespace(APPCACHE_NETWORK_NAMESPACE, kOnlineNamespaceUrl, - GURL(), false)); + AppCacheNamespace(APPCACHE_NETWORK_NAMESPACE, kOnlineNamespaceUrl, + GURL(), false)); manifest.online_whitelist_namespaces.push_back( - Namespace(APPCACHE_NETWORK_NAMESPACE, - kOnlineNamespaceWithinOtherNamespaces, - GURL(), false)); + AppCacheNamespace(APPCACHE_NETWORK_NAMESPACE, + kOnlineNamespaceWithinOtherNamespaces, GURL(), false)); manifest.fallback_namespaces.push_back( - Namespace(APPCACHE_FALLBACK_NAMESPACE, kFallbackNamespaceUrl1, - kFallbackEntryUrl1, false)); + AppCacheNamespace(APPCACHE_FALLBACK_NAMESPACE, kFallbackNamespaceUrl1, + kFallbackEntryUrl1, false)); manifest.fallback_namespaces.push_back( - Namespace(APPCACHE_FALLBACK_NAMESPACE, kFallbackNamespaceUrl2, - kFallbackEntryUrl2, false)); + AppCacheNamespace(APPCACHE_FALLBACK_NAMESPACE, kFallbackNamespaceUrl2, + kFallbackEntryUrl2, false)); manifest.intercept_namespaces.push_back( - Namespace(APPCACHE_INTERCEPT_NAMESPACE, kInterceptNamespace, - kInterceptNamespaceEntry, false)); + AppCacheNamespace(APPCACHE_INTERCEPT_NAMESPACE, kInterceptNamespace, + kInterceptNamespaceEntry, false)); manifest.intercept_namespaces.push_back( - Namespace(APPCACHE_INTERCEPT_NAMESPACE, kInterceptNamespaceWithinFallback, - kInterceptNamespaceEntry, false)); + AppCacheNamespace(APPCACHE_INTERCEPT_NAMESPACE, + kInterceptNamespaceWithinFallback, kInterceptNamespaceEntry, false)); // Create a cache with some namespaces and entries. scoped_refptr<AppCache> cache(new AppCache(service.storage(), 1234)); @@ -385,8 +367,8 @@ TEST(AppCacheTest, FindInterceptPatternResponseForRequest) { const int64 kInterceptResponseId = 1; Manifest manifest; manifest.intercept_namespaces.push_back( - Namespace(APPCACHE_INTERCEPT_NAMESPACE, kInterceptPatternNamespace, - kInterceptNamespaceEntry, true)); + AppCacheNamespace(APPCACHE_INTERCEPT_NAMESPACE, + kInterceptPatternNamespace, kInterceptNamespaceEntry, true)); scoped_refptr<AppCache> cache(new AppCache(service.storage(), 1234)); cache->InitializeWithManifest(&manifest); cache->AddEntry( @@ -456,7 +438,7 @@ TEST(AppCacheTest, FindFallbackPatternResponseForRequest) { const int64 kFallbackResponseId = 1; Manifest manifest; manifest.fallback_namespaces.push_back( - Namespace(APPCACHE_FALLBACK_NAMESPACE, kFallbackPatternNamespace, + AppCacheNamespace(APPCACHE_FALLBACK_NAMESPACE, kFallbackPatternNamespace, kFallbackNamespaceEntry, true)); scoped_refptr<AppCache> cache(new AppCache(service.storage(), 1234)); cache->InitializeWithManifest(&manifest); @@ -526,7 +508,7 @@ TEST(AppCacheTest, FindNetworkNamespacePatternResponseForRequest) { kNetworkNamespaceBase.Resolve("*.hit*")); Manifest manifest; manifest.online_whitelist_namespaces.push_back( - Namespace(APPCACHE_NETWORK_NAMESPACE, kNetworkPatternNamespace, + AppCacheNamespace(APPCACHE_NETWORK_NAMESPACE, kNetworkPatternNamespace, GURL(), true)); manifest.online_whitelist_all = false; scoped_refptr<AppCache> cache(new AppCache(service.storage(), 1234)); @@ -644,7 +626,7 @@ TEST(AppCacheTest, ToFromDatabaseRecords) { } TEST(AppCacheTest, IsNamespaceMatch) { - Namespace prefix; + AppCacheNamespace prefix; prefix.namespace_url = GURL("http://foo.com/prefix"); prefix.is_pattern = false; EXPECT_TRUE(prefix.IsMatch( @@ -652,7 +634,7 @@ TEST(AppCacheTest, IsNamespaceMatch) { EXPECT_FALSE(prefix.IsMatch( GURL("http://foo.com/nope"))); - Namespace bar_no_star; + AppCacheNamespace bar_no_star; bar_no_star.namespace_url = GURL("http://foo.com/bar"); bar_no_star.is_pattern = true; EXPECT_TRUE(bar_no_star.IsMatch( @@ -660,7 +642,7 @@ TEST(AppCacheTest, IsNamespaceMatch) { EXPECT_FALSE(bar_no_star.IsMatch( GURL("http://foo.com/bar/nope"))); - Namespace bar_star; + AppCacheNamespace bar_star; bar_star.namespace_url = GURL("http://foo.com/bar/*"); bar_star.is_pattern = true; EXPECT_TRUE(bar_star.IsMatch( @@ -670,7 +652,7 @@ TEST(AppCacheTest, IsNamespaceMatch) { EXPECT_FALSE(bar_star.IsMatch( GURL("http://foo.com/not_bar/should_not_match"))); - Namespace star_bar_star; + AppCacheNamespace star_bar_star; star_bar_star.namespace_url = GURL("http://foo.com/*/bar/*"); star_bar_star.is_pattern = true; EXPECT_TRUE(star_bar_star.IsMatch( @@ -680,7 +662,7 @@ TEST(AppCacheTest, IsNamespaceMatch) { EXPECT_FALSE(star_bar_star.IsMatch( GURL("http://foo.com/any/not_bar/no_match"))); - Namespace query_star_edit; + AppCacheNamespace query_star_edit; query_star_edit.namespace_url = GURL("http://foo.com/query?id=*&verb=edit*"); query_star_edit.is_pattern = true; EXPECT_TRUE(query_star_edit.IsMatch( @@ -692,7 +674,7 @@ TEST(AppCacheTest, IsNamespaceMatch) { EXPECT_TRUE(query_star_edit.IsMatch( GURL("http://foo.com/query?id=123&verb=print&verb=edit"))); - Namespace star_greediness; + AppCacheNamespace star_greediness; star_greediness.namespace_url = GURL("http://foo.com/*/b"); star_greediness.is_pattern = true; EXPECT_TRUE(star_greediness.IsMatch( diff --git a/content/browser/appcache/appcache_update_job.cc b/content/browser/appcache/appcache_update_job.cc new file mode 100644 index 0000000..347d4b5c --- /dev/null +++ b/content/browser/appcache/appcache_update_job.cc @@ -0,0 +1,1611 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/appcache/appcache_update_job.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/compiler_specific.h" +#include "base/message_loop/message_loop.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "content/browser/appcache/appcache_group.h" +#include "content/browser/appcache/appcache_histograms.h" +#include "net/base/io_buffer.h" +#include "net/base/load_flags.h" +#include "net/base/net_errors.h" +#include "net/base/request_priority.h" +#include "net/http/http_request_headers.h" +#include "net/http/http_response_headers.h" +#include "net/url_request/url_request_context.h" + +namespace content { + +static const int kBufferSize = 32768; +static const size_t kMaxConcurrentUrlFetches = 2; +static const int kMax503Retries = 3; + +static std::string FormatUrlErrorMessage( + const char* format, const GURL& url, + AppCacheUpdateJob::ResultType error, + int response_code) { + // Show the net response code if we have one. + int code = response_code; + if (error != AppCacheUpdateJob::SERVER_ERROR) + code = static_cast<int>(error); + return base::StringPrintf(format, code, url.spec().c_str()); +} + +// Helper class for collecting hosts per frontend when sending notifications +// so that only one notification is sent for all hosts using the same frontend. +class HostNotifier { + public: + typedef std::vector<int> HostIds; + typedef std::map<AppCacheFrontend*, HostIds> NotifyHostMap; + + // Caller is responsible for ensuring there will be no duplicate hosts. + void AddHost(AppCacheHost* host) { + std::pair<NotifyHostMap::iterator , bool> ret = hosts_to_notify.insert( + NotifyHostMap::value_type(host->frontend(), HostIds())); + ret.first->second.push_back(host->host_id()); + } + + void AddHosts(const std::set<AppCacheHost*>& hosts) { + for (std::set<AppCacheHost*>::const_iterator it = hosts.begin(); + it != hosts.end(); ++it) { + AddHost(*it); + } + } + + void SendNotifications(AppCacheEventID event_id) { + for (NotifyHostMap::iterator it = hosts_to_notify.begin(); + it != hosts_to_notify.end(); ++it) { + AppCacheFrontend* frontend = it->first; + frontend->OnEventRaised(it->second, event_id); + } + } + + void SendProgressNotifications( + const GURL& url, int num_total, int num_complete) { + for (NotifyHostMap::iterator it = hosts_to_notify.begin(); + it != hosts_to_notify.end(); ++it) { + AppCacheFrontend* frontend = it->first; + frontend->OnProgressEventRaised(it->second, url, + num_total, num_complete); + } + } + + void SendErrorNotifications(const AppCacheErrorDetails& details) { + DCHECK(!details.message.empty()); + for (NotifyHostMap::iterator it = hosts_to_notify.begin(); + it != hosts_to_notify.end(); ++it) { + AppCacheFrontend* frontend = it->first; + frontend->OnErrorEventRaised(it->second, details); + } + } + + void SendLogMessage(const std::string& message) { + for (NotifyHostMap::iterator it = hosts_to_notify.begin(); + it != hosts_to_notify.end(); ++it) { + AppCacheFrontend* frontend = it->first; + for (HostIds::iterator id = it->second.begin(); + id != it->second.end(); ++id) { + frontend->OnLogMessage(*id, APPCACHE_LOG_WARNING, message); + } + } + } + + private: + NotifyHostMap hosts_to_notify; +}; + +AppCacheUpdateJob::UrlToFetch::UrlToFetch(const GURL& url, + bool checked, + AppCacheResponseInfo* info) + : url(url), + storage_checked(checked), + existing_response_info(info) { +} + +AppCacheUpdateJob::UrlToFetch::~UrlToFetch() { +} + +// Helper class to fetch resources. Depending on the fetch type, +// can either fetch to an in-memory string or write the response +// data out to the disk cache. +AppCacheUpdateJob::URLFetcher::URLFetcher(const GURL& url, + FetchType fetch_type, + AppCacheUpdateJob* job) + : url_(url), + job_(job), + fetch_type_(fetch_type), + retry_503_attempts_(0), + buffer_(new net::IOBuffer(kBufferSize)), + request_(job->service_->request_context() + ->CreateRequest(url, net::DEFAULT_PRIORITY, this, NULL)), + result_(UPDATE_OK), + redirect_response_code_(-1) {} + +AppCacheUpdateJob::URLFetcher::~URLFetcher() { +} + +void AppCacheUpdateJob::URLFetcher::Start() { + request_->set_first_party_for_cookies(job_->manifest_url_); + request_->SetLoadFlags(request_->load_flags() | + net::LOAD_DISABLE_INTERCEPT); + if (existing_response_headers_.get()) + AddConditionalHeaders(existing_response_headers_.get()); + request_->Start(); +} + +void AppCacheUpdateJob::URLFetcher::OnReceivedRedirect( + net::URLRequest* request, const GURL& new_url, bool* defer_redirect) { + DCHECK(request_ == request); + // Redirect is not allowed by the update process. + job_->MadeProgress(); + redirect_response_code_ = request->GetResponseCode(); + request->Cancel(); + result_ = REDIRECT_ERROR; + OnResponseCompleted(); +} + +void AppCacheUpdateJob::URLFetcher::OnResponseStarted( + net::URLRequest *request) { + DCHECK(request == request_); + int response_code = -1; + if (request->status().is_success()) { + response_code = request->GetResponseCode(); + job_->MadeProgress(); + } + if ((response_code / 100) == 2) { + + // See http://code.google.com/p/chromium/issues/detail?id=69594 + // We willfully violate the HTML5 spec at this point in order + // to support the appcaching of cross-origin HTTPS resources. + // We've opted for a milder constraint and allow caching unless + // the resource has a "no-store" header. A spec change has been + // requested on the whatwg list. + // TODO(michaeln): Consider doing this for cross-origin HTTP resources too. + if (url_.SchemeIsSecure() && + url_.GetOrigin() != job_->manifest_url_.GetOrigin()) { + if (request->response_headers()-> + HasHeaderValue("cache-control", "no-store")) { + DCHECK_EQ(-1, redirect_response_code_); + request->Cancel(); + result_ = SERVER_ERROR; // Not the best match? + OnResponseCompleted(); + return; + } + } + + // Write response info to storage for URL fetches. Wait for async write + // completion before reading any response data. + if (fetch_type_ == URL_FETCH || fetch_type_ == MASTER_ENTRY_FETCH) { + response_writer_.reset(job_->CreateResponseWriter()); + scoped_refptr<HttpResponseInfoIOBuffer> io_buffer( + new HttpResponseInfoIOBuffer( + new net::HttpResponseInfo(request->response_info()))); + response_writer_->WriteInfo( + io_buffer.get(), + base::Bind(&URLFetcher::OnWriteComplete, base::Unretained(this))); + } else { + ReadResponseData(); + } + } else { + if (response_code > 0) + result_ = SERVER_ERROR; + else + result_ = NETWORK_ERROR; + OnResponseCompleted(); + } +} + +void AppCacheUpdateJob::URLFetcher::OnReadCompleted( + net::URLRequest* request, int bytes_read) { + DCHECK(request_ == request); + bool data_consumed = true; + if (request->status().is_success() && bytes_read > 0) { + job_->MadeProgress(); + data_consumed = ConsumeResponseData(bytes_read); + if (data_consumed) { + bytes_read = 0; + while (request->Read(buffer_.get(), kBufferSize, &bytes_read)) { + if (bytes_read > 0) { + data_consumed = ConsumeResponseData(bytes_read); + if (!data_consumed) + break; // wait for async data processing, then read more + } else { + break; + } + } + } + } + if (data_consumed && !request->status().is_io_pending()) { + DCHECK_EQ(UPDATE_OK, result_); + OnResponseCompleted(); + } +} + +void AppCacheUpdateJob::URLFetcher::AddConditionalHeaders( + const net::HttpResponseHeaders* headers) { + DCHECK(request_.get() && headers); + net::HttpRequestHeaders extra_headers; + + // Add If-Modified-Since header if response info has Last-Modified header. + const std::string last_modified = "Last-Modified"; + std::string last_modified_value; + headers->EnumerateHeader(NULL, last_modified, &last_modified_value); + if (!last_modified_value.empty()) { + extra_headers.SetHeader(net::HttpRequestHeaders::kIfModifiedSince, + last_modified_value); + } + + // Add If-None-Match header if response info has ETag header. + const std::string etag = "ETag"; + std::string etag_value; + headers->EnumerateHeader(NULL, etag, &etag_value); + if (!etag_value.empty()) { + extra_headers.SetHeader(net::HttpRequestHeaders::kIfNoneMatch, + etag_value); + } + if (!extra_headers.IsEmpty()) + request_->SetExtraRequestHeaders(extra_headers); +} + +void AppCacheUpdateJob::URLFetcher::OnWriteComplete(int result) { + if (result < 0) { + request_->Cancel(); + result_ = DISKCACHE_ERROR; + OnResponseCompleted(); + return; + } + ReadResponseData(); +} + +void AppCacheUpdateJob::URLFetcher::ReadResponseData() { + InternalUpdateState state = job_->internal_state_; + if (state == CACHE_FAILURE || state == CANCELLED || state == COMPLETED) + return; + int bytes_read = 0; + request_->Read(buffer_.get(), kBufferSize, &bytes_read); + OnReadCompleted(request_.get(), bytes_read); +} + +// Returns false if response data is processed asynchronously, in which +// case ReadResponseData will be invoked when it is safe to continue +// reading more response data from the request. +bool AppCacheUpdateJob::URLFetcher::ConsumeResponseData(int bytes_read) { + DCHECK_GT(bytes_read, 0); + switch (fetch_type_) { + case MANIFEST_FETCH: + case MANIFEST_REFETCH: + manifest_data_.append(buffer_->data(), bytes_read); + break; + case URL_FETCH: + case MASTER_ENTRY_FETCH: + DCHECK(response_writer_.get()); + response_writer_->WriteData( + buffer_.get(), + bytes_read, + base::Bind(&URLFetcher::OnWriteComplete, base::Unretained(this))); + return false; // wait for async write completion to continue reading + default: + NOTREACHED(); + } + return true; +} + +void AppCacheUpdateJob::URLFetcher::OnResponseCompleted() { + if (request_->status().is_success()) + job_->MadeProgress(); + + // Retry for 503s where retry-after is 0. + if (request_->status().is_success() && + request_->GetResponseCode() == 503 && + MaybeRetryRequest()) { + return; + } + + switch (fetch_type_) { + case MANIFEST_FETCH: + job_->HandleManifestFetchCompleted(this); + break; + case URL_FETCH: + job_->HandleUrlFetchCompleted(this); + break; + case MASTER_ENTRY_FETCH: + job_->HandleMasterEntryFetchCompleted(this); + break; + case MANIFEST_REFETCH: + job_->HandleManifestRefetchCompleted(this); + break; + default: + NOTREACHED(); + } + + delete this; +} + +bool AppCacheUpdateJob::URLFetcher::MaybeRetryRequest() { + if (retry_503_attempts_ >= kMax503Retries || + !request_->response_headers()->HasHeaderValue("retry-after", "0")) { + return false; + } + ++retry_503_attempts_; + result_ = UPDATE_OK; + request_ = job_->service_->request_context()->CreateRequest( + url_, net::DEFAULT_PRIORITY, this, NULL); + Start(); + return true; +} + +AppCacheUpdateJob::AppCacheUpdateJob(AppCacheServiceImpl* service, + AppCacheGroup* group) + : service_(service), + manifest_url_(group->manifest_url()), + group_(group), + update_type_(UNKNOWN_TYPE), + internal_state_(FETCH_MANIFEST), + master_entries_completed_(0), + url_fetches_completed_(0), + manifest_fetcher_(NULL), + manifest_has_valid_mime_type_(false), + stored_state_(UNSTORED), + storage_(service->storage()) { + service_->AddObserver(this); +} + +AppCacheUpdateJob::~AppCacheUpdateJob() { + if (service_) + service_->RemoveObserver(this); + if (internal_state_ != COMPLETED) + Cancel(); + + DCHECK(!manifest_fetcher_); + DCHECK(pending_url_fetches_.empty()); + DCHECK(!inprogress_cache_.get()); + DCHECK(pending_master_entries_.empty()); + DCHECK(master_entry_fetches_.empty()); + + if (group_) + group_->SetUpdateAppCacheStatus(AppCacheGroup::IDLE); +} + +void AppCacheUpdateJob::StartUpdate(AppCacheHost* host, + const GURL& new_master_resource) { + DCHECK(group_->update_job() == this); + DCHECK(!group_->is_obsolete()); + + bool is_new_pending_master_entry = false; + if (!new_master_resource.is_empty()) { + DCHECK(new_master_resource == host->pending_master_entry_url()); + DCHECK(!new_master_resource.has_ref()); + DCHECK(new_master_resource.GetOrigin() == manifest_url_.GetOrigin()); + + // Cannot add more to this update if already terminating. + if (IsTerminating()) { + group_->QueueUpdate(host, new_master_resource); + return; + } + + std::pair<PendingMasters::iterator, bool> ret = + pending_master_entries_.insert( + PendingMasters::value_type(new_master_resource, PendingHosts())); + is_new_pending_master_entry = ret.second; + ret.first->second.push_back(host); + host->AddObserver(this); + } + + // Notify host (if any) if already checking or downloading. + AppCacheGroup::UpdateAppCacheStatus update_status = group_->update_status(); + if (update_status == AppCacheGroup::CHECKING || + update_status == AppCacheGroup::DOWNLOADING) { + if (host) { + NotifySingleHost(host, APPCACHE_CHECKING_EVENT); + if (update_status == AppCacheGroup::DOWNLOADING) + NotifySingleHost(host, APPCACHE_DOWNLOADING_EVENT); + + // Add to fetch list or an existing entry if already fetched. + if (!new_master_resource.is_empty()) { + AddMasterEntryToFetchList(host, new_master_resource, + is_new_pending_master_entry); + } + } + return; + } + + // Begin update process for the group. + MadeProgress(); + group_->SetUpdateAppCacheStatus(AppCacheGroup::CHECKING); + if (group_->HasCache()) { + update_type_ = UPGRADE_ATTEMPT; + NotifyAllAssociatedHosts(APPCACHE_CHECKING_EVENT); + } else { + update_type_ = CACHE_ATTEMPT; + DCHECK(host); + NotifySingleHost(host, APPCACHE_CHECKING_EVENT); + } + + if (!new_master_resource.is_empty()) { + AddMasterEntryToFetchList(host, new_master_resource, + is_new_pending_master_entry); + } + + FetchManifest(true); +} + +AppCacheResponseWriter* AppCacheUpdateJob::CreateResponseWriter() { + AppCacheResponseWriter* writer = + storage_->CreateResponseWriter(manifest_url_, + group_->group_id()); + stored_response_ids_.push_back(writer->response_id()); + return writer; +} + +void AppCacheUpdateJob::HandleCacheFailure( + const AppCacheErrorDetails& error_details, + ResultType result, + const GURL& failed_resource_url) { + // 6.9.4 cache failure steps 2-8. + DCHECK(internal_state_ != CACHE_FAILURE); + DCHECK(!error_details.message.empty()); + DCHECK(result != UPDATE_OK); + internal_state_ = CACHE_FAILURE; + LogHistogramStats(result, failed_resource_url); + CancelAllUrlFetches(); + CancelAllMasterEntryFetches(error_details); + NotifyAllError(error_details); + DiscardInprogressCache(); + internal_state_ = COMPLETED; + DeleteSoon(); // To unwind the stack prior to deletion. +} + +void AppCacheUpdateJob::FetchManifest(bool is_first_fetch) { + DCHECK(!manifest_fetcher_); + manifest_fetcher_ = new URLFetcher( + manifest_url_, + is_first_fetch ? URLFetcher::MANIFEST_FETCH : + URLFetcher::MANIFEST_REFETCH, + this); + + // Add any necessary Http headers before sending fetch request. + if (is_first_fetch) { + AppCacheEntry* entry = (update_type_ == UPGRADE_ATTEMPT) ? + group_->newest_complete_cache()->GetEntry(manifest_url_) : NULL; + if (entry) { + // Asynchronously load response info for manifest from newest cache. + storage_->LoadResponseInfo(manifest_url_, group_->group_id(), + entry->response_id(), this); + } else { + manifest_fetcher_->Start(); + } + } else { + DCHECK(internal_state_ == REFETCH_MANIFEST); + DCHECK(manifest_response_info_.get()); + manifest_fetcher_->set_existing_response_headers( + manifest_response_info_->headers.get()); + manifest_fetcher_->Start(); + } +} + + +void AppCacheUpdateJob::HandleManifestFetchCompleted( + URLFetcher* fetcher) { + DCHECK_EQ(internal_state_, FETCH_MANIFEST); + DCHECK_EQ(manifest_fetcher_, fetcher); + manifest_fetcher_ = NULL; + + net::URLRequest* request = fetcher->request(); + int response_code = -1; + bool is_valid_response_code = false; + if (request->status().is_success()) { + response_code = request->GetResponseCode(); + is_valid_response_code = (response_code / 100 == 2); + + std::string mime_type; + request->GetMimeType(&mime_type); + manifest_has_valid_mime_type_ = (mime_type == "text/cache-manifest"); + } + + if (is_valid_response_code) { + manifest_data_ = fetcher->manifest_data(); + manifest_response_info_.reset( + new net::HttpResponseInfo(request->response_info())); + if (update_type_ == UPGRADE_ATTEMPT) + CheckIfManifestChanged(); // continues asynchronously + else + ContinueHandleManifestFetchCompleted(true); + } else if (response_code == 304 && update_type_ == UPGRADE_ATTEMPT) { + ContinueHandleManifestFetchCompleted(false); + } else if ((response_code == 404 || response_code == 410) && + update_type_ == UPGRADE_ATTEMPT) { + storage_->MakeGroupObsolete(group_, this, response_code); // async + } else { + const char* kFormatString = "Manifest fetch failed (%d) %s"; + std::string message = FormatUrlErrorMessage( + kFormatString, manifest_url_, fetcher->result(), response_code); + HandleCacheFailure(AppCacheErrorDetails(message, + APPCACHE_MANIFEST_ERROR, + manifest_url_, + response_code, + false /*is_cross_origin*/), + fetcher->result(), + GURL()); + } +} + +void AppCacheUpdateJob::OnGroupMadeObsolete(AppCacheGroup* group, + bool success, + int response_code) { + DCHECK(master_entry_fetches_.empty()); + CancelAllMasterEntryFetches(AppCacheErrorDetails( + "The cache has been made obsolete, " + "the manifest file returned 404 or 410", + APPCACHE_MANIFEST_ERROR, + GURL(), + response_code, + false /*is_cross_origin*/)); + if (success) { + DCHECK(group->is_obsolete()); + NotifyAllAssociatedHosts(APPCACHE_OBSOLETE_EVENT); + internal_state_ = COMPLETED; + MaybeCompleteUpdate(); + } else { + // Treat failure to mark group obsolete as a cache failure. + HandleCacheFailure(AppCacheErrorDetails( + "Failed to mark the cache as obsolete", + APPCACHE_UNKNOWN_ERROR, + GURL(), + 0, + false /*is_cross_origin*/), + DB_ERROR, + GURL()); + } +} + +void AppCacheUpdateJob::ContinueHandleManifestFetchCompleted(bool changed) { + DCHECK(internal_state_ == FETCH_MANIFEST); + + if (!changed) { + DCHECK(update_type_ == UPGRADE_ATTEMPT); + internal_state_ = NO_UPDATE; + + // Wait for pending master entries to download. + FetchMasterEntries(); + MaybeCompleteUpdate(); // if not done, run async 6.9.4 step 7 substeps + return; + } + + Manifest manifest; + if (!ParseManifest(manifest_url_, manifest_data_.data(), + manifest_data_.length(), + manifest_has_valid_mime_type_ ? + PARSE_MANIFEST_ALLOWING_INTERCEPTS : + PARSE_MANIFEST_PER_STANDARD, + manifest)) { + const char* kFormatString = "Failed to parse manifest %s"; + const std::string message = base::StringPrintf(kFormatString, + manifest_url_.spec().c_str()); + HandleCacheFailure( + AppCacheErrorDetails( + message, APPCACHE_SIGNATURE_ERROR, GURL(), 0, + false /*is_cross_origin*/), + MANIFEST_ERROR, + GURL()); + VLOG(1) << message; + return; + } + + // Proceed with update process. Section 6.9.4 steps 8-20. + internal_state_ = DOWNLOADING; + inprogress_cache_ = new AppCache(storage_, storage_->NewCacheId()); + BuildUrlFileList(manifest); + inprogress_cache_->InitializeWithManifest(&manifest); + + // Associate all pending master hosts with the newly created cache. + for (PendingMasters::iterator it = pending_master_entries_.begin(); + it != pending_master_entries_.end(); ++it) { + PendingHosts& hosts = it->second; + for (PendingHosts::iterator host_it = hosts.begin(); + host_it != hosts.end(); ++host_it) { + (*host_it) + ->AssociateIncompleteCache(inprogress_cache_.get(), manifest_url_); + } + } + + if (manifest.did_ignore_intercept_namespaces) { + // Must be done after associating all pending master hosts. + std::string message( + "Ignoring the INTERCEPT section of the application cache manifest " + "because the content type is not text/cache-manifest"); + LogConsoleMessageToAll(message); + } + + group_->SetUpdateAppCacheStatus(AppCacheGroup::DOWNLOADING); + NotifyAllAssociatedHosts(APPCACHE_DOWNLOADING_EVENT); + FetchUrls(); + FetchMasterEntries(); + MaybeCompleteUpdate(); // if not done, continues when async fetches complete +} + +void AppCacheUpdateJob::HandleUrlFetchCompleted(URLFetcher* fetcher) { + DCHECK(internal_state_ == DOWNLOADING); + + net::URLRequest* request = fetcher->request(); + const GURL& url = request->original_url(); + pending_url_fetches_.erase(url); + NotifyAllProgress(url); + ++url_fetches_completed_; + + int response_code = request->status().is_success() + ? request->GetResponseCode() + : fetcher->redirect_response_code(); + + AppCacheEntry& entry = url_file_list_.find(url)->second; + + if (response_code / 100 == 2) { + // Associate storage with the new entry. + DCHECK(fetcher->response_writer()); + entry.set_response_id(fetcher->response_writer()->response_id()); + entry.set_response_size(fetcher->response_writer()->amount_written()); + if (!inprogress_cache_->AddOrModifyEntry(url, entry)) + duplicate_response_ids_.push_back(entry.response_id()); + + // TODO(michaeln): Check for <html manifest=xxx> + // See http://code.google.com/p/chromium/issues/detail?id=97930 + // if (entry.IsMaster() && !(entry.IsExplicit() || fallback || intercept)) + // if (!manifestAttribute) skip it + + // Foreign entries will be detected during cache selection. + // Note: 6.9.4, step 17.9 possible optimization: if resource is HTML or XML + // file whose root element is an html element with a manifest attribute + // whose value doesn't match the manifest url of the application cache + // being processed, mark the entry as being foreign. + } else { + VLOG(1) << "Request status: " << request->status().status() + << " error: " << request->status().error() + << " response code: " << response_code; + if (entry.IsExplicit() || entry.IsFallback() || entry.IsIntercept()) { + if (response_code == 304 && fetcher->existing_entry().has_response_id()) { + // Keep the existing response. + entry.set_response_id(fetcher->existing_entry().response_id()); + entry.set_response_size(fetcher->existing_entry().response_size()); + inprogress_cache_->AddOrModifyEntry(url, entry); + } else { + const char* kFormatString = "Resource fetch failed (%d) %s"; + std::string message = FormatUrlErrorMessage( + kFormatString, url, fetcher->result(), response_code); + ResultType result = fetcher->result(); + bool is_cross_origin = url.GetOrigin() != manifest_url_.GetOrigin(); + switch (result) { + case DISKCACHE_ERROR: + HandleCacheFailure( + AppCacheErrorDetails( + message, APPCACHE_UNKNOWN_ERROR, GURL(), 0, + is_cross_origin), + result, + url); + break; + case NETWORK_ERROR: + HandleCacheFailure( + AppCacheErrorDetails(message, APPCACHE_RESOURCE_ERROR, url, 0, + is_cross_origin), + result, + url); + break; + default: + HandleCacheFailure(AppCacheErrorDetails(message, + APPCACHE_RESOURCE_ERROR, + url, + response_code, + is_cross_origin), + result, + url); + break; + } + return; + } + } else if (response_code == 404 || response_code == 410) { + // Entry is skipped. They are dropped from the cache. + } else if (update_type_ == UPGRADE_ATTEMPT && + fetcher->existing_entry().has_response_id()) { + // Keep the existing response. + // TODO(michaeln): Not sure this is a good idea. This is spec compliant + // but the old resource may or may not be compatible with the new contents + // of the cache. Impossible to know one way or the other. + entry.set_response_id(fetcher->existing_entry().response_id()); + entry.set_response_size(fetcher->existing_entry().response_size()); + inprogress_cache_->AddOrModifyEntry(url, entry); + } + } + + // Fetch another URL now that one request has completed. + DCHECK(internal_state_ != CACHE_FAILURE); + FetchUrls(); + MaybeCompleteUpdate(); +} + +void AppCacheUpdateJob::HandleMasterEntryFetchCompleted( + URLFetcher* fetcher) { + DCHECK(internal_state_ == NO_UPDATE || internal_state_ == DOWNLOADING); + + // TODO(jennb): Handle downloads completing during cache failure when update + // no longer fetches master entries directly. For now, we cancel all pending + // master entry fetches when entering cache failure state so this will never + // be called in CACHE_FAILURE state. + + net::URLRequest* request = fetcher->request(); + const GURL& url = request->original_url(); + master_entry_fetches_.erase(url); + ++master_entries_completed_; + + int response_code = request->status().is_success() + ? request->GetResponseCode() : -1; + + PendingMasters::iterator found = pending_master_entries_.find(url); + DCHECK(found != pending_master_entries_.end()); + PendingHosts& hosts = found->second; + + // Section 6.9.4. No update case: step 7.3, else step 22. + if (response_code / 100 == 2) { + // Add fetched master entry to the appropriate cache. + AppCache* cache = inprogress_cache_.get() ? inprogress_cache_.get() + : group_->newest_complete_cache(); + DCHECK(fetcher->response_writer()); + AppCacheEntry master_entry(AppCacheEntry::MASTER, + fetcher->response_writer()->response_id(), + fetcher->response_writer()->amount_written()); + if (cache->AddOrModifyEntry(url, master_entry)) + added_master_entries_.push_back(url); + else + duplicate_response_ids_.push_back(master_entry.response_id()); + + // In no-update case, associate host with the newest cache. + if (!inprogress_cache_.get()) { + // TODO(michaeln): defer until the updated cache has been stored + DCHECK(cache == group_->newest_complete_cache()); + for (PendingHosts::iterator host_it = hosts.begin(); + host_it != hosts.end(); ++host_it) { + (*host_it)->AssociateCompleteCache(cache); + } + } + } else { + HostNotifier host_notifier; + for (PendingHosts::iterator host_it = hosts.begin(); + host_it != hosts.end(); ++host_it) { + AppCacheHost* host = *host_it; + host_notifier.AddHost(host); + + // In downloading case, disassociate host from inprogress cache. + if (inprogress_cache_.get()) + host->AssociateNoCache(GURL()); + + host->RemoveObserver(this); + } + hosts.clear(); + + const char* kFormatString = "Manifest fetch failed (%d) %s"; + std::string message = FormatUrlErrorMessage( + kFormatString, request->url(), fetcher->result(), response_code); + host_notifier.SendErrorNotifications( + AppCacheErrorDetails(message, + APPCACHE_MANIFEST_ERROR, + request->url(), + response_code, + false /*is_cross_origin*/)); + + // In downloading case, update result is different if all master entries + // failed vs. only some failing. + if (inprogress_cache_.get()) { + // Only count successful downloads to know if all master entries failed. + pending_master_entries_.erase(found); + --master_entries_completed_; + + // Section 6.9.4, step 22.3. + if (update_type_ == CACHE_ATTEMPT && pending_master_entries_.empty()) { + HandleCacheFailure(AppCacheErrorDetails(message, + APPCACHE_MANIFEST_ERROR, + request->url(), + response_code, + false /*is_cross_origin*/), + fetcher->result(), + GURL()); + return; + } + } + } + + DCHECK(internal_state_ != CACHE_FAILURE); + FetchMasterEntries(); + MaybeCompleteUpdate(); +} + +void AppCacheUpdateJob::HandleManifestRefetchCompleted( + URLFetcher* fetcher) { + DCHECK(internal_state_ == REFETCH_MANIFEST); + DCHECK(manifest_fetcher_ == fetcher); + manifest_fetcher_ = NULL; + + net::URLRequest* request = fetcher->request(); + int response_code = request->status().is_success() + ? request->GetResponseCode() : -1; + if (response_code == 304 || manifest_data_ == fetcher->manifest_data()) { + // Only need to store response in storage if manifest is not already + // an entry in the cache. + AppCacheEntry* entry = inprogress_cache_->GetEntry(manifest_url_); + if (entry) { + entry->add_types(AppCacheEntry::MANIFEST); + StoreGroupAndCache(); + } else { + manifest_response_writer_.reset(CreateResponseWriter()); + scoped_refptr<HttpResponseInfoIOBuffer> io_buffer( + new HttpResponseInfoIOBuffer(manifest_response_info_.release())); + manifest_response_writer_->WriteInfo( + io_buffer.get(), + base::Bind(&AppCacheUpdateJob::OnManifestInfoWriteComplete, + base::Unretained(this))); + } + } else { + VLOG(1) << "Request status: " << request->status().status() + << " error: " << request->status().error() + << " response code: " << response_code; + ScheduleUpdateRetry(kRerunDelayMs); + if (response_code == 200) { + HandleCacheFailure(AppCacheErrorDetails("Manifest changed during update", + APPCACHE_CHANGED_ERROR, + GURL(), + 0, + false /*is_cross_origin*/), + MANIFEST_ERROR, + GURL()); + } else { + const char* kFormatString = "Manifest re-fetch failed (%d) %s"; + std::string message = FormatUrlErrorMessage( + kFormatString, manifest_url_, fetcher->result(), response_code); + HandleCacheFailure(AppCacheErrorDetails(message, + APPCACHE_MANIFEST_ERROR, + GURL(), + response_code, + false /*is_cross_origin*/), + fetcher->result(), + GURL()); + } + } +} + +void AppCacheUpdateJob::OnManifestInfoWriteComplete(int result) { + if (result > 0) { + scoped_refptr<net::StringIOBuffer> io_buffer( + new net::StringIOBuffer(manifest_data_)); + manifest_response_writer_->WriteData( + io_buffer.get(), + manifest_data_.length(), + base::Bind(&AppCacheUpdateJob::OnManifestDataWriteComplete, + base::Unretained(this))); + } else { + HandleCacheFailure( + AppCacheErrorDetails("Failed to write the manifest headers to storage", + APPCACHE_UNKNOWN_ERROR, + GURL(), + 0, + false /*is_cross_origin*/), + DISKCACHE_ERROR, + GURL()); + } +} + +void AppCacheUpdateJob::OnManifestDataWriteComplete(int result) { + if (result > 0) { + AppCacheEntry entry(AppCacheEntry::MANIFEST, + manifest_response_writer_->response_id(), + manifest_response_writer_->amount_written()); + if (!inprogress_cache_->AddOrModifyEntry(manifest_url_, entry)) + duplicate_response_ids_.push_back(entry.response_id()); + StoreGroupAndCache(); + } else { + HandleCacheFailure( + AppCacheErrorDetails("Failed to write the manifest data to storage", + APPCACHE_UNKNOWN_ERROR, + GURL(), + 0, + false /*is_cross_origin*/), + DISKCACHE_ERROR, + GURL()); + } +} + +void AppCacheUpdateJob::StoreGroupAndCache() { + DCHECK(stored_state_ == UNSTORED); + stored_state_ = STORING; + scoped_refptr<AppCache> newest_cache; + if (inprogress_cache_.get()) + newest_cache.swap(inprogress_cache_); + else + newest_cache = group_->newest_complete_cache(); + newest_cache->set_update_time(base::Time::Now()); + + // TODO(michaeln): dcheck is fishing for clues to crbug/95101 + DCHECK_EQ(manifest_url_, group_->manifest_url()); + storage_->StoreGroupAndNewestCache(group_, newest_cache.get(), this); +} + +void AppCacheUpdateJob::OnGroupAndNewestCacheStored(AppCacheGroup* group, + AppCache* newest_cache, + bool success, + bool would_exceed_quota) { + DCHECK(stored_state_ == STORING); + if (success) { + stored_state_ = STORED; + MaybeCompleteUpdate(); // will definitely complete + } else { + stored_state_ = UNSTORED; + + // Restore inprogress_cache_ to get the proper events delivered + // and the proper cleanup to occur. + if (newest_cache != group->newest_complete_cache()) + inprogress_cache_ = newest_cache; + + ResultType result = DB_ERROR; + AppCacheErrorReason reason = APPCACHE_UNKNOWN_ERROR; + std::string message("Failed to commit new cache to storage"); + if (would_exceed_quota) { + message.append(", would exceed quota"); + result = QUOTA_ERROR; + reason = APPCACHE_QUOTA_ERROR; + } + HandleCacheFailure( + AppCacheErrorDetails(message, reason, GURL(), 0, + false /*is_cross_origin*/), + result, + GURL()); + } +} + +void AppCacheUpdateJob::NotifySingleHost(AppCacheHost* host, + AppCacheEventID event_id) { + std::vector<int> ids(1, host->host_id()); + host->frontend()->OnEventRaised(ids, event_id); +} + +void AppCacheUpdateJob::NotifyAllAssociatedHosts(AppCacheEventID event_id) { + HostNotifier host_notifier; + AddAllAssociatedHostsToNotifier(&host_notifier); + host_notifier.SendNotifications(event_id); +} + +void AppCacheUpdateJob::NotifyAllProgress(const GURL& url) { + HostNotifier host_notifier; + AddAllAssociatedHostsToNotifier(&host_notifier); + host_notifier.SendProgressNotifications( + url, url_file_list_.size(), url_fetches_completed_); +} + +void AppCacheUpdateJob::NotifyAllFinalProgress() { + DCHECK(url_file_list_.size() == url_fetches_completed_); + NotifyAllProgress(GURL()); +} + +void AppCacheUpdateJob::NotifyAllError(const AppCacheErrorDetails& details) { + HostNotifier host_notifier; + AddAllAssociatedHostsToNotifier(&host_notifier); + host_notifier.SendErrorNotifications(details); +} + +void AppCacheUpdateJob::LogConsoleMessageToAll(const std::string& message) { + HostNotifier host_notifier; + AddAllAssociatedHostsToNotifier(&host_notifier); + host_notifier.SendLogMessage(message); +} + +void AppCacheUpdateJob::AddAllAssociatedHostsToNotifier( + HostNotifier* host_notifier) { + // Collect hosts so we only send one notification per frontend. + // A host can only be associated with a single cache so no need to worry + // about duplicate hosts being added to the notifier. + if (inprogress_cache_.get()) { + DCHECK(internal_state_ == DOWNLOADING || internal_state_ == CACHE_FAILURE); + host_notifier->AddHosts(inprogress_cache_->associated_hosts()); + } + + AppCacheGroup::Caches old_caches = group_->old_caches(); + for (AppCacheGroup::Caches::const_iterator it = old_caches.begin(); + it != old_caches.end(); ++it) { + host_notifier->AddHosts((*it)->associated_hosts()); + } + + AppCache* newest_cache = group_->newest_complete_cache(); + if (newest_cache) + host_notifier->AddHosts(newest_cache->associated_hosts()); +} + +void AppCacheUpdateJob::OnDestructionImminent(AppCacheHost* host) { + // The host is about to be deleted; remove from our collection. + PendingMasters::iterator found = + pending_master_entries_.find(host->pending_master_entry_url()); + DCHECK(found != pending_master_entries_.end()); + PendingHosts& hosts = found->second; + PendingHosts::iterator it = std::find(hosts.begin(), hosts.end(), host); + DCHECK(it != hosts.end()); + hosts.erase(it); +} + +void AppCacheUpdateJob::OnServiceReinitialized( + AppCacheStorageReference* old_storage_ref) { + // We continue to use the disabled instance, but arrange for its + // deletion when its no longer needed. + if (old_storage_ref->storage() == storage_) + disabled_storage_reference_ = old_storage_ref; +} + +void AppCacheUpdateJob::CheckIfManifestChanged() { + DCHECK(update_type_ == UPGRADE_ATTEMPT); + AppCacheEntry* entry = NULL; + if (group_->newest_complete_cache()) + entry = group_->newest_complete_cache()->GetEntry(manifest_url_); + if (!entry) { + // TODO(michaeln): This is just a bandaid to avoid a crash. + // http://code.google.com/p/chromium/issues/detail?id=95101 + if (service_->storage() == storage_) { + // Use a local variable because service_ is reset in HandleCacheFailure. + AppCacheServiceImpl* service = service_; + HandleCacheFailure( + AppCacheErrorDetails("Manifest entry not found in existing cache", + APPCACHE_UNKNOWN_ERROR, + GURL(), + 0, + false /*is_cross_origin*/), + DB_ERROR, + GURL()); + AppCacheHistograms::AddMissingManifestEntrySample(); + service->DeleteAppCacheGroup(manifest_url_, net::CompletionCallback()); + } + return; + } + + // Load manifest data from storage to compare against fetched manifest. + manifest_response_reader_.reset( + storage_->CreateResponseReader(manifest_url_, + group_->group_id(), + entry->response_id())); + read_manifest_buffer_ = new net::IOBuffer(kBufferSize); + manifest_response_reader_->ReadData( + read_manifest_buffer_.get(), + kBufferSize, + base::Bind(&AppCacheUpdateJob::OnManifestDataReadComplete, + base::Unretained(this))); // async read +} + +void AppCacheUpdateJob::OnManifestDataReadComplete(int result) { + if (result > 0) { + loaded_manifest_data_.append(read_manifest_buffer_->data(), result); + manifest_response_reader_->ReadData( + read_manifest_buffer_.get(), + kBufferSize, + base::Bind(&AppCacheUpdateJob::OnManifestDataReadComplete, + base::Unretained(this))); // read more + } else { + read_manifest_buffer_ = NULL; + manifest_response_reader_.reset(); + ContinueHandleManifestFetchCompleted( + result < 0 || manifest_data_ != loaded_manifest_data_); + } +} + +void AppCacheUpdateJob::BuildUrlFileList(const Manifest& manifest) { + for (base::hash_set<std::string>::const_iterator it = + manifest.explicit_urls.begin(); + it != manifest.explicit_urls.end(); ++it) { + AddUrlToFileList(GURL(*it), AppCacheEntry::EXPLICIT); + } + + const std::vector<AppCacheNamespace>& intercepts = + manifest.intercept_namespaces; + for (std::vector<AppCacheNamespace>::const_iterator it = intercepts.begin(); + it != intercepts.end(); ++it) { + int flags = AppCacheEntry::INTERCEPT; + if (it->is_executable) + flags |= AppCacheEntry::EXECUTABLE; + AddUrlToFileList(it->target_url, flags); + } + + const std::vector<AppCacheNamespace>& fallbacks = + manifest.fallback_namespaces; + for (std::vector<AppCacheNamespace>::const_iterator it = fallbacks.begin(); + it != fallbacks.end(); ++it) { + AddUrlToFileList(it->target_url, AppCacheEntry::FALLBACK); + } + + // Add all master entries from newest complete cache. + if (update_type_ == UPGRADE_ATTEMPT) { + const AppCache::EntryMap& entries = + group_->newest_complete_cache()->entries(); + for (AppCache::EntryMap::const_iterator it = entries.begin(); + it != entries.end(); ++it) { + const AppCacheEntry& entry = it->second; + if (entry.IsMaster()) + AddUrlToFileList(it->first, AppCacheEntry::MASTER); + } + } +} + +void AppCacheUpdateJob::AddUrlToFileList(const GURL& url, int type) { + std::pair<AppCache::EntryMap::iterator, bool> ret = url_file_list_.insert( + AppCache::EntryMap::value_type(url, AppCacheEntry(type))); + + if (ret.second) + urls_to_fetch_.push_back(UrlToFetch(url, false, NULL)); + else + ret.first->second.add_types(type); // URL already exists. Merge types. +} + +void AppCacheUpdateJob::FetchUrls() { + DCHECK(internal_state_ == DOWNLOADING); + + // Fetch each URL in the list according to section 6.9.4 step 17.1-17.3. + // Fetch up to the concurrent limit. Other fetches will be triggered as each + // each fetch completes. + while (pending_url_fetches_.size() < kMaxConcurrentUrlFetches && + !urls_to_fetch_.empty()) { + UrlToFetch url_to_fetch = urls_to_fetch_.front(); + urls_to_fetch_.pop_front(); + + AppCache::EntryMap::iterator it = url_file_list_.find(url_to_fetch.url); + DCHECK(it != url_file_list_.end()); + AppCacheEntry& entry = it->second; + if (ShouldSkipUrlFetch(entry)) { + NotifyAllProgress(url_to_fetch.url); + ++url_fetches_completed_; + } else if (AlreadyFetchedEntry(url_to_fetch.url, entry.types())) { + NotifyAllProgress(url_to_fetch.url); + ++url_fetches_completed_; // saved a URL request + } else if (!url_to_fetch.storage_checked && + MaybeLoadFromNewestCache(url_to_fetch.url, entry)) { + // Continues asynchronously after data is loaded from newest cache. + } else { + URLFetcher* fetcher = new URLFetcher( + url_to_fetch.url, URLFetcher::URL_FETCH, this); + if (url_to_fetch.existing_response_info.get()) { + DCHECK(group_->newest_complete_cache()); + AppCacheEntry* existing_entry = + group_->newest_complete_cache()->GetEntry(url_to_fetch.url); + DCHECK(existing_entry); + DCHECK(existing_entry->response_id() == + url_to_fetch.existing_response_info->response_id()); + fetcher->set_existing_response_headers( + url_to_fetch.existing_response_info->http_response_info()->headers + .get()); + fetcher->set_existing_entry(*existing_entry); + } + fetcher->Start(); + pending_url_fetches_.insert( + PendingUrlFetches::value_type(url_to_fetch.url, fetcher)); + } + } +} + +void AppCacheUpdateJob::CancelAllUrlFetches() { + // Cancel any pending URL requests. + for (PendingUrlFetches::iterator it = pending_url_fetches_.begin(); + it != pending_url_fetches_.end(); ++it) { + delete it->second; + } + + url_fetches_completed_ += + pending_url_fetches_.size() + urls_to_fetch_.size(); + pending_url_fetches_.clear(); + urls_to_fetch_.clear(); +} + +bool AppCacheUpdateJob::ShouldSkipUrlFetch(const AppCacheEntry& entry) { + // 6.6.4 Step 17 + // If the resource URL being processed was flagged as neither an + // "explicit entry" nor or a "fallback entry", then the user agent + // may skip this URL. + if (entry.IsExplicit() || entry.IsFallback() || entry.IsIntercept()) + return false; + + // TODO(jennb): decide if entry should be skipped to expire it from cache + return false; +} + +bool AppCacheUpdateJob::AlreadyFetchedEntry(const GURL& url, + int entry_type) { + DCHECK(internal_state_ == DOWNLOADING || internal_state_ == NO_UPDATE); + AppCacheEntry* existing = + inprogress_cache_.get() ? inprogress_cache_->GetEntry(url) + : group_->newest_complete_cache()->GetEntry(url); + if (existing) { + existing->add_types(entry_type); + return true; + } + return false; +} + +void AppCacheUpdateJob::AddMasterEntryToFetchList(AppCacheHost* host, + const GURL& url, + bool is_new) { + DCHECK(!IsTerminating()); + + if (internal_state_ == DOWNLOADING || internal_state_ == NO_UPDATE) { + AppCache* cache; + if (inprogress_cache_.get()) { + // always associate + host->AssociateIncompleteCache(inprogress_cache_.get(), manifest_url_); + cache = inprogress_cache_.get(); + } else { + cache = group_->newest_complete_cache(); + } + + // Update existing entry if it has already been fetched. + AppCacheEntry* entry = cache->GetEntry(url); + if (entry) { + entry->add_types(AppCacheEntry::MASTER); + if (internal_state_ == NO_UPDATE && !inprogress_cache_.get()) { + // only associate if have entry + host->AssociateCompleteCache(cache); + } + if (is_new) + ++master_entries_completed_; // pretend fetching completed + return; + } + } + + // Add to fetch list if not already fetching. + if (master_entry_fetches_.find(url) == master_entry_fetches_.end()) { + master_entries_to_fetch_.insert(url); + if (internal_state_ == DOWNLOADING || internal_state_ == NO_UPDATE) + FetchMasterEntries(); + } +} + +void AppCacheUpdateJob::FetchMasterEntries() { + DCHECK(internal_state_ == NO_UPDATE || internal_state_ == DOWNLOADING); + + // Fetch each master entry in the list, up to the concurrent limit. + // Additional fetches will be triggered as each fetch completes. + while (master_entry_fetches_.size() < kMaxConcurrentUrlFetches && + !master_entries_to_fetch_.empty()) { + const GURL& url = *master_entries_to_fetch_.begin(); + + if (AlreadyFetchedEntry(url, AppCacheEntry::MASTER)) { + ++master_entries_completed_; // saved a URL request + + // In no update case, associate hosts to newest cache in group + // now that master entry has been "successfully downloaded". + if (internal_state_ == NO_UPDATE) { + // TODO(michaeln): defer until the updated cache has been stored. + DCHECK(!inprogress_cache_.get()); + AppCache* cache = group_->newest_complete_cache(); + PendingMasters::iterator found = pending_master_entries_.find(url); + DCHECK(found != pending_master_entries_.end()); + PendingHosts& hosts = found->second; + for (PendingHosts::iterator host_it = hosts.begin(); + host_it != hosts.end(); ++host_it) { + (*host_it)->AssociateCompleteCache(cache); + } + } + } else { + URLFetcher* fetcher = new URLFetcher( + url, URLFetcher::MASTER_ENTRY_FETCH, this); + fetcher->Start(); + master_entry_fetches_.insert(PendingUrlFetches::value_type(url, fetcher)); + } + + master_entries_to_fetch_.erase(master_entries_to_fetch_.begin()); + } +} + +void AppCacheUpdateJob::CancelAllMasterEntryFetches( + const AppCacheErrorDetails& error_details) { + // For now, cancel all in-progress fetches for master entries and pretend + // all master entries fetches have completed. + // TODO(jennb): Delete this when update no longer fetches master entries + // directly. + + // Cancel all in-progress fetches. + for (PendingUrlFetches::iterator it = master_entry_fetches_.begin(); + it != master_entry_fetches_.end(); ++it) { + delete it->second; + master_entries_to_fetch_.insert(it->first); // back in unfetched list + } + master_entry_fetches_.clear(); + + master_entries_completed_ += master_entries_to_fetch_.size(); + + // Cache failure steps, step 2. + // Pretend all master entries that have not yet been fetched have completed + // downloading. Unassociate hosts from any appcache and send ERROR event. + HostNotifier host_notifier; + while (!master_entries_to_fetch_.empty()) { + const GURL& url = *master_entries_to_fetch_.begin(); + PendingMasters::iterator found = pending_master_entries_.find(url); + DCHECK(found != pending_master_entries_.end()); + PendingHosts& hosts = found->second; + for (PendingHosts::iterator host_it = hosts.begin(); + host_it != hosts.end(); ++host_it) { + AppCacheHost* host = *host_it; + host->AssociateNoCache(GURL()); + host_notifier.AddHost(host); + host->RemoveObserver(this); + } + hosts.clear(); + + master_entries_to_fetch_.erase(master_entries_to_fetch_.begin()); + } + host_notifier.SendErrorNotifications(error_details); +} + +bool AppCacheUpdateJob::MaybeLoadFromNewestCache(const GURL& url, + AppCacheEntry& entry) { + if (update_type_ != UPGRADE_ATTEMPT) + return false; + + AppCache* newest = group_->newest_complete_cache(); + AppCacheEntry* copy_me = newest->GetEntry(url); + if (!copy_me || !copy_me->has_response_id()) + return false; + + // Load HTTP headers for entry from newest cache. + loading_responses_.insert( + LoadingResponses::value_type(copy_me->response_id(), url)); + storage_->LoadResponseInfo(manifest_url_, group_->group_id(), + copy_me->response_id(), + this); + // Async: wait for OnResponseInfoLoaded to complete. + return true; +} + +void AppCacheUpdateJob::OnResponseInfoLoaded( + AppCacheResponseInfo* response_info, int64 response_id) { + const net::HttpResponseInfo* http_info = response_info ? + response_info->http_response_info() : NULL; + + // Needed response info for a manifest fetch request. + if (internal_state_ == FETCH_MANIFEST) { + if (http_info) + manifest_fetcher_->set_existing_response_headers( + http_info->headers.get()); + manifest_fetcher_->Start(); + return; + } + + LoadingResponses::iterator found = loading_responses_.find(response_id); + DCHECK(found != loading_responses_.end()); + const GURL& url = found->second; + + if (!http_info) { + LoadFromNewestCacheFailed(url, NULL); // no response found + } else { + // Check if response can be re-used according to HTTP caching semantics. + // Responses with a "vary" header get treated as expired. + const std::string name = "vary"; + std::string value; + void* iter = NULL; + if (!http_info->headers.get() || + http_info->headers->RequiresValidation(http_info->request_time, + http_info->response_time, + base::Time::Now()) || + http_info->headers->EnumerateHeader(&iter, name, &value)) { + LoadFromNewestCacheFailed(url, response_info); + } else { + DCHECK(group_->newest_complete_cache()); + AppCacheEntry* copy_me = group_->newest_complete_cache()->GetEntry(url); + DCHECK(copy_me); + DCHECK(copy_me->response_id() == response_id); + + AppCache::EntryMap::iterator it = url_file_list_.find(url); + DCHECK(it != url_file_list_.end()); + AppCacheEntry& entry = it->second; + entry.set_response_id(response_id); + entry.set_response_size(copy_me->response_size()); + inprogress_cache_->AddOrModifyEntry(url, entry); + NotifyAllProgress(url); + ++url_fetches_completed_; + } + } + loading_responses_.erase(found); + + MaybeCompleteUpdate(); +} + +void AppCacheUpdateJob::LoadFromNewestCacheFailed( + const GURL& url, AppCacheResponseInfo* response_info) { + if (internal_state_ == CACHE_FAILURE) + return; + + // Re-insert url at front of fetch list. Indicate storage has been checked. + urls_to_fetch_.push_front(UrlToFetch(url, true, response_info)); + FetchUrls(); +} + +void AppCacheUpdateJob::MaybeCompleteUpdate() { + DCHECK(internal_state_ != CACHE_FAILURE); + + // Must wait for any pending master entries or url fetches to complete. + if (master_entries_completed_ != pending_master_entries_.size() || + url_fetches_completed_ != url_file_list_.size()) { + DCHECK(internal_state_ != COMPLETED); + return; + } + + switch (internal_state_) { + case NO_UPDATE: + if (master_entries_completed_ > 0) { + switch (stored_state_) { + case UNSTORED: + StoreGroupAndCache(); + return; + case STORING: + return; + case STORED: + break; + } + } + // 6.9.4 steps 7.3-7.7. + NotifyAllAssociatedHosts(APPCACHE_NO_UPDATE_EVENT); + DiscardDuplicateResponses(); + internal_state_ = COMPLETED; + break; + case DOWNLOADING: + internal_state_ = REFETCH_MANIFEST; + FetchManifest(false); + break; + case REFETCH_MANIFEST: + DCHECK(stored_state_ == STORED); + NotifyAllFinalProgress(); + if (update_type_ == CACHE_ATTEMPT) + NotifyAllAssociatedHosts(APPCACHE_CACHED_EVENT); + else + NotifyAllAssociatedHosts(APPCACHE_UPDATE_READY_EVENT); + DiscardDuplicateResponses(); + internal_state_ = COMPLETED; + LogHistogramStats(UPDATE_OK, GURL()); + break; + case CACHE_FAILURE: + NOTREACHED(); // See HandleCacheFailure + break; + default: + break; + } + + // Let the stack unwind before deletion to make it less risky as this + // method is called from multiple places in this file. + if (internal_state_ == COMPLETED) + DeleteSoon(); +} + +void AppCacheUpdateJob::ScheduleUpdateRetry(int delay_ms) { + // TODO(jennb): post a delayed task with the "same parameters" as this job + // to retry the update at a later time. Need group, URLs of pending master + // entries and their hosts. +} + +void AppCacheUpdateJob::Cancel() { + internal_state_ = CANCELLED; + + LogHistogramStats(CANCELLED_ERROR, GURL()); + + if (manifest_fetcher_) { + delete manifest_fetcher_; + manifest_fetcher_ = NULL; + } + + for (PendingUrlFetches::iterator it = pending_url_fetches_.begin(); + it != pending_url_fetches_.end(); ++it) { + delete it->second; + } + pending_url_fetches_.clear(); + + for (PendingUrlFetches::iterator it = master_entry_fetches_.begin(); + it != master_entry_fetches_.end(); ++it) { + delete it->second; + } + master_entry_fetches_.clear(); + + ClearPendingMasterEntries(); + DiscardInprogressCache(); + + // Delete response writer to avoid any callbacks. + if (manifest_response_writer_) + manifest_response_writer_.reset(); + + storage_->CancelDelegateCallbacks(this); +} + +void AppCacheUpdateJob::ClearPendingMasterEntries() { + for (PendingMasters::iterator it = pending_master_entries_.begin(); + it != pending_master_entries_.end(); ++it) { + PendingHosts& hosts = it->second; + for (PendingHosts::iterator host_it = hosts.begin(); + host_it != hosts.end(); ++host_it) { + (*host_it)->RemoveObserver(this); + } + } + + pending_master_entries_.clear(); +} + +void AppCacheUpdateJob::DiscardInprogressCache() { + if (stored_state_ == STORING) { + // We can make no assumptions about whether the StoreGroupAndCacheTask + // actually completed or not. This condition should only be reachable + // during shutdown. Free things up and return to do no harm. + inprogress_cache_ = NULL; + added_master_entries_.clear(); + return; + } + + storage_->DoomResponses(manifest_url_, stored_response_ids_); + + if (!inprogress_cache_.get()) { + // We have to undo the changes we made, if any, to the existing cache. + if (group_ && group_->newest_complete_cache()) { + for (std::vector<GURL>::iterator iter = added_master_entries_.begin(); + iter != added_master_entries_.end(); ++iter) { + group_->newest_complete_cache()->RemoveEntry(*iter); + } + } + added_master_entries_.clear(); + return; + } + + AppCache::AppCacheHosts& hosts = inprogress_cache_->associated_hosts(); + while (!hosts.empty()) + (*hosts.begin())->AssociateNoCache(GURL()); + + inprogress_cache_ = NULL; + added_master_entries_.clear(); +} + +void AppCacheUpdateJob::DiscardDuplicateResponses() { + storage_->DoomResponses(manifest_url_, duplicate_response_ids_); +} + +void AppCacheUpdateJob::LogHistogramStats( + ResultType result, const GURL& failed_resource_url) { + AppCacheHistograms::CountUpdateJobResult(result, manifest_url_.GetOrigin()); + if (result == UPDATE_OK) + return; + + int percent_complete = 0; + if (url_file_list_.size() > 0) { + size_t actual_fetches_completed = url_fetches_completed_; + if (!failed_resource_url.is_empty() && actual_fetches_completed) + --actual_fetches_completed; + percent_complete = (static_cast<double>(actual_fetches_completed) / + static_cast<double>(url_file_list_.size())) * 100.0; + percent_complete = std::min(percent_complete, 99); + } + + bool was_making_progress = + base::Time::Now() - last_progress_time_ < + base::TimeDelta::FromMinutes(5); + + bool off_origin_resource_failure = + !failed_resource_url.is_empty() && + (failed_resource_url.GetOrigin() != manifest_url_.GetOrigin()); + + AppCacheHistograms::LogUpdateFailureStats( + manifest_url_.GetOrigin(), + percent_complete, + was_making_progress, + off_origin_resource_failure); +} + +void AppCacheUpdateJob::DeleteSoon() { + ClearPendingMasterEntries(); + manifest_response_writer_.reset(); + storage_->CancelDelegateCallbacks(this); + service_->RemoveObserver(this); + service_ = NULL; + + // Break the connection with the group so the group cannot call delete + // on this object after we've posted a task to delete ourselves. + group_->SetUpdateAppCacheStatus(AppCacheGroup::IDLE); + group_ = NULL; + + base::MessageLoop::current()->DeleteSoon(FROM_HERE, this); +} + +} // namespace content diff --git a/content/browser/appcache/appcache_update_job.h b/content/browser/appcache/appcache_update_job.h new file mode 100644 index 0000000..14fc831 --- /dev/null +++ b/content/browser/appcache/appcache_update_job.h @@ -0,0 +1,352 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_APPCACHE_APPCACHE_UPDATE_JOB_H_ +#define CONTENT_BROWSER_APPCACHE_APPCACHE_UPDATE_JOB_H_ + +#include <deque> +#include <map> +#include <set> +#include <string> +#include <vector> + +#include "base/gtest_prod_util.h" +#include "base/memory/ref_counted.h" +#include "base/time/time.h" +#include "content/browser/appcache/appcache.h" +#include "content/browser/appcache/appcache_host.h" +#include "content/browser/appcache/appcache_response.h" +#include "content/browser/appcache/appcache_service_impl.h" +#include "content/browser/appcache/appcache_storage.h" +#include "content/common/appcache_interfaces.h" +#include "content/common/content_export.h" +#include "net/base/completion_callback.h" +#include "net/http/http_response_headers.h" +#include "net/url_request/url_request.h" +#include "url/gurl.h" + +namespace content { +FORWARD_DECLARE_TEST(AppCacheGroupTest, QueueUpdate); +class AppCacheGroupTest; +class AppCacheUpdateJobTest; +} + +namespace content { + +class HostNotifier; + +// Application cache Update algorithm and state. +class CONTENT_EXPORT AppCacheUpdateJob + : public AppCacheStorage::Delegate, + public AppCacheHost::Observer, + public AppCacheServiceImpl::Observer { + public: + // Used for uma stats only for now, so new values are append only. + enum ResultType { + UPDATE_OK, DB_ERROR, DISKCACHE_ERROR, QUOTA_ERROR, REDIRECT_ERROR, + MANIFEST_ERROR, NETWORK_ERROR, SERVER_ERROR, CANCELLED_ERROR, + NUM_UPDATE_JOB_RESULT_TYPES + }; + + AppCacheUpdateJob(AppCacheServiceImpl* service, AppCacheGroup* group); + virtual ~AppCacheUpdateJob(); + + // Triggers the update process or adds more info if this update is already + // in progress. + void StartUpdate(AppCacheHost* host, const GURL& new_master_resource); + + private: + friend class content::AppCacheGroupTest; + friend class content::AppCacheUpdateJobTest; + class URLFetcher; + + // Master entries have multiple hosts, for example, the same page is opened + // in different tabs. + typedef std::vector<AppCacheHost*> PendingHosts; + typedef std::map<GURL, PendingHosts> PendingMasters; + typedef std::map<GURL, URLFetcher*> PendingUrlFetches; + typedef std::map<int64, GURL> LoadingResponses; + + static const int kRerunDelayMs = 1000; + + // TODO(michaeln): Rework the set of states vs update types vs stored states. + // The NO_UPDATE state is really more of an update type. For all update types + // storing the results is relevant. + + enum UpdateType { + UNKNOWN_TYPE, + UPGRADE_ATTEMPT, + CACHE_ATTEMPT, + }; + + enum InternalUpdateState { + FETCH_MANIFEST, + NO_UPDATE, + DOWNLOADING, + + // Every state after this comment indicates the update is terminating. + REFETCH_MANIFEST, + CACHE_FAILURE, + CANCELLED, + COMPLETED, + }; + + enum StoredState { + UNSTORED, + STORING, + STORED, + }; + + struct UrlToFetch { + UrlToFetch(const GURL& url, bool checked, AppCacheResponseInfo* info); + ~UrlToFetch(); + + GURL url; + bool storage_checked; + scoped_refptr<AppCacheResponseInfo> existing_response_info; + }; + + class URLFetcher : public net::URLRequest::Delegate { + public: + enum FetchType { + MANIFEST_FETCH, + URL_FETCH, + MASTER_ENTRY_FETCH, + MANIFEST_REFETCH, + }; + URLFetcher(const GURL& url, + FetchType fetch_type, + AppCacheUpdateJob* job); + virtual ~URLFetcher(); + void Start(); + FetchType fetch_type() const { return fetch_type_; } + net::URLRequest* request() const { return request_.get(); } + const AppCacheEntry& existing_entry() const { return existing_entry_; } + const std::string& manifest_data() const { return manifest_data_; } + AppCacheResponseWriter* response_writer() const { + return response_writer_.get(); + } + void set_existing_response_headers(net::HttpResponseHeaders* headers) { + existing_response_headers_ = headers; + } + void set_existing_entry(const AppCacheEntry& entry) { + existing_entry_ = entry; + } + ResultType result() const { return result_; } + int redirect_response_code() const { return redirect_response_code_; } + + private: + // URLRequest::Delegate overrides + virtual void OnReceivedRedirect(net::URLRequest* request, + const GURL& new_url, + bool* defer_redirect) OVERRIDE; + virtual void OnResponseStarted(net::URLRequest* request) OVERRIDE; + virtual void OnReadCompleted(net::URLRequest* request, + int bytes_read) OVERRIDE; + + void AddConditionalHeaders(const net::HttpResponseHeaders* headers); + void OnWriteComplete(int result); + void ReadResponseData(); + bool ConsumeResponseData(int bytes_read); + void OnResponseCompleted(); + bool MaybeRetryRequest(); + + GURL url_; + AppCacheUpdateJob* job_; + FetchType fetch_type_; + int retry_503_attempts_; + scoped_refptr<net::IOBuffer> buffer_; + scoped_ptr<net::URLRequest> request_; + AppCacheEntry existing_entry_; + scoped_refptr<net::HttpResponseHeaders> existing_response_headers_; + std::string manifest_data_; + ResultType result_; + int redirect_response_code_; + scoped_ptr<AppCacheResponseWriter> response_writer_; + }; // class URLFetcher + + AppCacheResponseWriter* CreateResponseWriter(); + + // Methods for AppCacheStorage::Delegate. + virtual void OnResponseInfoLoaded(AppCacheResponseInfo* response_info, + int64 response_id) OVERRIDE; + virtual void OnGroupAndNewestCacheStored(AppCacheGroup* group, + AppCache* newest_cache, + bool success, + bool would_exceed_quota) OVERRIDE; + virtual void OnGroupMadeObsolete(AppCacheGroup* group, + bool success, + int response_code) OVERRIDE; + + // Methods for AppCacheHost::Observer. + virtual void OnCacheSelectionComplete(AppCacheHost* host) OVERRIDE {} // N/A + virtual void OnDestructionImminent(AppCacheHost* host) OVERRIDE; + + // Methods for AppCacheServiceImpl::Observer. + virtual void OnServiceReinitialized( + AppCacheStorageReference* old_storage) OVERRIDE; + + void HandleCacheFailure(const AppCacheErrorDetails& details, + ResultType result, + const GURL& failed_resource_url); + + void FetchManifest(bool is_first_fetch); + void HandleManifestFetchCompleted(URLFetcher* fetcher); + void ContinueHandleManifestFetchCompleted(bool changed); + + void HandleUrlFetchCompleted(URLFetcher* fetcher); + void HandleMasterEntryFetchCompleted(URLFetcher* fetcher); + + void HandleManifestRefetchCompleted(URLFetcher* fetcher); + void OnManifestInfoWriteComplete(int result); + void OnManifestDataWriteComplete(int result); + + void StoreGroupAndCache(); + + void NotifySingleHost(AppCacheHost* host, AppCacheEventID event_id); + void NotifyAllAssociatedHosts(AppCacheEventID event_id); + void NotifyAllProgress(const GURL& url); + void NotifyAllFinalProgress(); + void NotifyAllError(const AppCacheErrorDetails& detals); + void LogConsoleMessageToAll(const std::string& message); + void AddAllAssociatedHostsToNotifier(HostNotifier* notifier); + + // Checks if manifest is byte for byte identical with the manifest + // in the newest application cache. + void CheckIfManifestChanged(); + void OnManifestDataReadComplete(int result); + + // Creates the list of files that may need to be fetched and initiates + // fetches. Section 6.9.4 steps 12-17 + void BuildUrlFileList(const Manifest& manifest); + void AddUrlToFileList(const GURL& url, int type); + void FetchUrls(); + void CancelAllUrlFetches(); + bool ShouldSkipUrlFetch(const AppCacheEntry& entry); + + // If entry already exists in the cache currently being updated, merge + // the entry type information with the existing entry. + // Returns true if entry exists in cache currently being updated. + bool AlreadyFetchedEntry(const GURL& url, int entry_type); + + // TODO(jennb): Delete when update no longer fetches master entries directly. + // Creates the list of master entries that need to be fetched and initiates + // fetches. + void AddMasterEntryToFetchList(AppCacheHost* host, const GURL& url, + bool is_new); + void FetchMasterEntries(); + void CancelAllMasterEntryFetches(const AppCacheErrorDetails& details); + + // Asynchronously loads the entry from the newest complete cache if the + // HTTP caching semantics allow. + // Returns false if immediately obvious that data cannot be loaded from + // newest complete cache. + bool MaybeLoadFromNewestCache(const GURL& url, AppCacheEntry& entry); + void LoadFromNewestCacheFailed(const GURL& url, + AppCacheResponseInfo* newest_response_info); + + // Does nothing if update process is still waiting for pending master + // entries or URL fetches to complete downloading. Otherwise, completes + // the update process. + void MaybeCompleteUpdate(); + + // Schedules a rerun of the entire update with the same parameters as + // this update job after a short delay. + void ScheduleUpdateRetry(int delay_ms); + + void Cancel(); + void ClearPendingMasterEntries(); + void DiscardInprogressCache(); + void DiscardDuplicateResponses(); + + void LogHistogramStats(ResultType result, const GURL& failed_resource_url); + void MadeProgress() { last_progress_time_ = base::Time::Now(); } + + // Deletes this object after letting the stack unwind. + void DeleteSoon(); + + bool IsTerminating() { return internal_state_ >= REFETCH_MANIFEST || + stored_state_ != UNSTORED; } + + AppCacheServiceImpl* service_; + const GURL manifest_url_; // here for easier access + + // Defined prior to refs to AppCaches and Groups because destruction + // order matters, the disabled_storage_reference_ must outlive those + // objects. + scoped_refptr<AppCacheStorageReference> disabled_storage_reference_; + + scoped_refptr<AppCache> inprogress_cache_; + + AppCacheGroup* group_; + + UpdateType update_type_; + InternalUpdateState internal_state_; + base::Time last_progress_time_; + + PendingMasters pending_master_entries_; + size_t master_entries_completed_; + + // TODO(jennb): Delete when update no longer fetches master entries directly. + // Helper containers to track which pending master entries have yet to be + // fetched and which are currently being fetched. Master entries that + // are listed in the manifest may be fetched as a regular URL instead of + // as a separate master entry fetch to optimize against duplicate fetches. + std::set<GURL> master_entries_to_fetch_; + PendingUrlFetches master_entry_fetches_; + + // URLs of files to fetch along with their flags. + AppCache::EntryMap url_file_list_; + size_t url_fetches_completed_; + + // Helper container to track which urls have not been fetched yet. URLs are + // removed when the fetch is initiated. Flag indicates whether an attempt + // to load the URL from storage has already been tried and failed. + std::deque<UrlToFetch> urls_to_fetch_; + + // Helper container to track which urls are being loaded from response + // storage. + LoadingResponses loading_responses_; + + // Keep track of pending URL requests so we can cancel them if necessary. + URLFetcher* manifest_fetcher_; + PendingUrlFetches pending_url_fetches_; + + // Temporary storage of manifest response data for parsing and comparison. + std::string manifest_data_; + scoped_ptr<net::HttpResponseInfo> manifest_response_info_; + scoped_ptr<AppCacheResponseWriter> manifest_response_writer_; + scoped_refptr<net::IOBuffer> read_manifest_buffer_; + std::string loaded_manifest_data_; + scoped_ptr<AppCacheResponseReader> manifest_response_reader_; + bool manifest_has_valid_mime_type_; + + // New master entries added to the cache by this job, used to cleanup + // in error conditions. + std::vector<GURL> added_master_entries_; + + // Response ids stored by this update job, used to cleanup in + // error conditions. + std::vector<int64> stored_response_ids_; + + // In some cases we fetch the same resource multiple times, and then + // have to delete the duplicates upon successful update. These ids + // are also in the stored_response_ids_ collection so we only schedule + // these for deletion on success. + // TODO(michaeln): Rework when we no longer fetches master entries directly. + std::vector<int64> duplicate_response_ids_; + + // Whether we've stored the resulting group/cache yet. + StoredState stored_state_; + + AppCacheStorage* storage_; + + FRIEND_TEST_ALL_PREFIXES(content::AppCacheGroupTest, QueueUpdate); + + DISALLOW_COPY_AND_ASSIGN(AppCacheUpdateJob); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_APPCACHE_APPCACHE_UPDATE_JOB_H_ diff --git a/content/browser/appcache/appcache_update_job_unittest.cc b/content/browser/appcache/appcache_update_job_unittest.cc index bc3957e..6eb4e02 100644 --- a/content/browser/appcache/appcache_update_job_unittest.cc +++ b/content/browser/appcache/appcache_update_job_unittest.cc @@ -7,6 +7,10 @@ #include "base/stl_util.h" #include "base/synchronization/waitable_event.h" #include "base/threading/thread.h" +#include "content/browser/appcache/appcache_group.h" +#include "content/browser/appcache/appcache_host.h" +#include "content/browser/appcache/appcache_response.h" +#include "content/browser/appcache/appcache_update_job.h" #include "content/browser/appcache/mock_appcache_service.h" #include "net/base/net_errors.h" #include "net/http/http_response_headers.h" @@ -15,35 +19,6 @@ #include "net/url_request/url_request_test_job.h" #include "net/url_request/url_request_test_util.h" #include "testing/gtest/include/gtest/gtest.h" -#include "webkit/browser/appcache/appcache_group.h" -#include "webkit/browser/appcache/appcache_host.h" -#include "webkit/browser/appcache/appcache_response.h" -#include "webkit/browser/appcache/appcache_update_job.h" - -using appcache::AppCache; -using appcache::AppCacheEntry; -using appcache::AppCacheFrontend; -using appcache::AppCacheHost; -using appcache::AppCacheGroup; -using appcache::AppCacheResponseInfo; -using appcache::AppCacheUpdateJob; -using appcache::AppCacheResponseWriter; -using appcache::APPCACHE_CACHED_EVENT; -using appcache::APPCACHE_CHECKING_EVENT; -using appcache::APPCACHE_DOWNLOADING_EVENT; -using appcache::APPCACHE_ERROR_EVENT; -using appcache::AppCacheEventID; -using appcache::APPCACHE_FALLBACK_NAMESPACE; -using appcache::HttpResponseInfoIOBuffer; -using appcache::kAppCacheNoCacheId; -using appcache::kAppCacheNoResponseId; -using appcache::Namespace; -using appcache::APPCACHE_NETWORK_NAMESPACE; -using appcache::APPCACHE_NO_UPDATE_EVENT; -using appcache::APPCACHE_OBSOLETE_EVENT; -using appcache::APPCACHE_PROGRESS_EVENT; -using appcache::APPCACHE_UPDATE_READY_EVENT; -using appcache::AppCacheStatus; namespace content { class AppCacheUpdateJobTest; @@ -227,7 +202,8 @@ class MockHttpServerJobFactory } }; -inline bool operator==(const Namespace& lhs, const Namespace& rhs) { +inline bool operator==(const AppCacheNamespace& lhs, + const AppCacheNamespace& rhs) { return lhs.type == rhs.type && lhs.namespace_url == rhs.namespace_url && lhs.target_url == rhs.target_url; @@ -244,7 +220,7 @@ class MockFrontend : public AppCacheFrontend { } virtual void OnCacheSelected( - int host_id, const appcache::AppCacheInfo& info) OVERRIDE { + int host_id, const AppCacheInfo& info) OVERRIDE { } virtual void OnStatusChanged(const std::vector<int>& host_ids, @@ -268,7 +244,7 @@ class MockFrontend : public AppCacheFrontend { } virtual void OnErrorEventRaised(const std::vector<int>& host_ids, - const appcache::AppCacheErrorDetails& details) + const AppCacheErrorDetails& details) OVERRIDE { error_message_ = details.message; OnEventRaised(host_ids, APPCACHE_ERROR_EVENT); @@ -306,7 +282,7 @@ class MockFrontend : public AppCacheFrontend { } virtual void OnLogMessage(int host_id, - appcache::AppCacheLogLevel log_level, + AppCacheLogLevel log_level, const std::string& message) OVERRIDE { } @@ -3273,7 +3249,7 @@ class AppCacheUpdateJobTest : public testing::Test, expected = 1; ASSERT_EQ(expected, cache->fallback_namespaces_.size()); EXPECT_TRUE(cache->fallback_namespaces_[0] == - Namespace( + AppCacheNamespace( APPCACHE_FALLBACK_NAMESPACE, MockHttpServer::GetMockUrl("files/fallback1"), MockHttpServer::GetMockUrl("files/fallback1a"), @@ -3301,7 +3277,7 @@ class AppCacheUpdateJobTest : public testing::Test, expected = 1; ASSERT_EQ(expected, cache->fallback_namespaces_.size()); EXPECT_TRUE(cache->fallback_namespaces_[0] == - Namespace( + AppCacheNamespace( APPCACHE_FALLBACK_NAMESPACE, MockHttpServer::GetMockUrl("files/fallback1"), MockHttpServer::GetMockUrl("files/explicit1"), @@ -3309,7 +3285,7 @@ class AppCacheUpdateJobTest : public testing::Test, EXPECT_EQ(expected, cache->online_whitelist_namespaces_.size()); EXPECT_TRUE(cache->online_whitelist_namespaces_[0] == - Namespace( + AppCacheNamespace( APPCACHE_NETWORK_NAMESPACE, MockHttpServer::GetMockUrl("files/online1"), GURL(), false)); @@ -3498,7 +3474,7 @@ TEST_F(AppCacheUpdateJobTest, AlreadyDownloading) { EXPECT_EQ(expected, events[1].first.size()); EXPECT_EQ(host.host_id(), events[1].first[0]); - EXPECT_EQ(appcache::APPCACHE_DOWNLOADING_EVENT, events[1].second); + EXPECT_EQ(APPCACHE_DOWNLOADING_EVENT, events[1].second); EXPECT_EQ(AppCacheGroup::DOWNLOADING, group->update_status()); } diff --git a/content/browser/appcache/appcache_url_request_job.cc b/content/browser/appcache/appcache_url_request_job.cc new file mode 100644 index 0000000..b12a5b6 --- /dev/null +++ b/content/browser/appcache/appcache_url_request_job.cc @@ -0,0 +1,449 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/appcache/appcache_url_request_job.h" + +#include <vector> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/command_line.h" +#include "base/compiler_specific.h" +#include "base/message_loop/message_loop.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "content/browser/appcache/appcache.h" +#include "content/browser/appcache/appcache_group.h" +#include "content/browser/appcache/appcache_histograms.h" +#include "content/browser/appcache/appcache_host.h" +#include "content/browser/appcache/appcache_service_impl.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" +#include "net/base/net_log.h" +#include "net/http/http_request_headers.h" +#include "net/http/http_response_headers.h" +#include "net/http/http_util.h" +#include "net/url_request/url_request.h" +#include "net/url_request/url_request_status.h" + +namespace content { + +AppCacheURLRequestJob::AppCacheURLRequestJob( + net::URLRequest* request, + net::NetworkDelegate* network_delegate, + AppCacheStorage* storage, + AppCacheHost* host, + bool is_main_resource) + : net::URLRequestJob(request, network_delegate), + host_(host), + storage_(storage), + has_been_started_(false), has_been_killed_(false), + delivery_type_(AWAITING_DELIVERY_ORDERS), + group_id_(0), cache_id_(kAppCacheNoCacheId), is_fallback_(false), + is_main_resource_(is_main_resource), + cache_entry_not_found_(false), + weak_factory_(this) { + DCHECK(storage_); +} + +void AppCacheURLRequestJob::DeliverAppCachedResponse( + const GURL& manifest_url, int64 group_id, int64 cache_id, + const AppCacheEntry& entry, bool is_fallback) { + DCHECK(!has_delivery_orders()); + DCHECK(entry.has_response_id()); + delivery_type_ = APPCACHED_DELIVERY; + manifest_url_ = manifest_url; + group_id_ = group_id; + cache_id_ = cache_id; + entry_ = entry; + is_fallback_ = is_fallback; + MaybeBeginDelivery(); +} + +void AppCacheURLRequestJob::DeliverNetworkResponse() { + DCHECK(!has_delivery_orders()); + delivery_type_ = NETWORK_DELIVERY; + storage_ = NULL; // not needed + MaybeBeginDelivery(); +} + +void AppCacheURLRequestJob::DeliverErrorResponse() { + DCHECK(!has_delivery_orders()); + delivery_type_ = ERROR_DELIVERY; + storage_ = NULL; // not needed + MaybeBeginDelivery(); +} + +void AppCacheURLRequestJob::MaybeBeginDelivery() { + if (has_been_started() && has_delivery_orders()) { + // Start asynchronously so that all error reporting and data + // callbacks happen as they would for network requests. + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&AppCacheURLRequestJob::BeginDelivery, + weak_factory_.GetWeakPtr())); + } +} + +void AppCacheURLRequestJob::BeginDelivery() { + DCHECK(has_delivery_orders() && has_been_started()); + + if (has_been_killed()) + return; + + switch (delivery_type_) { + case NETWORK_DELIVERY: + AppCacheHistograms::AddNetworkJobStartDelaySample( + base::TimeTicks::Now() - start_time_tick_); + // To fallthru to the network, we restart the request which will + // cause a new job to be created to retrieve the resource from the + // network. Our caller is responsible for arranging to not re-intercept + // the same request. + NotifyRestartRequired(); + break; + + case ERROR_DELIVERY: + AppCacheHistograms::AddErrorJobStartDelaySample( + base::TimeTicks::Now() - start_time_tick_); + request()->net_log().AddEvent( + net::NetLog::TYPE_APPCACHE_DELIVERING_ERROR_RESPONSE); + NotifyStartError(net::URLRequestStatus(net::URLRequestStatus::FAILED, + net::ERR_FAILED)); + break; + + case APPCACHED_DELIVERY: + if (entry_.IsExecutable()) { + BeginExecutableHandlerDelivery(); + return; + } + AppCacheHistograms::AddAppCacheJobStartDelaySample( + base::TimeTicks::Now() - start_time_tick_); + request()->net_log().AddEvent( + is_fallback_ ? + net::NetLog::TYPE_APPCACHE_DELIVERING_FALLBACK_RESPONSE : + net::NetLog::TYPE_APPCACHE_DELIVERING_CACHED_RESPONSE); + storage_->LoadResponseInfo( + manifest_url_, group_id_, entry_.response_id(), this); + break; + + default: + NOTREACHED(); + break; + } +} + +void AppCacheURLRequestJob::BeginExecutableHandlerDelivery() { + DCHECK(CommandLine::ForCurrentProcess()-> + HasSwitch(kEnableExecutableHandlers)); + if (!storage_->service()->handler_factory()) { + BeginErrorDelivery("missing handler factory"); + return; + } + + request()->net_log().AddEvent( + net::NetLog::TYPE_APPCACHE_DELIVERING_EXECUTABLE_RESPONSE); + + // We defer job delivery until the executable handler is spun up and + // provides a response. The sequence goes like this... + // + // 1. First we load the cache. + // 2. Then if the handler is not spun up, we load the script resource which + // is needed to spin it up. + // 3. Then we ask then we ask the handler to compute a response. + // 4. Finally we deilver that response to the caller. + storage_->LoadCache(cache_id_, this); +} + +void AppCacheURLRequestJob::OnCacheLoaded(AppCache* cache, int64 cache_id) { + DCHECK_EQ(cache_id_, cache_id); + DCHECK(!has_been_killed()); + + if (!cache) { + BeginErrorDelivery("cache load failed"); + return; + } + + // Keep references to ensure they don't go out of scope until job completion. + cache_ = cache; + group_ = cache->owning_group(); + + // If the handler is spun up, ask it to compute a response. + AppCacheExecutableHandler* handler = + cache->GetExecutableHandler(entry_.response_id()); + if (handler) { + InvokeExecutableHandler(handler); + return; + } + + // Handler is not spun up yet, load the script resource to do that. + // NOTE: This is not ideal since multiple jobs may be doing this, + // concurrently but close enough for now, the first to load the script + // will win. + + // Read the script data, truncating if its too large. + // NOTE: we just issue one read and don't bother chaining if the resource + // is very (very) large, close enough for now. + const int64 kLimit = 500 * 1000; + handler_source_buffer_ = new net::GrowableIOBuffer(); + handler_source_buffer_->SetCapacity(kLimit); + handler_source_reader_.reset(storage_->CreateResponseReader( + manifest_url_, group_id_, entry_.response_id())); + handler_source_reader_->ReadData( + handler_source_buffer_.get(), + kLimit, + base::Bind(&AppCacheURLRequestJob::OnExecutableSourceLoaded, + base::Unretained(this))); +} + +void AppCacheURLRequestJob::OnExecutableSourceLoaded(int result) { + DCHECK(!has_been_killed()); + handler_source_reader_.reset(); + if (result < 0) { + BeginErrorDelivery("script source load failed"); + return; + } + + handler_source_buffer_->SetCapacity(result); // Free up some memory. + + AppCacheExecutableHandler* handler = cache_->GetOrCreateExecutableHandler( + entry_.response_id(), handler_source_buffer_.get()); + handler_source_buffer_ = NULL; // not needed anymore + if (handler) { + InvokeExecutableHandler(handler); + return; + } + + BeginErrorDelivery("factory failed to produce a handler"); +} + +void AppCacheURLRequestJob::InvokeExecutableHandler( + AppCacheExecutableHandler* handler) { + handler->HandleRequest( + request(), + base::Bind(&AppCacheURLRequestJob::OnExecutableResponseCallback, + weak_factory_.GetWeakPtr())); +} + +void AppCacheURLRequestJob::OnExecutableResponseCallback( + const AppCacheExecutableHandler::Response& response) { + DCHECK(!has_been_killed()); + if (response.use_network) { + delivery_type_ = NETWORK_DELIVERY; + storage_ = NULL; + BeginDelivery(); + return; + } + + if (!response.cached_resource_url.is_empty()) { + AppCacheEntry* entry_ptr = cache_->GetEntry(response.cached_resource_url); + if (entry_ptr && !entry_ptr->IsExecutable()) { + entry_ = *entry_ptr; + BeginDelivery(); + return; + } + } + + if (!response.redirect_url.is_empty()) { + // TODO(michaeln): playback a redirect + // response_headers_(new HttpResponseHeaders(response_headers)), + // fallthru for now to deliver an error + } + + // Otherwise, return an error. + BeginErrorDelivery("handler returned an invalid response"); +} + +void AppCacheURLRequestJob::BeginErrorDelivery(const char* message) { + if (host_) + host_->frontend()->OnLogMessage(host_->host_id(), APPCACHE_LOG_ERROR, + message); + delivery_type_ = ERROR_DELIVERY; + storage_ = NULL; + BeginDelivery(); +} + +AppCacheURLRequestJob::~AppCacheURLRequestJob() { + if (storage_) + storage_->CancelDelegateCallbacks(this); +} + +void AppCacheURLRequestJob::OnResponseInfoLoaded( + AppCacheResponseInfo* response_info, int64 response_id) { + DCHECK(is_delivering_appcache_response()); + scoped_refptr<AppCacheURLRequestJob> protect(this); + if (response_info) { + info_ = response_info; + reader_.reset(storage_->CreateResponseReader( + manifest_url_, group_id_, entry_.response_id())); + + if (is_range_request()) + SetupRangeResponse(); + + NotifyHeadersComplete(); + } else { + if (storage_->service()->storage() == storage_) { + // A resource that is expected to be in the appcache is missing. + // See http://code.google.com/p/chromium/issues/detail?id=50657 + // Instead of failing the request, we restart the request. The retry + // attempt will fallthru to the network instead of trying to load + // from the appcache. + storage_->service()->CheckAppCacheResponse(manifest_url_, cache_id_, + entry_.response_id()); + AppCacheHistograms::CountResponseRetrieval( + false, is_main_resource_, manifest_url_.GetOrigin()); + } + cache_entry_not_found_ = true; + NotifyRestartRequired(); + } +} + +const net::HttpResponseInfo* AppCacheURLRequestJob::http_info() const { + if (!info_.get()) + return NULL; + if (range_response_info_) + return range_response_info_.get(); + return info_->http_response_info(); +} + +void AppCacheURLRequestJob::SetupRangeResponse() { + DCHECK(is_range_request() && info_.get() && reader_.get() && + is_delivering_appcache_response()); + int resource_size = static_cast<int>(info_->response_data_size()); + if (resource_size < 0 || !range_requested_.ComputeBounds(resource_size)) { + range_requested_ = net::HttpByteRange(); + return; + } + + DCHECK(range_requested_.IsValid()); + int offset = static_cast<int>(range_requested_.first_byte_position()); + int length = static_cast<int>(range_requested_.last_byte_position() - + range_requested_.first_byte_position() + 1); + + // Tell the reader about the range to read. + reader_->SetReadRange(offset, length); + + // Make a copy of the full response headers and fix them up + // for the range we'll be returning. + range_response_info_.reset( + new net::HttpResponseInfo(*info_->http_response_info())); + net::HttpResponseHeaders* headers = range_response_info_->headers.get(); + headers->UpdateWithNewRange( + range_requested_, resource_size, true /* replace status line */); +} + +void AppCacheURLRequestJob::OnReadComplete(int result) { + DCHECK(is_delivering_appcache_response()); + if (result == 0) { + NotifyDone(net::URLRequestStatus()); + AppCacheHistograms::CountResponseRetrieval( + true, is_main_resource_, manifest_url_.GetOrigin()); + } else if (result < 0) { + if (storage_->service()->storage() == storage_) { + storage_->service()->CheckAppCacheResponse(manifest_url_, cache_id_, + entry_.response_id()); + } + NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED, result)); + AppCacheHistograms::CountResponseRetrieval( + false, is_main_resource_, manifest_url_.GetOrigin()); + } else { + SetStatus(net::URLRequestStatus()); // Clear the IO_PENDING status + } + NotifyReadComplete(result); +} + +// net::URLRequestJob overrides ------------------------------------------------ + +void AppCacheURLRequestJob::Start() { + DCHECK(!has_been_started()); + has_been_started_ = true; + start_time_tick_ = base::TimeTicks::Now(); + MaybeBeginDelivery(); +} + +void AppCacheURLRequestJob::Kill() { + if (!has_been_killed_) { + has_been_killed_ = true; + reader_.reset(); + handler_source_reader_.reset(); + if (storage_) { + storage_->CancelDelegateCallbacks(this); + storage_ = NULL; + } + host_ = NULL; + info_ = NULL; + cache_ = NULL; + group_ = NULL; + range_response_info_.reset(); + net::URLRequestJob::Kill(); + weak_factory_.InvalidateWeakPtrs(); + } +} + +net::LoadState AppCacheURLRequestJob::GetLoadState() const { + if (!has_been_started()) + return net::LOAD_STATE_IDLE; + if (!has_delivery_orders()) + return net::LOAD_STATE_WAITING_FOR_APPCACHE; + if (delivery_type_ != APPCACHED_DELIVERY) + return net::LOAD_STATE_IDLE; + if (!info_.get()) + return net::LOAD_STATE_WAITING_FOR_APPCACHE; + if (reader_.get() && reader_->IsReadPending()) + return net::LOAD_STATE_READING_RESPONSE; + return net::LOAD_STATE_IDLE; +} + +bool AppCacheURLRequestJob::GetMimeType(std::string* mime_type) const { + if (!http_info()) + return false; + return http_info()->headers->GetMimeType(mime_type); +} + +bool AppCacheURLRequestJob::GetCharset(std::string* charset) { + if (!http_info()) + return false; + return http_info()->headers->GetCharset(charset); +} + +void AppCacheURLRequestJob::GetResponseInfo(net::HttpResponseInfo* info) { + if (!http_info()) + return; + *info = *http_info(); +} + +int AppCacheURLRequestJob::GetResponseCode() const { + if (!http_info()) + return -1; + return http_info()->headers->response_code(); +} + +bool AppCacheURLRequestJob::ReadRawData(net::IOBuffer* buf, int buf_size, + int *bytes_read) { + DCHECK(is_delivering_appcache_response()); + DCHECK_NE(buf_size, 0); + DCHECK(bytes_read); + DCHECK(!reader_->IsReadPending()); + reader_->ReadData( + buf, buf_size, base::Bind(&AppCacheURLRequestJob::OnReadComplete, + base::Unretained(this))); + SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING, 0)); + return false; +} + +void AppCacheURLRequestJob::SetExtraRequestHeaders( + const net::HttpRequestHeaders& headers) { + std::string value; + std::vector<net::HttpByteRange> ranges; + if (!headers.GetHeader(net::HttpRequestHeaders::kRange, &value) || + !net::HttpUtil::ParseRangeHeader(value, &ranges)) { + return; + } + + // If multiple ranges are requested, we play dumb and + // return the entire response with 200 OK. + if (ranges.size() == 1U) + range_requested_ = ranges[0]; +} + +} // namespace content diff --git a/content/browser/appcache/appcache_url_request_job.h b/content/browser/appcache/appcache_url_request_job.h new file mode 100644 index 0000000..4b9bbcd --- /dev/null +++ b/content/browser/appcache/appcache_url_request_job.h @@ -0,0 +1,182 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_APPCACHE_APPCACHE_URL_REQUEST_JOB_H_ +#define CONTENT_BROWSER_APPCACHE_APPCACHE_URL_REQUEST_JOB_H_ + +#include <string> + +#include "base/memory/weak_ptr.h" +#include "content/browser/appcache/appcache_entry.h" +#include "content/browser/appcache/appcache_executable_handler.h" +#include "content/browser/appcache/appcache_response.h" +#include "content/browser/appcache/appcache_storage.h" +#include "content/common/content_export.h" +#include "net/http/http_byte_range.h" +#include "net/url_request/url_request_job.h" + +namespace content { +class AppCacheRequestHandlerTest; +class AppCacheURLRequestJobTest; +} + +namespace net { +class GrowableIOBuffer; +}; + +namespace content { + +class AppCacheHost; + +// A net::URLRequestJob derivative that knows how to return a response stored +// in the appcache. +class CONTENT_EXPORT AppCacheURLRequestJob + : public net::URLRequestJob, + public AppCacheStorage::Delegate { + public: + AppCacheURLRequestJob(net::URLRequest* request, + net::NetworkDelegate* network_delegate, + AppCacheStorage* storage, + AppCacheHost* host, + bool is_main_resource); + + // Informs the job of what response it should deliver. Only one of these + // methods should be called, and only once per job. A job will sit idle and + // wait indefinitely until one of the deliver methods is called. + void DeliverAppCachedResponse(const GURL& manifest_url, int64 group_id, + int64 cache_id, const AppCacheEntry& entry, + bool is_fallback); + void DeliverNetworkResponse(); + void DeliverErrorResponse(); + + bool is_waiting() const { + return delivery_type_ == AWAITING_DELIVERY_ORDERS; + } + + bool is_delivering_appcache_response() const { + return delivery_type_ == APPCACHED_DELIVERY; + } + + bool is_delivering_network_response() const { + return delivery_type_ == NETWORK_DELIVERY; + } + + bool is_delivering_error_response() const { + return delivery_type_ == ERROR_DELIVERY; + } + + // Accessors for the info about the appcached response, if any, + // that this job has been instructed to deliver. These are only + // valid to call if is_delivering_appcache_response. + const GURL& manifest_url() const { return manifest_url_; } + int64 group_id() const { return group_id_; } + int64 cache_id() const { return cache_id_; } + const AppCacheEntry& entry() const { return entry_; } + + // net::URLRequestJob's Kill method is made public so the users of this + // class in the appcache namespace can call it. + virtual void Kill() OVERRIDE; + + // Returns true if the job has been started by the net library. + bool has_been_started() const { + return has_been_started_; + } + + // Returns true if the job has been killed. + bool has_been_killed() const { + return has_been_killed_; + } + + // Returns true if the cache entry was not found in the disk cache. + bool cache_entry_not_found() const { + return cache_entry_not_found_; + } + + protected: + virtual ~AppCacheURLRequestJob(); + + private: + friend class content::AppCacheRequestHandlerTest; + friend class content::AppCacheURLRequestJobTest; + + enum DeliveryType { + AWAITING_DELIVERY_ORDERS, + APPCACHED_DELIVERY, + NETWORK_DELIVERY, + ERROR_DELIVERY + }; + + // Returns true if one of the Deliver methods has been called. + bool has_delivery_orders() const { + return !is_waiting(); + } + + void MaybeBeginDelivery(); + void BeginDelivery(); + + // For executable response handling. + void BeginExecutableHandlerDelivery(); + void OnExecutableSourceLoaded(int result); + void InvokeExecutableHandler(AppCacheExecutableHandler* handler); + void OnExecutableResponseCallback( + const AppCacheExecutableHandler::Response& response); + void BeginErrorDelivery(const char* message); + + // AppCacheStorage::Delegate methods + virtual void OnResponseInfoLoaded( + AppCacheResponseInfo* response_info, int64 response_id) OVERRIDE; + virtual void OnCacheLoaded(AppCache* cache, int64 cache_id) OVERRIDE; + + const net::HttpResponseInfo* http_info() const; + bool is_range_request() const { return range_requested_.IsValid(); } + void SetupRangeResponse(); + + // AppCacheResponseReader completion callback + void OnReadComplete(int result); + + // net::URLRequestJob methods, see url_request_job.h for doc comments + virtual void Start() OVERRIDE; + virtual net::LoadState GetLoadState() const OVERRIDE; + virtual bool GetCharset(std::string* charset) OVERRIDE; + virtual void GetResponseInfo(net::HttpResponseInfo* info) OVERRIDE; + virtual bool ReadRawData(net::IOBuffer* buf, + int buf_size, + int *bytes_read) OVERRIDE; + + // Sets extra request headers for Job types that support request headers. + // This is how we get informed of range-requests. + virtual void SetExtraRequestHeaders( + const net::HttpRequestHeaders& headers) OVERRIDE; + + // FilterContext methods + virtual bool GetMimeType(std::string* mime_type) const OVERRIDE; + virtual int GetResponseCode() const OVERRIDE; + + AppCacheHost* host_; + AppCacheStorage* storage_; + base::TimeTicks start_time_tick_; + bool has_been_started_; + bool has_been_killed_; + DeliveryType delivery_type_; + GURL manifest_url_; + int64 group_id_; + int64 cache_id_; + AppCacheEntry entry_; + bool is_fallback_; + bool is_main_resource_; // Used for histogram logging. + bool cache_entry_not_found_; + scoped_refptr<AppCacheResponseInfo> info_; + scoped_refptr<net::GrowableIOBuffer> handler_source_buffer_; + scoped_ptr<AppCacheResponseReader> handler_source_reader_; + net::HttpByteRange range_requested_; + scoped_ptr<net::HttpResponseInfo> range_response_info_; + scoped_ptr<AppCacheResponseReader> reader_; + scoped_refptr<AppCache> cache_; + scoped_refptr<AppCacheGroup> group_; + base::WeakPtrFactory<AppCacheURLRequestJob> weak_factory_; +}; + +} // namespace content + +#endif // CONTENT_BROWSER_APPCACHE_APPCACHE_REQUEST_HANDLER_H_ diff --git a/content/browser/appcache/appcache_url_request_job_unittest.cc b/content/browser/appcache/appcache_url_request_job_unittest.cc index edaa07e..7c70eed 100644 --- a/content/browser/appcache/appcache_url_request_job_unittest.cc +++ b/content/browser/appcache/appcache_url_request_job_unittest.cc @@ -12,6 +12,8 @@ #include "base/pickle.h" #include "base/synchronization/waitable_event.h" #include "base/threading/thread.h" +#include "content/browser/appcache/appcache_response.h" +#include "content/browser/appcache/appcache_url_request_job.h" #include "content/browser/appcache/mock_appcache_service.h" #include "net/base/io_buffer.h" #include "net/base/net_errors.h" @@ -22,17 +24,7 @@ #include "net/url_request/url_request_error_job.h" #include "net/url_request/url_request_job_factory.h" #include "testing/gtest/include/gtest/gtest.h" -#include "webkit/browser/appcache/appcache_response.h" -#include "webkit/browser/appcache/appcache_url_request_job.h" - -using appcache::AppCacheEntry; -using appcache::AppCacheStorage; -using appcache::AppCacheResponseInfo; -using appcache::AppCacheResponseReader; -using appcache::AppCacheResponseWriter; -using appcache::AppCacheURLRequestJob; -using appcache::HttpResponseInfoIOBuffer; -using appcache::kAppCacheNoCacheId; + using net::IOBuffer; using net::WrappedIOBuffer; diff --git a/content/browser/appcache/appcache_working_set.cc b/content/browser/appcache/appcache_working_set.cc new file mode 100644 index 0000000..4394097 --- /dev/null +++ b/content/browser/appcache/appcache_working_set.cc @@ -0,0 +1,80 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/appcache/appcache_working_set.h" + +#include "base/logging.h" +#include "content/browser/appcache/appcache.h" +#include "content/browser/appcache/appcache_group.h" +#include "content/browser/appcache/appcache_response.h" + +namespace content { + +AppCacheWorkingSet::AppCacheWorkingSet() : is_disabled_(false) {} + +AppCacheWorkingSet::~AppCacheWorkingSet() { + DCHECK(caches_.empty()); + DCHECK(groups_.empty()); + DCHECK(groups_by_origin_.empty()); +} + +void AppCacheWorkingSet::Disable() { + if (is_disabled_) + return; + is_disabled_ = true; + caches_.clear(); + groups_.clear(); + groups_by_origin_.clear(); + response_infos_.clear(); +} + +void AppCacheWorkingSet::AddCache(AppCache* cache) { + if (is_disabled_) + return; + DCHECK(cache->cache_id() != kAppCacheNoCacheId); + int64 cache_id = cache->cache_id(); + DCHECK(caches_.find(cache_id) == caches_.end()); + caches_.insert(CacheMap::value_type(cache_id, cache)); +} + +void AppCacheWorkingSet::RemoveCache(AppCache* cache) { + caches_.erase(cache->cache_id()); +} + +void AppCacheWorkingSet::AddGroup(AppCacheGroup* group) { + if (is_disabled_) + return; + const GURL& url = group->manifest_url(); + DCHECK(groups_.find(url) == groups_.end()); + groups_.insert(GroupMap::value_type(url, group)); + groups_by_origin_[url.GetOrigin()].insert(GroupMap::value_type(url, group)); +} + +void AppCacheWorkingSet::RemoveGroup(AppCacheGroup* group) { + const GURL& url = group->manifest_url(); + groups_.erase(url); + + GURL origin_url = url.GetOrigin(); + GroupMap* groups_in_origin = GetMutableGroupsInOrigin(origin_url); + if (groups_in_origin) { + groups_in_origin->erase(url); + if (groups_in_origin->empty()) + groups_by_origin_.erase(origin_url); + } +} + +void AppCacheWorkingSet::AddResponseInfo(AppCacheResponseInfo* info) { + if (is_disabled_) + return; + DCHECK(info->response_id() != kAppCacheNoResponseId); + int64 response_id = info->response_id(); + DCHECK(response_infos_.find(response_id) == response_infos_.end()); + response_infos_.insert(ResponseInfoMap::value_type(response_id, info)); +} + +void AppCacheWorkingSet::RemoveResponseInfo(AppCacheResponseInfo* info) { + response_infos_.erase(info->response_id()); +} + +} // namespace diff --git a/content/browser/appcache/appcache_working_set.h b/content/browser/appcache/appcache_working_set.h new file mode 100644 index 0000000..5b9d00a --- /dev/null +++ b/content/browser/appcache/appcache_working_set.h @@ -0,0 +1,76 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_APPCACHE_APPCACHE_WORKING_SET_H_ +#define CONTENT_BROWSER_APPCACHE_APPCACHE_WORKING_SET_H_ + +#include <map> + +#include "base/containers/hash_tables.h" +#include "content/common/content_export.h" +#include "url/gurl.h" + +namespace content { + +class AppCache; +class AppCacheGroup; +class AppCacheResponseInfo; + +// Represents the working set of appcache object instances +// currently in memory. +class CONTENT_EXPORT AppCacheWorkingSet { + public: + typedef std::map<GURL, AppCacheGroup*> GroupMap; + + AppCacheWorkingSet(); + ~AppCacheWorkingSet(); + + void Disable(); + bool is_disabled() const { return is_disabled_; } + + void AddCache(AppCache* cache); + void RemoveCache(AppCache* cache); + AppCache* GetCache(int64 id) { + CacheMap::iterator it = caches_.find(id); + return (it != caches_.end()) ? it->second : NULL; + } + + void AddGroup(AppCacheGroup* group); + void RemoveGroup(AppCacheGroup* group); + AppCacheGroup* GetGroup(const GURL& manifest_url) { + GroupMap::iterator it = groups_.find(manifest_url); + return (it != groups_.end()) ? it->second : NULL; + } + + const GroupMap* GetGroupsInOrigin(const GURL& origin_url) { + return GetMutableGroupsInOrigin(origin_url); + } + + void AddResponseInfo(AppCacheResponseInfo* response_info); + void RemoveResponseInfo(AppCacheResponseInfo* response_info); + AppCacheResponseInfo* GetResponseInfo(int64 id) { + ResponseInfoMap::iterator it = response_infos_.find(id); + return (it != response_infos_.end()) ? it->second : NULL; + } + + private: + typedef base::hash_map<int64, AppCache*> CacheMap; + typedef std::map<GURL, GroupMap> GroupsByOriginMap; + typedef base::hash_map<int64, AppCacheResponseInfo*> ResponseInfoMap; + + GroupMap* GetMutableGroupsInOrigin(const GURL& origin_url) { + GroupsByOriginMap::iterator it = groups_by_origin_.find(origin_url); + return (it != groups_by_origin_.end()) ? &it->second : NULL; + } + + CacheMap caches_; + GroupMap groups_; + GroupsByOriginMap groups_by_origin_; // origin -> (manifest -> group) + ResponseInfoMap response_infos_; + bool is_disabled_; +}; + +} // namespace content + +#endif // CONTENT_BROWSER_APPCACHE_APPCACHE_WORKING_SET_H_ diff --git a/content/browser/appcache/chrome_appcache_service.cc b/content/browser/appcache/chrome_appcache_service.cc index c72c218..8d686e8 100644 --- a/content/browser/appcache/chrome_appcache_service.cc +++ b/content/browser/appcache/chrome_appcache_service.cc @@ -5,12 +5,12 @@ #include "content/browser/appcache/chrome_appcache_service.h" #include "base/files/file_path.h" +#include "content/browser/appcache/appcache_storage_impl.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/content_browser_client.h" #include "content/public/browser/resource_context.h" #include "net/base/net_errors.h" #include "net/url_request/url_request_context_getter.h" -#include "webkit/browser/appcache/appcache_storage_impl.h" #include "webkit/browser/quota/quota_manager.h" namespace content { diff --git a/content/browser/appcache/chrome_appcache_service.h b/content/browser/appcache/chrome_appcache_service.h index 2f33d9b..8bc54d4 100644 --- a/content/browser/appcache/chrome_appcache_service.h +++ b/content/browser/appcache/chrome_appcache_service.h @@ -8,9 +8,9 @@ #include "base/compiler_specific.h" #include "base/memory/ref_counted.h" #include "base/sequenced_task_runner_helpers.h" +#include "content/browser/appcache/appcache_policy.h" +#include "content/browser/appcache/appcache_service_impl.h" #include "content/common/content_export.h" -#include "webkit/browser/appcache/appcache_policy.h" -#include "webkit/browser/appcache/appcache_service_impl.h" #include "webkit/browser/quota/special_storage_policy.h" namespace base { @@ -40,8 +40,8 @@ struct ChromeAppCacheServiceDeleter; class CONTENT_EXPORT ChromeAppCacheService : public base::RefCountedThreadSafe<ChromeAppCacheService, ChromeAppCacheServiceDeleter>, - NON_EXPORTED_BASE(public appcache::AppCacheServiceImpl), - NON_EXPORTED_BASE(public appcache::AppCachePolicy) { + NON_EXPORTED_BASE(public AppCacheServiceImpl), + NON_EXPORTED_BASE(public AppCachePolicy) { public: explicit ChromeAppCacheService(quota::QuotaManagerProxy* proxy); diff --git a/content/browser/appcache/chrome_appcache_service_unittest.cc b/content/browser/appcache/chrome_appcache_service_unittest.cc index 1800d0b..98bf68a 100644 --- a/content/browser/appcache/chrome_appcache_service_unittest.cc +++ b/content/browser/appcache/chrome_appcache_service_unittest.cc @@ -7,6 +7,8 @@ #include "base/files/scoped_temp_dir.h" #include "base/memory/ref_counted.h" #include "base/message_loop/message_loop.h" +#include "content/browser/appcache/appcache_database.h" +#include "content/browser/appcache/appcache_storage_impl.h" #include "content/browser/appcache/chrome_appcache_service.h" #include "content/browser/browser_thread_impl.h" #include "content/public/browser/resource_context.h" @@ -15,8 +17,6 @@ #include "content/test/appcache_test_helper.h" #include "net/url_request/url_request_context_getter.h" #include "testing/gtest/include/gtest/gtest.h" -#include "webkit/browser/appcache/appcache_database.h" -#include "webkit/browser/appcache/appcache_storage_impl.h" #include <set> @@ -116,8 +116,8 @@ ChromeAppCacheServiceTest::CreateAppCacheServiceImpl( // Steps needed to initialize the storage of AppCache data. message_loop_.RunUntilIdle(); if (init_storage) { - appcache::AppCacheStorageImpl* storage = - static_cast<appcache::AppCacheStorageImpl*>( + AppCacheStorageImpl* storage = + static_cast<AppCacheStorageImpl*>( appcache_service->storage()); storage->database_->db_connection(); storage->disk_cache(); diff --git a/content/browser/appcache/manifest_parser.cc b/content/browser/appcache/manifest_parser.cc new file mode 100644 index 0000000..fb63f22 --- /dev/null +++ b/content/browser/appcache/manifest_parser.cc @@ -0,0 +1,382 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// This is a port of ManifestParser.cc from WebKit/WebCore/loader/appcache. + +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "content/browser/appcache/manifest_parser.h" + +#include "base/command_line.h" +#include "base/i18n/icu_string_conversions.h" +#include "base/logging.h" +#include "base/strings/utf_string_conversions.h" +#include "url/gurl.h" + +namespace content { + +namespace { + +// Helper function used to identify 'isPattern' annotations. +bool HasPatternMatchingAnnotation(const wchar_t* line_p, + const wchar_t* line_end) { + // Skip whitespace separating the resource url from the annotation. + // Note: trailing whitespace has already been trimmed from the line. + while (line_p < line_end && (*line_p == '\t' || *line_p == ' ')) + ++line_p; + if (line_p == line_end) + return false; + std::wstring annotation(line_p, line_end - line_p); + return annotation == L"isPattern"; +} + +} + +enum Mode { + EXPLICIT, + INTERCEPT, + FALLBACK, + ONLINE_WHITELIST, + UNKNOWN_MODE, +}; + +enum InterceptVerb { + RETURN, + EXECUTE, + UNKNOWN_VERB, +}; + +Manifest::Manifest() + : online_whitelist_all(false), + did_ignore_intercept_namespaces(false) { +} + +Manifest::~Manifest() {} + +bool ParseManifest(const GURL& manifest_url, const char* data, int length, + ParseMode parse_mode, Manifest& manifest) { + // This is an implementation of the parsing algorithm specified in + // the HTML5 offline web application docs: + // http://www.w3.org/TR/html5/offline.html + // Do not modify it without consulting those docs. + // Though you might be tempted to convert these wstrings to UTF-8 or + // base::string16, this implementation seems simpler given the constraints. + + const wchar_t kSignature[] = L"CACHE MANIFEST"; + const size_t kSignatureLength = arraysize(kSignature) - 1; + const wchar_t kChromiumSignature[] = L"CHROMIUM CACHE MANIFEST"; + const size_t kChromiumSignatureLength = arraysize(kChromiumSignature) - 1; + + DCHECK(manifest.explicit_urls.empty()); + DCHECK(manifest.fallback_namespaces.empty()); + DCHECK(manifest.online_whitelist_namespaces.empty()); + DCHECK(!manifest.online_whitelist_all); + DCHECK(!manifest.did_ignore_intercept_namespaces); + + Mode mode = EXPLICIT; + + std::wstring data_string; + // TODO(jennb): cannot do UTF8ToWide(data, length, &data_string); + // until UTF8ToWide uses 0xFFFD Unicode replacement character. + base::CodepageToWide(std::string(data, length), base::kCodepageUTF8, + base::OnStringConversionError::SUBSTITUTE, &data_string); + const wchar_t* p = data_string.c_str(); + const wchar_t* end = p + data_string.length(); + + // Look for the magic signature: "^\xFEFF?CACHE MANIFEST[ \t]?" + // Example: "CACHE MANIFEST #comment" is a valid signature. + // Example: "CACHE MANIFEST;V2" is not. + + // When the input data starts with a UTF-8 Byte-Order-Mark + // (0xEF, 0xBB, 0xBF), the UTF8ToWide() function converts it to a + // Unicode BOM (U+FEFF). Skip a converted Unicode BOM if it exists. + int bom_offset = 0; + if (!data_string.empty() && data_string[0] == 0xFEFF) { + bom_offset = 1; + ++p; + } + + if (p >= end) + return false; + + // Check for a supported signature and skip p past it. + if (0 == data_string.compare(bom_offset, kSignatureLength, + kSignature)) { + p += kSignatureLength; + } else if (0 == data_string.compare(bom_offset, kChromiumSignatureLength, + kChromiumSignature)) { + p += kChromiumSignatureLength; + } else { + return false; + } + + // Character after "CACHE MANIFEST" must be whitespace. + if (p < end && *p != ' ' && *p != '\t' && *p != '\n' && *p != '\r') + return false; + + // Skip to the end of the line. + while (p < end && *p != '\r' && *p != '\n') + ++p; + + while (1) { + // Skip whitespace + while (p < end && (*p == '\n' || *p == '\r' || *p == ' ' || *p == '\t')) + ++p; + + if (p == end) + break; + + const wchar_t* line_start = p; + + // Find the end of the line + while (p < end && *p != '\r' && *p != '\n') + ++p; + + // Check if we have a comment + if (*line_start == '#') + continue; + + // Get rid of trailing whitespace + const wchar_t* tmp = p - 1; + while (tmp > line_start && (*tmp == ' ' || *tmp == '\t')) + --tmp; + + std::wstring line(line_start, tmp - line_start + 1); + + if (line == L"CACHE:") { + mode = EXPLICIT; + } else if (line == L"FALLBACK:") { + mode = FALLBACK; + } else if (line == L"NETWORK:") { + mode = ONLINE_WHITELIST; + } else if (line == L"CHROMIUM-INTERCEPT:") { + mode = INTERCEPT; + } else if (*(line.end() - 1) == ':') { + mode = UNKNOWN_MODE; + } else if (mode == UNKNOWN_MODE) { + continue; + } else if (line == L"*" && mode == ONLINE_WHITELIST) { + manifest.online_whitelist_all = true; + continue; + } else if (mode == EXPLICIT || mode == ONLINE_WHITELIST) { + const wchar_t *line_p = line.c_str(); + const wchar_t *line_end = line_p + line.length(); + + // Look for whitespace separating the URL from subsequent ignored tokens. + while (line_p < line_end && *line_p != '\t' && *line_p != ' ') + ++line_p; + + base::string16 url16; + base::WideToUTF16(line.c_str(), line_p - line.c_str(), &url16); + GURL url = manifest_url.Resolve(url16); + if (!url.is_valid()) + continue; + if (url.has_ref()) { + GURL::Replacements replacements; + replacements.ClearRef(); + url = url.ReplaceComponents(replacements); + } + + // Scheme component must be the same as the manifest URL's. + if (url.scheme() != manifest_url.scheme()) { + continue; + } + + // See http://code.google.com/p/chromium/issues/detail?id=69594 + // We willfully violate the HTML5 spec at this point in order + // to support the appcaching of cross-origin HTTPS resources. + // Per the spec, EXPLICIT cross-origin HTTS resources should be + // ignored here. We've opted for a milder constraint and allow + // caching unless the resource has a "no-store" header. That + // condition is enforced in AppCacheUpdateJob. + + if (mode == EXPLICIT) { + manifest.explicit_urls.insert(url.spec()); + } else { + bool is_pattern = HasPatternMatchingAnnotation(line_p, line_end); + manifest.online_whitelist_namespaces.push_back( + AppCacheNamespace(APPCACHE_NETWORK_NAMESPACE, url, GURL(), + is_pattern)); + } + } else if (mode == INTERCEPT) { + if (parse_mode != PARSE_MANIFEST_ALLOWING_INTERCEPTS) { + manifest.did_ignore_intercept_namespaces = true; + continue; + } + + // Lines of the form, + // <urlnamespace> <intercept_type> <targeturl> + const wchar_t* line_p = line.c_str(); + const wchar_t* line_end = line_p + line.length(); + + // Look for first whitespace separating the url namespace from + // the intercept type. + while (line_p < line_end && *line_p != '\t' && *line_p != ' ') + ++line_p; + + if (line_p == line_end) + continue; // There was no whitespace separating the URLs. + + base::string16 namespace_url16; + base::WideToUTF16(line.c_str(), line_p - line.c_str(), &namespace_url16); + GURL namespace_url = manifest_url.Resolve(namespace_url16); + if (!namespace_url.is_valid()) + continue; + if (namespace_url.has_ref()) { + GURL::Replacements replacements; + replacements.ClearRef(); + namespace_url = namespace_url.ReplaceComponents(replacements); + } + + // The namespace URL must have the same scheme, host and port + // as the manifest's URL. + if (manifest_url.GetOrigin() != namespace_url.GetOrigin()) + continue; + + // Skip whitespace separating namespace from the type. + while (line_p < line_end && (*line_p == '\t' || *line_p == ' ')) + ++line_p; + + // Look for whitespace separating the type from the target url. + const wchar_t* type_start = line_p; + while (line_p < line_end && *line_p != '\t' && *line_p != ' ') + ++line_p; + + // Look for a type value we understand, otherwise skip the line. + InterceptVerb verb = UNKNOWN_VERB; + std::wstring type(type_start, line_p - type_start); + if (type == L"return") { + verb = RETURN; + } else if (type == L"execute" && + CommandLine::ForCurrentProcess()->HasSwitch( + kEnableExecutableHandlers)) { + verb = EXECUTE; + } + if (verb == UNKNOWN_VERB) + continue; + + // Skip whitespace separating type from the target_url. + while (line_p < line_end && (*line_p == '\t' || *line_p == ' ')) + ++line_p; + + // Look for whitespace separating the URL from subsequent ignored tokens. + const wchar_t* target_url_start = line_p; + while (line_p < line_end && *line_p != '\t' && *line_p != ' ') + ++line_p; + + base::string16 target_url16; + base::WideToUTF16(target_url_start, line_p - target_url_start, + &target_url16); + GURL target_url = manifest_url.Resolve(target_url16); + if (!target_url.is_valid()) + continue; + + if (target_url.has_ref()) { + GURL::Replacements replacements; + replacements.ClearRef(); + target_url = target_url.ReplaceComponents(replacements); + } + if (manifest_url.GetOrigin() != target_url.GetOrigin()) + continue; + + bool is_pattern = HasPatternMatchingAnnotation(line_p, line_end); + manifest.intercept_namespaces.push_back( + AppCacheNamespace(APPCACHE_INTERCEPT_NAMESPACE, namespace_url, + target_url, is_pattern, verb == EXECUTE)); + } else if (mode == FALLBACK) { + const wchar_t* line_p = line.c_str(); + const wchar_t* line_end = line_p + line.length(); + + // Look for whitespace separating the two URLs + while (line_p < line_end && *line_p != '\t' && *line_p != ' ') + ++line_p; + + if (line_p == line_end) { + // There was no whitespace separating the URLs. + continue; + } + + base::string16 namespace_url16; + base::WideToUTF16(line.c_str(), line_p - line.c_str(), &namespace_url16); + GURL namespace_url = manifest_url.Resolve(namespace_url16); + if (!namespace_url.is_valid()) + continue; + if (namespace_url.has_ref()) { + GURL::Replacements replacements; + replacements.ClearRef(); + namespace_url = namespace_url.ReplaceComponents(replacements); + } + + // Fallback namespace URL must have the same scheme, host and port + // as the manifest's URL. + if (manifest_url.GetOrigin() != namespace_url.GetOrigin()) { + continue; + } + + // Skip whitespace separating fallback namespace from URL. + while (line_p < line_end && (*line_p == '\t' || *line_p == ' ')) + ++line_p; + + // Look for whitespace separating the URL from subsequent ignored tokens. + const wchar_t* fallback_start = line_p; + while (line_p < line_end && *line_p != '\t' && *line_p != ' ') + ++line_p; + + base::string16 fallback_url16; + base::WideToUTF16(fallback_start, line_p - fallback_start, + &fallback_url16); + GURL fallback_url = manifest_url.Resolve(fallback_url16); + if (!fallback_url.is_valid()) + continue; + if (fallback_url.has_ref()) { + GURL::Replacements replacements; + replacements.ClearRef(); + fallback_url = fallback_url.ReplaceComponents(replacements); + } + + // Fallback entry URL must have the same scheme, host and port + // as the manifest's URL. + if (manifest_url.GetOrigin() != fallback_url.GetOrigin()) { + continue; + } + + bool is_pattern = HasPatternMatchingAnnotation(line_p, line_end); + + // Store regardless of duplicate namespace URL. Only first match + // will ever be used. + manifest.fallback_namespaces.push_back( + AppCacheNamespace(APPCACHE_FALLBACK_NAMESPACE, namespace_url, + fallback_url, is_pattern)); + } else { + NOTREACHED(); + } + } + + return true; +} + +} // namespace content diff --git a/content/browser/appcache/manifest_parser.h b/content/browser/appcache/manifest_parser.h new file mode 100644 index 0000000..4deb599 --- /dev/null +++ b/content/browser/appcache/manifest_parser.h @@ -0,0 +1,72 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// This is a port of ManifestParser.h from WebKit/WebCore/loader/appcache. + +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CONTENT_BROWSER_APPCACHE_MANIFEST_PARSER_H_ +#define CONTENT_BROWSER_APPCACHE_MANIFEST_PARSER_H_ + +#include <string> +#include <vector> + +#include "base/containers/hash_tables.h" +#include "content/common/appcache_interfaces.h" +#include "content/common/content_export.h" + +class GURL; + +namespace content { + +struct CONTENT_EXPORT Manifest { + Manifest(); + ~Manifest(); + + base::hash_set<std::string> explicit_urls; + AppCacheNamespaceVector intercept_namespaces; + AppCacheNamespaceVector fallback_namespaces; + AppCacheNamespaceVector online_whitelist_namespaces; + bool online_whitelist_all; + bool did_ignore_intercept_namespaces; +}; + +enum ParseMode { + PARSE_MANIFEST_PER_STANDARD, + PARSE_MANIFEST_ALLOWING_INTERCEPTS +}; + +CONTENT_EXPORT bool ParseManifest( + const GURL& manifest_url, + const char* data, + int length, + ParseMode parse_mode, + Manifest& manifest); + +} // namespace content + +#endif // CONTENT_BROWSER_APPCACHE_MANIFEST_PARSER_H_ diff --git a/content/browser/appcache/manifest_parser_unittest.cc b/content/browser/appcache/manifest_parser_unittest.cc index 8a44376..caefca37 100644 --- a/content/browser/appcache/manifest_parser_unittest.cc +++ b/content/browser/appcache/manifest_parser_unittest.cc @@ -4,17 +4,9 @@ #include <string> +#include "content/browser/appcache/manifest_parser.h" #include "testing/gtest/include/gtest/gtest.h" #include "url/gurl.h" -#include "webkit/browser/appcache/manifest_parser.h" - -using appcache::Manifest; -using appcache::NamespaceVector; -using appcache::APPCACHE_FALLBACK_NAMESPACE; -using appcache::APPCACHE_INTERCEPT_NAMESPACE; -using appcache::APPCACHE_NETWORK_NAMESPACE; -using appcache::PARSE_MANIFEST_ALLOWING_INTERCEPTS; -using appcache::PARSE_MANIFEST_PER_STANDARD; namespace content { @@ -167,7 +159,7 @@ TEST(AppCacheManifestParserTest, WhitelistUrls) { EXPECT_TRUE(manifest.intercept_namespaces.empty()); EXPECT_FALSE(manifest.online_whitelist_all); - const NamespaceVector& online = manifest.online_whitelist_namespaces; + const AppCacheNamespaceVector& online = manifest.online_whitelist_namespaces; const size_t kExpected = 6; ASSERT_EQ(kExpected, online.size()); EXPECT_EQ(APPCACHE_NETWORK_NAMESPACE, online[0].type); @@ -213,7 +205,7 @@ TEST(AppCacheManifestParserTest, FallbackUrls) { EXPECT_TRUE(manifest.online_whitelist_namespaces.empty()); EXPECT_FALSE(manifest.online_whitelist_all); - const NamespaceVector& fallbacks = manifest.fallback_namespaces; + const AppCacheNamespaceVector& fallbacks = manifest.fallback_namespaces; const size_t kExpected = 5; ASSERT_EQ(kExpected, fallbacks.size()); EXPECT_EQ(APPCACHE_FALLBACK_NAMESPACE, fallbacks[0].type); @@ -264,7 +256,7 @@ TEST(AppCacheManifestParserTest, FallbackUrlsWithPort) { EXPECT_TRUE(manifest.online_whitelist_namespaces.empty()); EXPECT_FALSE(manifest.online_whitelist_all); - const NamespaceVector& fallbacks = manifest.fallback_namespaces; + const AppCacheNamespaceVector& fallbacks = manifest.fallback_namespaces; const size_t kExpected = 3; ASSERT_EQ(kExpected, fallbacks.size()); EXPECT_EQ(APPCACHE_FALLBACK_NAMESPACE, fallbacks[0].type); @@ -308,7 +300,7 @@ TEST(AppCacheManifestParserTest, InterceptUrls) { EXPECT_TRUE(manifest.online_whitelist_namespaces.empty()); EXPECT_FALSE(manifest.online_whitelist_all); - const NamespaceVector& intercepts = manifest.intercept_namespaces; + const AppCacheNamespaceVector& intercepts = manifest.intercept_namespaces; const size_t kExpected = 3; ASSERT_EQ(kExpected, intercepts.size()); EXPECT_EQ(APPCACHE_INTERCEPT_NAMESPACE, intercepts[0].type); @@ -372,7 +364,7 @@ TEST(AppCacheManifestParserTest, ComboUrls) { EXPECT_TRUE(urls.find("http://combo.com:99/explicit-2") != urls.end()); EXPECT_TRUE(urls.find("http://www.diff.com/explicit-3") != urls.end()); - const NamespaceVector& online = manifest.online_whitelist_namespaces; + const AppCacheNamespaceVector& online = manifest.online_whitelist_namespaces; expected = 4; ASSERT_EQ(expected, online.size()); EXPECT_EQ(GURL("http://combo.com/whitelist-1"), @@ -384,7 +376,7 @@ TEST(AppCacheManifestParserTest, ComboUrls) { EXPECT_EQ(GURL("http://combo.com:99/whitelist-4"), online[3].namespace_url); - const NamespaceVector& fallbacks = manifest.fallback_namespaces; + const AppCacheNamespaceVector& fallbacks = manifest.fallback_namespaces; expected = 2; ASSERT_EQ(expected, fallbacks.size()); EXPECT_EQ(APPCACHE_FALLBACK_NAMESPACE, fallbacks[0].type); diff --git a/content/browser/appcache/mock_appcache_policy.h b/content/browser/appcache/mock_appcache_policy.h index bc75764..93de476 100644 --- a/content/browser/appcache/mock_appcache_policy.h +++ b/content/browser/appcache/mock_appcache_policy.h @@ -6,12 +6,12 @@ #define CONTENT_BROWSER_APPCACHE_MOCK_APPCACHE_POLICY_H_ #include "base/compiler_specific.h" +#include "content/browser/appcache/appcache_policy.h" #include "url/gurl.h" -#include "webkit/browser/appcache/appcache_policy.h" namespace content { -class MockAppCachePolicy : public appcache::AppCachePolicy { +class MockAppCachePolicy : public AppCachePolicy { public: MockAppCachePolicy(); virtual ~MockAppCachePolicy(); diff --git a/content/browser/appcache/mock_appcache_service.h b/content/browser/appcache/mock_appcache_service.h index 691736b..ad86c54 100644 --- a/content/browser/appcache/mock_appcache_service.h +++ b/content/browser/appcache/mock_appcache_service.h @@ -6,12 +6,10 @@ #define CONTENT_BROWSER_APPCACHE_MOCK_APPCACHE_SERVICE_H_ #include "base/compiler_specific.h" +#include "content/browser/appcache/appcache_service_impl.h" #include "content/browser/appcache/mock_appcache_storage.h" -#include "webkit/browser/appcache/appcache_service_impl.h" #include "webkit/browser/quota/quota_manager.h" -using appcache::AppCacheServiceImpl; - namespace content { // For use by unit tests. diff --git a/content/browser/appcache/mock_appcache_storage.cc b/content/browser/appcache/mock_appcache_storage.cc index ea9673d..91630f7 100644 --- a/content/browser/appcache/mock_appcache_storage.cc +++ b/content/browser/appcache/mock_appcache_storage.cc @@ -9,11 +9,11 @@ #include "base/memory/ref_counted.h" #include "base/message_loop/message_loop.h" #include "base/stl_util.h" -#include "webkit/browser/appcache/appcache.h" -#include "webkit/browser/appcache/appcache_entry.h" -#include "webkit/browser/appcache/appcache_group.h" -#include "webkit/browser/appcache/appcache_response.h" -#include "webkit/browser/appcache/appcache_service_impl.h" +#include "content/browser/appcache/appcache.h" +#include "content/browser/appcache/appcache_entry.h" +#include "content/browser/appcache/appcache_group.h" +#include "content/browser/appcache/appcache_response.h" +#include "content/browser/appcache/appcache_service_impl.h" // This is a quick and easy 'mock' implementation of the storage interface // that doesn't put anything to disk. @@ -25,13 +25,6 @@ // happen with a real disk-backed storage impl that involves IO on a // background thread. -using appcache::AppCacheResponseWriter; -using appcache::AppCacheServiceImpl; -using appcache::APPCACHE_FALLBACK_NAMESPACE; -using appcache::APPCACHE_INTERCEPT_NAMESPACE; -using appcache::kAppCacheNoCacheId; -using appcache::AppCacheNamespaceType; - namespace content { MockAppCacheStorage::MockAppCacheStorage(AppCacheServiceImpl* service) diff --git a/content/browser/appcache/mock_appcache_storage.h b/content/browser/appcache/mock_appcache_storage.h index 5d31748..2c4f45583a 100644 --- a/content/browser/appcache/mock_appcache_storage.h +++ b/content/browser/appcache/mock_appcache_storage.h @@ -14,22 +14,11 @@ #include "base/gtest_prod_util.h" #include "base/memory/scoped_ptr.h" #include "base/memory/weak_ptr.h" -#include "webkit/browser/appcache/appcache.h" -#include "webkit/browser/appcache/appcache_disk_cache.h" -#include "webkit/browser/appcache/appcache_group.h" -#include "webkit/browser/appcache/appcache_response.h" -#include "webkit/browser/appcache/appcache_storage.h" - -using appcache::AppCache; -using appcache::AppCacheDiskCache; -using appcache::AppCacheEntry; -using appcache::AppCacheGroup; -using appcache::AppCacheInfoCollection; -using appcache::AppCacheResponseReader; -using appcache::AppCacheResponseWriter; -using appcache::AppCacheServiceImpl; -using appcache::AppCacheStorage; -using appcache::kAppCacheNoCacheId; +#include "content/browser/appcache/appcache.h" +#include "content/browser/appcache/appcache_disk_cache.h" +#include "content/browser/appcache/appcache_group.h" +#include "content/browser/appcache/appcache_response.h" +#include "content/browser/appcache/appcache_storage.h" namespace content { FORWARD_DECLARE_TEST(AppCacheServiceImplTest, DeleteAppCachesForOrigin); diff --git a/content/browser/appcache/mock_appcache_storage_unittest.cc b/content/browser/appcache/mock_appcache_storage_unittest.cc index f1c9149..c41fe53 100644 --- a/content/browser/appcache/mock_appcache_storage_unittest.cc +++ b/content/browser/appcache/mock_appcache_storage_unittest.cc @@ -3,24 +3,12 @@ // found in the LICENSE file. #include "base/run_loop.h" +#include "content/browser/appcache/appcache.h" +#include "content/browser/appcache/appcache_group.h" +#include "content/browser/appcache/appcache_response.h" +#include "content/browser/appcache/appcache_storage.h" #include "content/browser/appcache/mock_appcache_service.h" #include "testing/gtest/include/gtest/gtest.h" -#include "webkit/browser/appcache/appcache.h" -#include "webkit/browser/appcache/appcache_group.h" -#include "webkit/browser/appcache/appcache_response.h" -#include "webkit/browser/appcache/appcache_storage.h" - -using appcache::AppCache; -using appcache::AppCacheEntry; -using appcache::AppCacheGroup; -using appcache::AppCacheStorage; -using appcache::APPCACHE_FALLBACK_NAMESPACE; -using appcache::APPCACHE_INTERCEPT_NAMESPACE; -using appcache::kAppCacheNoCacheId; -using appcache::kAppCacheNoResponseId; -using appcache::Manifest; -using appcache::Namespace; -using appcache::APPCACHE_NETWORK_NAMESPACE; namespace content { @@ -483,10 +471,10 @@ TEST_F(MockAppCacheStorageTest, BasicFindMainFallbackResponse) { Manifest manifest; manifest.fallback_namespaces.push_back( - Namespace(APPCACHE_FALLBACK_NAMESPACE, kFallbackNamespaceUrl1, + AppCacheNamespace(APPCACHE_FALLBACK_NAMESPACE, kFallbackNamespaceUrl1, kFallbackEntryUrl1, false)); manifest.fallback_namespaces.push_back( - Namespace(APPCACHE_FALLBACK_NAMESPACE, kFallbackNamespaceUrl2, + AppCacheNamespace(APPCACHE_FALLBACK_NAMESPACE, kFallbackNamespaceUrl2, kFallbackEntryUrl2, false)); scoped_refptr<AppCache> cache(new AppCache(service.storage(), kCacheId)); @@ -595,7 +583,7 @@ TEST_F(MockAppCacheStorageTest, FindMainResponseExclusions) { Manifest manifest; manifest.online_whitelist_namespaces.push_back( - Namespace(APPCACHE_NETWORK_NAMESPACE, kOnlineNamespaceUrl, + AppCacheNamespace(APPCACHE_NETWORK_NAMESPACE, kOnlineNamespaceUrl, GURL(), false)); scoped_refptr<AppCache> cache(new AppCache(service.storage(), kCacheId)); cache->InitializeWithManifest(&manifest); diff --git a/content/browser/appcache/view_appcache_internals_job.cc b/content/browser/appcache/view_appcache_internals_job.cc index 0cc64c3..3f8ac4f 100644 --- a/content/browser/appcache/view_appcache_internals_job.cc +++ b/content/browser/appcache/view_appcache_internals_job.cc @@ -17,6 +17,12 @@ #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" +#include "content/browser/appcache/appcache.h" +#include "content/browser/appcache/appcache_group.h" +#include "content/browser/appcache/appcache_policy.h" +#include "content/browser/appcache/appcache_response.h" +#include "content/browser/appcache/appcache_service_impl.h" +#include "content/browser/appcache/appcache_storage.h" #include "net/base/escape.h" #include "net/base/io_buffer.h" #include "net/base/net_errors.h" @@ -24,24 +30,6 @@ #include "net/url_request/url_request.h" #include "net/url_request/url_request_simple_job.h" #include "net/url_request/view_cache_helper.h" -#include "webkit/browser/appcache/appcache.h" -#include "webkit/browser/appcache/appcache_group.h" -#include "webkit/browser/appcache/appcache_policy.h" -#include "webkit/browser/appcache/appcache_response.h" -#include "webkit/browser/appcache/appcache_service_impl.h" -#include "webkit/browser/appcache/appcache_storage.h" - -using appcache::AppCacheGroup; -using appcache::AppCacheInfo; -using appcache::AppCacheInfoCollection; -using appcache::AppCacheInfoVector; -using appcache::AppCacheServiceImpl; -using appcache::AppCacheStorage; -using appcache::AppCacheStorageReference; -using appcache::AppCacheResourceInfo; -using appcache::AppCacheResourceInfoVector; -using appcache::AppCacheResponseInfo; -using appcache::AppCacheResponseReader; namespace content { namespace { diff --git a/content/browser/appcache/view_appcache_internals_job.h b/content/browser/appcache/view_appcache_internals_job.h index f11b68e..7fb2ea99 100644 --- a/content/browser/appcache/view_appcache_internals_job.h +++ b/content/browser/appcache/view_appcache_internals_job.h @@ -13,7 +13,7 @@ class URLRequest; class URLRequestJob; } -namespace appcache { +namespace content { class AppCacheServiceImpl; } @@ -24,12 +24,12 @@ class ViewAppCacheInternalsJobFactory { static net::URLRequestJob* CreateJobForRequest( net::URLRequest* request, net::NetworkDelegate* network_delegate, - appcache::AppCacheServiceImpl* service); + AppCacheServiceImpl* service); private: DISALLOW_IMPLICIT_CONSTRUCTORS(ViewAppCacheInternalsJobFactory); }; -} // namespace appcache +} // namespace content #endif // CONTENT_BROWSER_APPCACHE_VIEW_APPCACHE_INTERNALS_JOB_H_ diff --git a/content/browser/browser_context.cc b/content/browser/browser_context.cc index dbc5949..1562386 100644 --- a/content/browser/browser_context.cc +++ b/content/browser/browser_context.cc @@ -69,7 +69,7 @@ StoragePartition* GetStoragePartitionFromConfig( void SaveSessionStateOnIOThread( const scoped_refptr<net::URLRequestContextGetter>& context_getter, - appcache::AppCacheServiceImpl* appcache_service) { + AppCacheServiceImpl* appcache_service) { net::URLRequestContext* context = context_getter->GetURLRequestContext(); context->cookie_store()->GetCookieMonster()-> SetForceKeepSessionState(); @@ -236,7 +236,7 @@ void BrowserContext::SaveSessionState(BrowserContext* browser_context) { base::Bind( &SaveSessionStateOnIOThread, make_scoped_refptr(browser_context->GetRequestContext()), - static_cast<appcache::AppCacheServiceImpl*>( + static_cast<AppCacheServiceImpl*>( storage_partition->GetAppCacheService()))); } diff --git a/content/browser/cross_site_transfer_browsertest.cc b/content/browser/cross_site_transfer_browsertest.cc index fff180d..9d2d1cc 100644 --- a/content/browser/cross_site_transfer_browsertest.cc +++ b/content/browser/cross_site_transfer_browsertest.cc @@ -36,7 +36,7 @@ class TrackingResourceDispatcherHostDelegate virtual void RequestBeginning( net::URLRequest* request, ResourceContext* resource_context, - appcache::AppCacheService* appcache_service, + AppCacheService* appcache_service, ResourceType::Type resource_type, int child_id, int route_id, diff --git a/content/browser/loader/resource_dispatcher_host_impl.cc b/content/browser/loader/resource_dispatcher_host_impl.cc index d342ec3..561b215 100644 --- a/content/browser/loader/resource_dispatcher_host_impl.cc +++ b/content/browser/loader/resource_dispatcher_host_impl.cc @@ -54,6 +54,7 @@ #include "content/browser/streams/stream_registry.h" #include "content/browser/worker_host/worker_service_impl.h" #include "content/browser/web_contents/web_contents_impl.h" +#include "content/common/appcache_interfaces.h" #include "content/common/resource_messages.h" #include "content/common/resource_request_body.h" #include "content/common/ssl_status_serialization.h" @@ -94,7 +95,6 @@ #include "webkit/browser/blob/blob_url_request_job_factory.h" #include "webkit/browser/fileapi/file_permission_policy.h" #include "webkit/browser/fileapi/file_system_context.h" -#include "webkit/common/appcache/appcache_interfaces.h" #include "webkit/common/blob/shareable_file_reference.h" using base::Time; diff --git a/content/browser/loader/resource_dispatcher_host_unittest.cc b/content/browser/loader/resource_dispatcher_host_unittest.cc index a815032..18f9c30 100644 --- a/content/browser/loader/resource_dispatcher_host_unittest.cc +++ b/content/browser/loader/resource_dispatcher_host_unittest.cc @@ -24,6 +24,7 @@ #include "content/browser/loader/resource_message_filter.h" #include "content/browser/loader/resource_request_info_impl.h" #include "content/browser/worker_host/worker_service_impl.h" +#include "content/common/appcache_interfaces.h" #include "content/common/child_process_host_impl.h" #include "content/common/resource_messages.h" #include "content/common/view_messages.h" @@ -50,7 +51,6 @@ #include "net/url_request/url_request_test_job.h" #include "net/url_request/url_request_test_util.h" #include "testing/gtest/include/gtest/gtest.h" -#include "webkit/common/appcache/appcache_interfaces.h" #include "webkit/common/blob/shareable_file_reference.h" // TODO(eroman): Write unit tests for SafeBrowsing that exercise @@ -139,7 +139,7 @@ static ResourceHostMsg_Request CreateResourceRequest( request.origin_pid = 0; request.resource_type = type; request.request_context = 0; - request.appcache_host_id = appcache::kAppCacheNoHostId; + request.appcache_host_id = kAppCacheNoHostId; request.download_to_file = false; request.is_main_frame = true; request.parent_is_main_frame = false; @@ -662,7 +662,7 @@ class TestResourceDispatcherHostDelegate virtual void RequestBeginning( net::URLRequest* request, ResourceContext* resource_context, - appcache::AppCacheService* appcache_service, + AppCacheService* appcache_service, ResourceType::Type resource_type, int child_id, int route_id, diff --git a/content/browser/quota/mock_quota_manager_unittest.cc b/content/browser/quota/mock_quota_manager_unittest.cc index e1885c4..c62cf6b 100644 --- a/content/browser/quota/mock_quota_manager_unittest.cc +++ b/content/browser/quota/mock_quota_manager_unittest.cc @@ -223,4 +223,4 @@ TEST_F(MockQuotaManagerTest, ModifiedOrigins) { EXPECT_EQ(0UL, origins().count(kOrigin1)); EXPECT_EQ(1UL, origins().count(kOrigin2)); } -} // Namespace content +} // AppCacheNamespace content diff --git a/content/browser/service_worker/service_worker_disk_cache.cc b/content/browser/service_worker/service_worker_disk_cache.cc index a8ca52a..d31f00c 100644 --- a/content/browser/service_worker/service_worker_disk_cache.cc +++ b/content/browser/service_worker/service_worker_disk_cache.cc @@ -8,15 +8,12 @@ namespace content { ServiceWorkerResponseReader::ServiceWorkerResponseReader( int64 response_id, ServiceWorkerDiskCache* disk_cache) - : appcache::AppCacheResponseReader(response_id, 0, disk_cache) { + : AppCacheResponseReader(response_id, 0, disk_cache) { } ServiceWorkerResponseWriter::ServiceWorkerResponseWriter( int64 response_id, ServiceWorkerDiskCache* disk_cache) - : appcache::AppCacheResponseWriter(response_id, 0, disk_cache) { -} - -HttpResponseInfoIOBuffer::~HttpResponseInfoIOBuffer() { + : AppCacheResponseWriter(response_id, 0, disk_cache) { } } // namespace content diff --git a/content/browser/service_worker/service_worker_disk_cache.h b/content/browser/service_worker/service_worker_disk_cache.h index 8385012..7fa51ee 100644 --- a/content/browser/service_worker/service_worker_disk_cache.h +++ b/content/browser/service_worker/service_worker_disk_cache.h @@ -5,8 +5,8 @@ #ifndef CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_DISK_CACHE_H_ #define CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_DISK_CACHE_H_ +#include "content/browser/appcache/appcache_disk_cache.h" #include "content/common/content_export.h" -#include "webkit/browser/appcache/appcache_disk_cache.h" namespace content { @@ -17,11 +17,11 @@ namespace content { // resused classes to a more common location. class CONTENT_EXPORT ServiceWorkerDiskCache - : public appcache::AppCacheDiskCache { + : public AppCacheDiskCache { }; class CONTENT_EXPORT ServiceWorkerResponseReader - : public appcache::AppCacheResponseReader { + : public AppCacheResponseReader { protected: // Should only be constructed by the storage class. friend class ServiceWorkerStorage; @@ -31,7 +31,7 @@ class CONTENT_EXPORT ServiceWorkerResponseReader }; class CONTENT_EXPORT ServiceWorkerResponseWriter - : public appcache::AppCacheResponseWriter { + : public AppCacheResponseWriter { protected: // Should only be constructed by the storage class. friend class ServiceWorkerStorage; @@ -40,16 +40,6 @@ class CONTENT_EXPORT ServiceWorkerResponseWriter ServiceWorkerDiskCache* disk_cache); }; -struct CONTENT_EXPORT HttpResponseInfoIOBuffer - : public appcache::HttpResponseInfoIOBuffer { - public: - HttpResponseInfoIOBuffer() : appcache::HttpResponseInfoIOBuffer() {} - explicit HttpResponseInfoIOBuffer(net::HttpResponseInfo* info) - : appcache::HttpResponseInfoIOBuffer(info) {} - protected: - virtual ~HttpResponseInfoIOBuffer(); -}; - } // namespace content #endif // CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_DISK_CACHE_H_ diff --git a/content/browser/service_worker/service_worker_request_handler.cc b/content/browser/service_worker/service_worker_request_handler.cc index e3c5ae1..504a7a0d 100644 --- a/content/browser/service_worker/service_worker_request_handler.cc +++ b/content/browser/service_worker/service_worker_request_handler.cc @@ -40,13 +40,13 @@ class ServiceWorkerRequestInterceptor DISALLOW_COPY_AND_ASSIGN(ServiceWorkerRequestInterceptor); }; -bool IsMethodSupported(const std::string& method) { +bool IsMethodSupportedForAppCache(const std::string& method) { return (method == "GET") || (method == "HEAD"); } -bool IsSchemeAndMethodSupported(const net::URLRequest* request) { +bool IsSchemeAndMethodSupportedForAppCache(const net::URLRequest* request) { return request->url().SchemeIsHTTPOrHTTPS() && - IsMethodSupported(request->method()); + IsMethodSupportedForAppCache(request->method()); } } // namespace @@ -58,7 +58,7 @@ void ServiceWorkerRequestHandler::InitializeHandler( int process_id, int provider_id, ResourceType::Type resource_type) { - if (!IsSchemeAndMethodSupported(request)) { + if (!IsSchemeAndMethodSupportedForAppCache(request)) { return; } diff --git a/content/browser/storage_partition_impl_map.cc b/content/browser/storage_partition_impl_map.cc index fdfb34e..d1d2a55 100644 --- a/content/browser/storage_partition_impl_map.cc +++ b/content/browser/storage_partition_impl_map.cc @@ -40,7 +40,6 @@ #include "webkit/browser/fileapi/file_system_url_request_job_factory.h" #include "webkit/common/blob/blob_data.h" -using appcache::AppCacheServiceImpl; using fileapi::FileSystemContext; using webkit_blob::BlobStorageContext; diff --git a/content/browser/transition_browsertest.cc b/content/browser/transition_browsertest.cc index 5a6ae9e..79a12a5 100644 --- a/content/browser/transition_browsertest.cc +++ b/content/browser/transition_browsertest.cc @@ -41,7 +41,7 @@ class TransitionBrowserTestObserver virtual void RequestBeginning( net::URLRequest* request, ResourceContext* resource_context, - appcache::AppCacheService* appcache_service, + content::AppCacheService* appcache_service, ResourceType::Type resource_type, int child_id, int route_id, diff --git a/content/browser/webui/url_data_manager_backend.cc b/content/browser/webui/url_data_manager_backend.cc index 9f661af..369a4e6 100644 --- a/content/browser/webui/url_data_manager_backend.cc +++ b/content/browser/webui/url_data_manager_backend.cc @@ -44,8 +44,6 @@ #include "net/url_request/url_request_job_factory.h" #include "url/url_util.h" -using appcache::AppCacheServiceImpl; - namespace content { namespace { diff --git a/content/browser/webui/url_data_manager_backend.h b/content/browser/webui/url_data_manager_backend.h index a307cdb..03b3d72 100644 --- a/content/browser/webui/url_data_manager_backend.h +++ b/content/browser/webui/url_data_manager_backend.h @@ -18,7 +18,7 @@ class GURL; -namespace appcache { +namespace content { class AppCacheServiceImpl; } @@ -48,7 +48,7 @@ class URLDataManagerBackend : public base::SupportsUserData::Data { static net::URLRequestJobFactory::ProtocolHandler* CreateProtocolHandler( content::ResourceContext* resource_context, bool is_incognito, - appcache::AppCacheServiceImpl* appcache_service, + AppCacheServiceImpl* appcache_service, ChromeBlobStorageContext* blob_storage_context); // Adds a DataSource to the collection of data sources. diff --git a/content/child/appcache/appcache_backend_proxy.cc b/content/child/appcache/appcache_backend_proxy.cc index e1d96d6..bf31494 100644 --- a/content/child/appcache/appcache_backend_proxy.cc +++ b/content/child/appcache/appcache_backend_proxy.cc @@ -54,8 +54,8 @@ void AppCacheBackendProxy::MarkAsForeignEntry( cache_document_was_loaded_from)); } -appcache::AppCacheStatus AppCacheBackendProxy::GetStatus(int host_id) { - appcache::AppCacheStatus status = appcache::APPCACHE_STATUS_UNCACHED; +AppCacheStatus AppCacheBackendProxy::GetStatus(int host_id) { + AppCacheStatus status = APPCACHE_STATUS_UNCACHED; sender_->Send(new AppCacheHostMsg_GetStatus(host_id, &status)); return status; } @@ -73,7 +73,7 @@ bool AppCacheBackendProxy::SwapCache(int host_id) { } void AppCacheBackendProxy::GetResourceList( - int host_id, std::vector<appcache::AppCacheResourceInfo>* resource_infos) { + int host_id, std::vector<AppCacheResourceInfo>* resource_infos) { sender_->Send(new AppCacheHostMsg_GetResourceList(host_id, resource_infos)); } diff --git a/content/child/appcache/appcache_backend_proxy.h b/content/child/appcache/appcache_backend_proxy.h index 389f08c..98eed36 100644 --- a/content/child/appcache/appcache_backend_proxy.h +++ b/content/child/appcache/appcache_backend_proxy.h @@ -7,13 +7,13 @@ #include <vector> +#include "content/common/appcache_interfaces.h" #include "ipc/ipc_sender.h" -#include "webkit/common/appcache/appcache_interfaces.h" namespace content { // Sends appcache related messages to the main process. -class AppCacheBackendProxy : public appcache::AppCacheBackend { +class AppCacheBackendProxy : public AppCacheBackend { public: explicit AppCacheBackendProxy(IPC::Sender* sender) : sender_(sender) {} @@ -38,12 +38,12 @@ class AppCacheBackendProxy : public appcache::AppCacheBackend { int host_id, const GURL& document_url, int64 cache_document_was_loaded_from) OVERRIDE; - virtual appcache::AppCacheStatus GetStatus(int host_id) OVERRIDE; + virtual AppCacheStatus GetStatus(int host_id) OVERRIDE; virtual bool StartUpdate(int host_id) OVERRIDE; virtual bool SwapCache(int host_id) OVERRIDE; virtual void GetResourceList( int host_id, - std::vector<appcache::AppCacheResourceInfo>* resource_infos) OVERRIDE; + std::vector<AppCacheResourceInfo>* resource_infos) OVERRIDE; private: IPC::Sender* sender_; diff --git a/content/child/appcache/appcache_dispatcher.cc b/content/child/appcache/appcache_dispatcher.cc index 11e5b9d..02986b1 100644 --- a/content/child/appcache/appcache_dispatcher.cc +++ b/content/child/appcache/appcache_dispatcher.cc @@ -10,7 +10,7 @@ namespace content { AppCacheDispatcher::AppCacheDispatcher( IPC::Sender* sender, - appcache::AppCacheFrontend* frontend) + AppCacheFrontend* frontend) : backend_proxy_(sender), frontend_(frontend) {} @@ -32,17 +32,17 @@ bool AppCacheDispatcher::OnMessageReceived(const IPC::Message& msg) { } void AppCacheDispatcher::OnCacheSelected( - int host_id, const appcache::AppCacheInfo& info) { + int host_id, const AppCacheInfo& info) { frontend_->OnCacheSelected(host_id, info); } void AppCacheDispatcher::OnStatusChanged(const std::vector<int>& host_ids, - appcache::AppCacheStatus status) { + AppCacheStatus status) { frontend_->OnStatusChanged(host_ids, status); } void AppCacheDispatcher::OnEventRaised(const std::vector<int>& host_ids, - appcache::AppCacheEventID event_id) { + AppCacheEventID event_id) { frontend_->OnEventRaised(host_ids, event_id); } @@ -54,14 +54,14 @@ void AppCacheDispatcher::OnProgressEventRaised( void AppCacheDispatcher::OnErrorEventRaised( const std::vector<int>& host_ids, - const appcache::AppCacheErrorDetails& details) { + const AppCacheErrorDetails& details) { frontend_->OnErrorEventRaised(host_ids, details); } void AppCacheDispatcher::OnLogMessage( int host_id, int log_level, const std::string& message) { frontend_->OnLogMessage( - host_id, static_cast<appcache::AppCacheLogLevel>(log_level), message); + host_id, static_cast<AppCacheLogLevel>(log_level), message); } void AppCacheDispatcher::OnContentBlocked(int host_id, diff --git a/content/child/appcache/appcache_dispatcher.h b/content/child/appcache/appcache_dispatcher.h index 6b62727..82f4711 100644 --- a/content/child/appcache/appcache_dispatcher.h +++ b/content/child/appcache/appcache_dispatcher.h @@ -10,8 +10,8 @@ #include "base/memory/scoped_ptr.h" #include "content/child/appcache/appcache_backend_proxy.h" +#include "content/common/appcache_interfaces.h" #include "ipc/ipc_listener.h" -#include "webkit/common/appcache/appcache_interfaces.h" namespace content { @@ -22,7 +22,7 @@ namespace content { class AppCacheDispatcher : public IPC::Listener { public: AppCacheDispatcher(IPC::Sender* sender, - appcache::AppCacheFrontend* frontend); + AppCacheFrontend* frontend); virtual ~AppCacheDispatcher(); AppCacheBackendProxy* backend_proxy() { return &backend_proxy_; } @@ -32,20 +32,20 @@ class AppCacheDispatcher : public IPC::Listener { private: // Ipc message handlers - void OnCacheSelected(int host_id, const appcache::AppCacheInfo& info); + void OnCacheSelected(int host_id, const AppCacheInfo& info); void OnStatusChanged(const std::vector<int>& host_ids, - appcache::AppCacheStatus status); + AppCacheStatus status); void OnEventRaised(const std::vector<int>& host_ids, - appcache::AppCacheEventID event_id); + AppCacheEventID event_id); void OnProgressEventRaised(const std::vector<int>& host_ids, const GURL& url, int num_total, int num_complete); void OnErrorEventRaised(const std::vector<int>& host_ids, - const appcache::AppCacheErrorDetails& details); + const AppCacheErrorDetails& details); void OnLogMessage(int host_id, int log_level, const std::string& message); void OnContentBlocked(int host_id, const GURL& manifest_url); AppCacheBackendProxy backend_proxy_; - scoped_ptr<appcache::AppCacheFrontend> frontend_; + scoped_ptr<AppCacheFrontend> frontend_; }; } // namespace content diff --git a/content/child/appcache/appcache_frontend_impl.cc b/content/child/appcache/appcache_frontend_impl.cc index 1a33e0d..8ede37a 100644 --- a/content/child/appcache/appcache_frontend_impl.cc +++ b/content/child/appcache/appcache_frontend_impl.cc @@ -19,14 +19,14 @@ inline WebApplicationCacheHostImpl* GetHost(int id) { } void AppCacheFrontendImpl::OnCacheSelected(int host_id, - const appcache::AppCacheInfo& info) { + const AppCacheInfo& info) { WebApplicationCacheHostImpl* host = GetHost(host_id); if (host) host->OnCacheSelected(info); } void AppCacheFrontendImpl::OnStatusChanged(const std::vector<int>& host_ids, - appcache::AppCacheStatus status) { + AppCacheStatus status) { for (std::vector<int>::const_iterator i = host_ids.begin(); i != host_ids.end(); ++i) { WebApplicationCacheHostImpl* host = GetHost(*i); @@ -36,10 +36,10 @@ void AppCacheFrontendImpl::OnStatusChanged(const std::vector<int>& host_ids, } void AppCacheFrontendImpl::OnEventRaised(const std::vector<int>& host_ids, - appcache::AppCacheEventID event_id) { + AppCacheEventID event_id) { DCHECK(event_id != - appcache::APPCACHE_PROGRESS_EVENT); // See OnProgressEventRaised. - DCHECK(event_id != appcache::APPCACHE_ERROR_EVENT); // See OnErrorEventRaised. + APPCACHE_PROGRESS_EVENT); // See OnProgressEventRaised. + DCHECK(event_id != APPCACHE_ERROR_EVENT); // See OnErrorEventRaised. for (std::vector<int>::const_iterator i = host_ids.begin(); i != host_ids.end(); ++i) { WebApplicationCacheHostImpl* host = GetHost(*i); @@ -63,7 +63,7 @@ void AppCacheFrontendImpl::OnProgressEventRaised( void AppCacheFrontendImpl::OnErrorEventRaised( const std::vector<int>& host_ids, - const appcache::AppCacheErrorDetails& details) { + const AppCacheErrorDetails& details) { for (std::vector<int>::const_iterator i = host_ids.begin(); i != host_ids.end(); ++i) { WebApplicationCacheHostImpl* host = GetHost(*i); @@ -73,7 +73,7 @@ void AppCacheFrontendImpl::OnErrorEventRaised( } void AppCacheFrontendImpl::OnLogMessage(int host_id, - appcache::AppCacheLogLevel log_level, + AppCacheLogLevel log_level, const std::string& message) { WebApplicationCacheHostImpl* host = GetHost(host_id); if (host) @@ -90,67 +90,67 @@ void AppCacheFrontendImpl::OnContentBlocked(int host_id, // Ensure that enum values never get out of sync with the // ones declared for use within the WebKit api COMPILE_ASSERT((int)WebApplicationCacheHost::Uncached == - (int)appcache::APPCACHE_STATUS_UNCACHED, Uncached); + (int)APPCACHE_STATUS_UNCACHED, Uncached); COMPILE_ASSERT((int)WebApplicationCacheHost::Idle == - (int)appcache::APPCACHE_STATUS_IDLE, Idle); + (int)APPCACHE_STATUS_IDLE, Idle); COMPILE_ASSERT((int)WebApplicationCacheHost::Checking == - (int)appcache::APPCACHE_STATUS_CHECKING, Checking); + (int)APPCACHE_STATUS_CHECKING, Checking); COMPILE_ASSERT((int)WebApplicationCacheHost::Downloading == - (int)appcache::APPCACHE_STATUS_DOWNLOADING, Downloading); + (int)APPCACHE_STATUS_DOWNLOADING, Downloading); COMPILE_ASSERT((int)WebApplicationCacheHost::UpdateReady == - (int)appcache::APPCACHE_STATUS_UPDATE_READY, UpdateReady); + (int)APPCACHE_STATUS_UPDATE_READY, UpdateReady); COMPILE_ASSERT((int)WebApplicationCacheHost::Obsolete == - (int)appcache::APPCACHE_STATUS_OBSOLETE, Obsolete); + (int)APPCACHE_STATUS_OBSOLETE, Obsolete); COMPILE_ASSERT((int)WebApplicationCacheHost::CheckingEvent == - (int)appcache::APPCACHE_CHECKING_EVENT, CheckingEvent); + (int)APPCACHE_CHECKING_EVENT, CheckingEvent); COMPILE_ASSERT((int)WebApplicationCacheHost::ErrorEvent == - (int)appcache::APPCACHE_ERROR_EVENT, ErrorEvent); + (int)APPCACHE_ERROR_EVENT, ErrorEvent); COMPILE_ASSERT((int)WebApplicationCacheHost::NoUpdateEvent == - (int)appcache::APPCACHE_NO_UPDATE_EVENT, NoUpdateEvent); + (int)APPCACHE_NO_UPDATE_EVENT, NoUpdateEvent); COMPILE_ASSERT((int)WebApplicationCacheHost::DownloadingEvent == - (int)appcache::APPCACHE_DOWNLOADING_EVENT, DownloadingEvent); + (int)APPCACHE_DOWNLOADING_EVENT, DownloadingEvent); COMPILE_ASSERT((int)WebApplicationCacheHost::ProgressEvent == - (int)appcache::APPCACHE_PROGRESS_EVENT, ProgressEvent); + (int)APPCACHE_PROGRESS_EVENT, ProgressEvent); COMPILE_ASSERT((int)WebApplicationCacheHost::UpdateReadyEvent == - (int)appcache::APPCACHE_UPDATE_READY_EVENT, UpdateReadyEvent); + (int)APPCACHE_UPDATE_READY_EVENT, UpdateReadyEvent); COMPILE_ASSERT((int)WebApplicationCacheHost::CachedEvent == - (int)appcache::APPCACHE_CACHED_EVENT, CachedEvent); + (int)APPCACHE_CACHED_EVENT, CachedEvent); COMPILE_ASSERT((int)WebApplicationCacheHost::ObsoleteEvent == - (int)appcache::APPCACHE_OBSOLETE_EVENT, ObsoleteEvent); + (int)APPCACHE_OBSOLETE_EVENT, ObsoleteEvent); COMPILE_ASSERT((int)WebConsoleMessage::LevelDebug == - (int)appcache::APPCACHE_LOG_DEBUG, LevelDebug); + (int)APPCACHE_LOG_DEBUG, LevelDebug); COMPILE_ASSERT((int)WebConsoleMessage::LevelLog == - (int)appcache::APPCACHE_LOG_INFO, LevelLog); + (int)APPCACHE_LOG_INFO, LevelLog); COMPILE_ASSERT((int)WebConsoleMessage::LevelWarning == - (int)appcache::APPCACHE_LOG_WARNING, LevelWarning); + (int)APPCACHE_LOG_WARNING, LevelWarning); COMPILE_ASSERT((int)WebConsoleMessage::LevelError == - (int)appcache::APPCACHE_LOG_ERROR, LevelError); + (int)APPCACHE_LOG_ERROR, LevelError); COMPILE_ASSERT((int)WebApplicationCacheHost::ManifestError == - (int)appcache::APPCACHE_MANIFEST_ERROR, + (int)APPCACHE_MANIFEST_ERROR, ManifestError); COMPILE_ASSERT((int)WebApplicationCacheHost::SignatureError == - (int)appcache::APPCACHE_SIGNATURE_ERROR, + (int)APPCACHE_SIGNATURE_ERROR, SignatureError); COMPILE_ASSERT((int)WebApplicationCacheHost::ResourceError == - (int)appcache::APPCACHE_RESOURCE_ERROR, + (int)APPCACHE_RESOURCE_ERROR, ResourceError); COMPILE_ASSERT((int)WebApplicationCacheHost::ChangedError == - (int)appcache::APPCACHE_CHANGED_ERROR, + (int)APPCACHE_CHANGED_ERROR, ChangedError); COMPILE_ASSERT((int)WebApplicationCacheHost::AbortError == - (int)appcache::APPCACHE_ABORT_ERROR, + (int)APPCACHE_ABORT_ERROR, AbortError); COMPILE_ASSERT((int)WebApplicationCacheHost::QuotaError == - (int)appcache::APPCACHE_QUOTA_ERROR, + (int)APPCACHE_QUOTA_ERROR, QuotaError); COMPILE_ASSERT((int)WebApplicationCacheHost::PolicyError == - (int)appcache::APPCACHE_POLICY_ERROR, + (int)APPCACHE_POLICY_ERROR, PolicyError); COMPILE_ASSERT((int)WebApplicationCacheHost::UnknownError == - (int)appcache::APPCACHE_UNKNOWN_ERROR, + (int)APPCACHE_UNKNOWN_ERROR, UnknownError); } // namespace content diff --git a/content/child/appcache/appcache_frontend_impl.h b/content/child/appcache/appcache_frontend_impl.h index 90ac4fe..161f661 100644 --- a/content/child/appcache/appcache_frontend_impl.h +++ b/content/child/appcache/appcache_frontend_impl.h @@ -5,27 +5,27 @@ #ifndef CONTENT_CHILD_APPCACHE_APPCACHE_FRONTEND_IMPL_H_ #define CONTENT_CHILD_APPCACHE_APPCACHE_FRONTEND_IMPL_H_ -#include "webkit/common/appcache/appcache_interfaces.h" +#include "content/common/appcache_interfaces.h" namespace content { -class AppCacheFrontendImpl : public appcache::AppCacheFrontend { +class AppCacheFrontendImpl : public AppCacheFrontend { public: virtual void OnCacheSelected(int host_id, - const appcache::AppCacheInfo& info) OVERRIDE; + const AppCacheInfo& info) OVERRIDE; virtual void OnStatusChanged(const std::vector<int>& host_ids, - appcache::AppCacheStatus status) OVERRIDE; + AppCacheStatus status) OVERRIDE; virtual void OnEventRaised(const std::vector<int>& host_ids, - appcache::AppCacheEventID event_id) OVERRIDE; + AppCacheEventID event_id) OVERRIDE; virtual void OnProgressEventRaised(const std::vector<int>& host_ids, const GURL& url, int num_total, int num_complete) OVERRIDE; virtual void OnErrorEventRaised(const std::vector<int>& host_ids, - const appcache::AppCacheErrorDetails& details) + const AppCacheErrorDetails& details) OVERRIDE; virtual void OnLogMessage(int host_id, - appcache::AppCacheLogLevel log_level, + AppCacheLogLevel log_level, const std::string& message) OVERRIDE; virtual void OnContentBlocked(int host_id, const GURL& manifest_url) OVERRIDE; }; diff --git a/content/child/appcache/web_application_cache_host_impl.cc b/content/child/appcache/web_application_cache_host_impl.cc index 427262e..321f467 100644 --- a/content/child/appcache/web_application_cache_host_impl.cc +++ b/content/child/appcache/web_application_cache_host_impl.cc @@ -20,8 +20,6 @@ using blink::WebURLRequest; using blink::WebURL; using blink::WebURLResponse; using blink::WebVector; -using appcache::AppCacheBackend; -using appcache::AppCacheResourceInfo; namespace content { @@ -61,12 +59,12 @@ WebApplicationCacheHostImpl::WebApplicationCacheHostImpl( : client_(client), backend_(backend), host_id_(all_hosts()->Add(this)), - status_(appcache::APPCACHE_STATUS_UNCACHED), + status_(APPCACHE_STATUS_UNCACHED), is_scheme_supported_(false), is_get_method_(false), is_new_master_entry_(MAYBE), was_select_cache_called_(false) { - DCHECK(client && backend && (host_id_ != appcache::kAppCacheNoHostId)); + DCHECK(client && backend && (host_id_ != kAppCacheNoHostId)); backend_->RegisterHost(host_id_); } @@ -77,45 +75,45 @@ WebApplicationCacheHostImpl::~WebApplicationCacheHostImpl() { } void WebApplicationCacheHostImpl::OnCacheSelected( - const appcache::AppCacheInfo& info) { + const AppCacheInfo& info) { cache_info_ = info; client_->didChangeCacheAssociation(); } void WebApplicationCacheHostImpl::OnStatusChanged( - appcache::AppCacheStatus status) { + AppCacheStatus status) { // TODO(michaeln): delete me, not used } void WebApplicationCacheHostImpl::OnEventRaised( - appcache::AppCacheEventID event_id) { + AppCacheEventID event_id) { DCHECK(event_id != - appcache::APPCACHE_PROGRESS_EVENT); // See OnProgressEventRaised. - DCHECK(event_id != appcache::APPCACHE_ERROR_EVENT); // See OnErrorEventRaised. + APPCACHE_PROGRESS_EVENT); // See OnProgressEventRaised. + DCHECK(event_id != APPCACHE_ERROR_EVENT); // See OnErrorEventRaised. // Emit logging output prior to calling out to script as we can get // deleted within the script event handler. const char* kFormatString = "Application Cache %s event"; std::string message = base::StringPrintf(kFormatString, kEventNames[event_id]); - OnLogMessage(appcache::APPCACHE_LOG_INFO, message); + OnLogMessage(APPCACHE_LOG_INFO, message); switch (event_id) { - case appcache::APPCACHE_CHECKING_EVENT: - status_ = appcache::APPCACHE_STATUS_CHECKING; + case APPCACHE_CHECKING_EVENT: + status_ = APPCACHE_STATUS_CHECKING; break; - case appcache::APPCACHE_DOWNLOADING_EVENT: - status_ = appcache::APPCACHE_STATUS_DOWNLOADING; + case APPCACHE_DOWNLOADING_EVENT: + status_ = APPCACHE_STATUS_DOWNLOADING; break; - case appcache::APPCACHE_UPDATE_READY_EVENT: - status_ = appcache::APPCACHE_STATUS_UPDATE_READY; + case APPCACHE_UPDATE_READY_EVENT: + status_ = APPCACHE_STATUS_UPDATE_READY; break; - case appcache::APPCACHE_CACHED_EVENT: - case appcache::APPCACHE_NO_UPDATE_EVENT: - status_ = appcache::APPCACHE_STATUS_IDLE; + case APPCACHE_CACHED_EVENT: + case APPCACHE_NO_UPDATE_EVENT: + status_ = APPCACHE_STATUS_IDLE; break; - case appcache::APPCACHE_OBSOLETE_EVENT: - status_ = appcache::APPCACHE_STATUS_OBSOLETE; + case APPCACHE_OBSOLETE_EVENT: + status_ = APPCACHE_STATUS_OBSOLETE; break; default: NOTREACHED(); @@ -132,25 +130,25 @@ void WebApplicationCacheHostImpl::OnProgressEventRaised( const char* kFormatString = "Application Cache Progress event (%d of %d) %s"; std::string message = base::StringPrintf(kFormatString, num_complete, num_total, url.spec().c_str()); - OnLogMessage(appcache::APPCACHE_LOG_INFO, message); - status_ = appcache::APPCACHE_STATUS_DOWNLOADING; + OnLogMessage(APPCACHE_LOG_INFO, message); + status_ = APPCACHE_STATUS_DOWNLOADING; client_->notifyProgressEventListener(url, num_total, num_complete); } void WebApplicationCacheHostImpl::OnErrorEventRaised( - const appcache::AppCacheErrorDetails& details) { + const AppCacheErrorDetails& details) { // Emit logging output prior to calling out to script as we can get // deleted within the script event handler. const char* kFormatString = "Application Cache Error event: %s"; std::string full_message = base::StringPrintf(kFormatString, details.message.c_str()); - OnLogMessage(appcache::APPCACHE_LOG_ERROR, full_message); + OnLogMessage(APPCACHE_LOG_ERROR, full_message); - status_ = cache_info_.is_complete ? appcache::APPCACHE_STATUS_IDLE : - appcache::APPCACHE_STATUS_UNCACHED; + status_ = cache_info_.is_complete ? APPCACHE_STATUS_IDLE : + APPCACHE_STATUS_UNCACHED; if (details.is_cross_origin) { // Don't leak detailed information to script for cross-origin resources. - DCHECK_EQ(appcache::APPCACHE_RESOURCE_ERROR, details.reason); + DCHECK_EQ(APPCACHE_RESOURCE_ERROR, details.reason); client_->notifyErrorEventListener( static_cast<ErrorReason>(details.reason), details.url, 0, WebString()); } else { @@ -168,13 +166,13 @@ void WebApplicationCacheHostImpl::willStartMainResourceRequest( original_main_resource_url_ = ClearUrlRef(request.url()); std::string method = request.httpMethod().utf8(); - is_get_method_ = (method == appcache::kHttpGETMethod); + is_get_method_ = (method == kHttpGETMethod); DCHECK(method == StringToUpperASCII(method)); const WebApplicationCacheHostImpl* spawning_host_impl = static_cast<const WebApplicationCacheHostImpl*>(spawning_host); if (spawning_host_impl && (spawning_host_impl != this) && - (spawning_host_impl->status_ != appcache::APPCACHE_STATUS_UNCACHED)) { + (spawning_host_impl->status_ != APPCACHE_STATUS_UNCACHED)) { backend_->SetSpawningHostId(host_id_, spawning_host_impl->host_id()); } } @@ -189,8 +187,8 @@ void WebApplicationCacheHostImpl::selectCacheWithoutManifest() { return; was_select_cache_called_ = true; - status_ = (document_response_.appCacheID() == appcache::kAppCacheNoCacheId) ? - appcache::APPCACHE_STATUS_UNCACHED : appcache::APPCACHE_STATUS_CHECKING; + status_ = (document_response_.appCacheID() == kAppCacheNoCacheId) ? + APPCACHE_STATUS_UNCACHED : APPCACHE_STATUS_CHECKING; is_new_master_entry_ = NO; backend_->SelectCache(host_id_, document_url_, document_response_.appCacheID(), @@ -207,18 +205,18 @@ bool WebApplicationCacheHostImpl::selectCacheWithManifest( // 6.9.6 The application cache selection algorithm // Check for new 'master' entries. - if (document_response_.appCacheID() == appcache::kAppCacheNoCacheId) { + if (document_response_.appCacheID() == kAppCacheNoCacheId) { if (is_scheme_supported_ && is_get_method_ && (manifest_gurl.GetOrigin() == document_url_.GetOrigin())) { - status_ = appcache::APPCACHE_STATUS_CHECKING; + status_ = APPCACHE_STATUS_CHECKING; is_new_master_entry_ = YES; } else { - status_ = appcache::APPCACHE_STATUS_UNCACHED; + status_ = APPCACHE_STATUS_UNCACHED; is_new_master_entry_ = NO; manifest_gurl = GURL(); } backend_->SelectCache( - host_id_, document_url_, appcache::kAppCacheNoCacheId, manifest_gurl); + host_id_, document_url_, kAppCacheNoCacheId, manifest_gurl); return true; } @@ -230,11 +228,11 @@ bool WebApplicationCacheHostImpl::selectCacheWithManifest( if (document_manifest_gurl != manifest_gurl) { backend_->MarkAsForeignEntry(host_id_, document_url_, document_response_.appCacheID()); - status_ = appcache::APPCACHE_STATUS_UNCACHED; + status_ = APPCACHE_STATUS_UNCACHED; return false; // the navigation will be restarted } - status_ = appcache::APPCACHE_STATUS_CHECKING; + status_ = APPCACHE_STATUS_CHECKING; // Its a 'master' entry thats already in the cache. backend_->SelectCache(host_id_, document_url_, @@ -251,8 +249,8 @@ void WebApplicationCacheHostImpl::didReceiveResponseForMainResource( is_get_method_ = true; // A redirect was involved. original_main_resource_url_ = GURL(); - is_scheme_supported_ = appcache::IsSchemeSupported(document_url_); - if ((document_response_.appCacheID() != appcache::kAppCacheNoCacheId) || + is_scheme_supported_ = IsSchemeSupportedForAppCache(document_url_); + if ((document_response_.appCacheID() != kAppCacheNoCacheId) || !is_scheme_supported_ || !is_get_method_) is_new_master_entry_ = NO; } @@ -277,9 +275,9 @@ WebApplicationCacheHost::Status WebApplicationCacheHostImpl::status() { bool WebApplicationCacheHostImpl::startUpdate() { if (!backend_->StartUpdate(host_id_)) return false; - if (status_ == appcache::APPCACHE_STATUS_IDLE || - status_ == appcache::APPCACHE_STATUS_UPDATE_READY) - status_ = appcache::APPCACHE_STATUS_CHECKING; + if (status_ == APPCACHE_STATUS_IDLE || + status_ == APPCACHE_STATUS_UPDATE_READY) + status_ = APPCACHE_STATUS_CHECKING; else status_ = backend_->GetStatus(host_id_); return true; diff --git a/content/child/appcache/web_application_cache_host_impl.h b/content/child/appcache/web_application_cache_host_impl.h index 07ec2db..fe27be2 100644 --- a/content/child/appcache/web_application_cache_host_impl.h +++ b/content/child/appcache/web_application_cache_host_impl.h @@ -7,12 +7,12 @@ #include <string> +#include "content/common/appcache_interfaces.h" #include "third_party/WebKit/public/platform/WebApplicationCacheHost.h" #include "third_party/WebKit/public/platform/WebApplicationCacheHostClient.h" #include "third_party/WebKit/public/platform/WebURLResponse.h" #include "third_party/WebKit/public/platform/WebVector.h" #include "url/gurl.h" -#include "webkit/common/appcache/appcache_interfaces.h" namespace content { @@ -23,19 +23,19 @@ class WebApplicationCacheHostImpl static WebApplicationCacheHostImpl* FromId(int id); WebApplicationCacheHostImpl(blink::WebApplicationCacheHostClient* client, - appcache::AppCacheBackend* backend); + AppCacheBackend* backend); virtual ~WebApplicationCacheHostImpl(); int host_id() const { return host_id_; } - appcache::AppCacheBackend* backend() const { return backend_; } + AppCacheBackend* backend() const { return backend_; } blink::WebApplicationCacheHostClient* client() const { return client_; } - virtual void OnCacheSelected(const appcache::AppCacheInfo& info); - void OnStatusChanged(appcache::AppCacheStatus); - void OnEventRaised(appcache::AppCacheEventID); + virtual void OnCacheSelected(const AppCacheInfo& info); + void OnStatusChanged(AppCacheStatus); + void OnEventRaised(AppCacheEventID); void OnProgressEventRaised(const GURL& url, int num_total, int num_complete); - void OnErrorEventRaised(const appcache::AppCacheErrorDetails& details); - virtual void OnLogMessage(appcache::AppCacheLogLevel log_level, + void OnErrorEventRaised(const AppCacheErrorDetails& details); + virtual void OnLogMessage(AppCacheLogLevel log_level, const std::string& message) {} virtual void OnContentBlocked(const GURL& manifest_url) {} @@ -62,15 +62,15 @@ class WebApplicationCacheHostImpl }; blink::WebApplicationCacheHostClient* client_; - appcache::AppCacheBackend* backend_; + AppCacheBackend* backend_; int host_id_; - appcache::AppCacheStatus status_; + AppCacheStatus status_; blink::WebURLResponse document_response_; GURL document_url_; bool is_scheme_supported_; bool is_get_method_; IsNewMasterEntry is_new_master_entry_; - appcache::AppCacheInfo cache_info_; + AppCacheInfo cache_info_; GURL original_main_resource_url_; // Used to detect redirection. bool was_select_cache_called_; }; diff --git a/content/child/resource_dispatcher_unittest.cc b/content/child/resource_dispatcher_unittest.cc index 86dc17f..3e427ca 100644 --- a/content/child/resource_dispatcher_unittest.cc +++ b/content/child/resource_dispatcher_unittest.cc @@ -16,6 +16,7 @@ #include "content/child/request_extra_data.h" #include "content/child/request_info.h" #include "content/child/resource_dispatcher.h" +#include "content/common/appcache_interfaces.h" #include "content/common/resource_messages.h" #include "content/common/service_worker/service_worker_types.h" #include "content/public/child/request_peer.h" @@ -24,7 +25,6 @@ #include "net/http/http_response_headers.h" #include "testing/gtest/include/gtest/gtest.h" #include "webkit/child/resource_loader_bridge.h" -#include "webkit/common/appcache/appcache_interfaces.h" using webkit_glue::ResourceLoaderBridge; @@ -331,7 +331,7 @@ class ResourceDispatcherTest : public testing::Test, public IPC::Sender { request_info.load_flags = 0; request_info.requestor_pid = 0; request_info.request_type = ResourceType::SUB_RESOURCE; - request_info.appcache_host_id = appcache::kAppCacheNoHostId; + request_info.appcache_host_id = kAppCacheNoHostId; request_info.routing_id = 0; request_info.download_to_file = download_to_file; RequestExtraData extra_data; diff --git a/content/common/appcache_interfaces.cc b/content/common/appcache_interfaces.cc new file mode 100644 index 0000000..f9f9a05 --- /dev/null +++ b/content/common/appcache_interfaces.cc @@ -0,0 +1,139 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/common/appcache_interfaces.h" + +#include <set> + +#include "base/strings/string_util.h" +#include "net/url_request/url_request.h" +#include "url/gurl.h" + +namespace content { + +const char kHttpScheme[] = "http"; +const char kHttpsScheme[] = "https"; +const char kDevToolsScheme[] = "chrome-devtools"; +const char kHttpGETMethod[] = "GET"; +const char kHttpHEADMethod[] = "HEAD"; + +const char kEnableExecutableHandlers[] = "enable-appcache-executable-handlers"; + +const base::FilePath::CharType kAppCacheDatabaseName[] = + FILE_PATH_LITERAL("Index"); + +AppCacheInfo::AppCacheInfo() + : cache_id(kAppCacheNoCacheId), + group_id(0), + status(APPCACHE_STATUS_UNCACHED), + size(0), + is_complete(false) { +} + +AppCacheInfo::~AppCacheInfo() { +} + +AppCacheResourceInfo::AppCacheResourceInfo() + : url(), + size(0), + is_master(false), + is_manifest(false), + is_intercept(false), + is_fallback(false), + is_foreign(false), + is_explicit(false), + response_id(kAppCacheNoResponseId) { +} + +AppCacheResourceInfo::~AppCacheResourceInfo() { +} + +AppCacheErrorDetails::AppCacheErrorDetails() + : message(), + reason(APPCACHE_UNKNOWN_ERROR), + url(), + status(0), + is_cross_origin(false) {} + +AppCacheErrorDetails::AppCacheErrorDetails( + std::string in_message, + AppCacheErrorReason in_reason, + GURL in_url, + int in_status, + bool in_is_cross_origin) + : message(in_message), + reason(in_reason), + url(in_url), + status(in_status), + is_cross_origin(in_is_cross_origin) {} + +AppCacheErrorDetails::~AppCacheErrorDetails() {} + +AppCacheNamespace::AppCacheNamespace() + : type(APPCACHE_FALLBACK_NAMESPACE), + is_pattern(false), + is_executable(false) { +} + +AppCacheNamespace::AppCacheNamespace( + AppCacheNamespaceType type, const GURL& url, const GURL& target, + bool is_pattern) + : type(type), + namespace_url(url), + target_url(target), + is_pattern(is_pattern), + is_executable(false) { +} + +AppCacheNamespace::AppCacheNamespace( + AppCacheNamespaceType type, const GURL& url, const GURL& target, + bool is_pattern, bool is_executable) + : type(type), + namespace_url(url), + target_url(target), + is_pattern(is_pattern), + is_executable(is_executable) { +} + +AppCacheNamespace::~AppCacheNamespace() { +} + +bool AppCacheNamespace::IsMatch(const GURL& url) const { + if (is_pattern) { + // We have to escape '?' characters since MatchPattern also treats those + // as wildcards which we don't want here, we only do '*'s. + std::string pattern = namespace_url.spec(); + if (namespace_url.has_query()) + ReplaceSubstringsAfterOffset(&pattern, 0, "?", "\\?"); + return MatchPattern(url.spec(), pattern); + } + return StartsWithASCII(url.spec(), namespace_url.spec(), true); +} + +bool IsSchemeSupportedForAppCache(const GURL& url) { + bool supported = url.SchemeIs(kHttpScheme) || url.SchemeIs(kHttpsScheme) || + url.SchemeIs(kDevToolsScheme); + +#ifndef NDEBUG + // TODO(michaeln): It would be really nice if this could optionally work for + // file and filesystem urls too to help web developers experiment and test + // their apps, perhaps enabled via a cmd line flag or some other developer + // tool setting. Unfortunately file scheme net::URLRequests don't produce the + // same signalling (200 response codes, headers) as http URLRequests, so this + // doesn't work just yet. + // supported |= url.SchemeIsFile(); +#endif + return supported; +} + +bool IsMethodSupportedForAppCache(const std::string& method) { + return (method == kHttpGETMethod) || (method == kHttpHEADMethod); +} + +bool IsSchemeAndMethodSupportedForAppCache(const net::URLRequest* request) { + return IsSchemeSupportedForAppCache(request->url()) && + IsMethodSupportedForAppCache(request->method()); +} + +} // namespace content diff --git a/content/common/appcache_interfaces.h b/content/common/appcache_interfaces.h new file mode 100644 index 0000000..7199fbb --- /dev/null +++ b/content/common/appcache_interfaces.h @@ -0,0 +1,188 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_COMMON_APPCACHE_APPCACHE_INTERFACES_H_ +#define CONTENT_COMMON_APPCACHE_APPCACHE_INTERFACES_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/files/file_path.h" +#include "content/public/common/appcache_info.h" + +namespace net { +class URLRequest; +} + +namespace content { + +// Defines constants, types, and abstract classes used in the main +// process and in child processes. + +enum AppCacheEventID { + APPCACHE_CHECKING_EVENT, + APPCACHE_ERROR_EVENT, + APPCACHE_NO_UPDATE_EVENT, + APPCACHE_DOWNLOADING_EVENT, + APPCACHE_PROGRESS_EVENT, + APPCACHE_UPDATE_READY_EVENT, + APPCACHE_CACHED_EVENT, + APPCACHE_OBSOLETE_EVENT, + APPCACHE_EVENT_ID_LAST = APPCACHE_OBSOLETE_EVENT +}; + +// Temporarily renumber them in wierd way, to help remove LOG_TIP from WebKit +enum AppCacheLogLevel { + APPCACHE_LOG_DEBUG = 4, + APPCACHE_LOG_INFO = 1, + APPCACHE_LOG_WARNING = 2, + APPCACHE_LOG_ERROR = 3, +}; + +enum AppCacheNamespaceType { + APPCACHE_FALLBACK_NAMESPACE, + APPCACHE_INTERCEPT_NAMESPACE, + APPCACHE_NETWORK_NAMESPACE +}; + +enum AppCacheErrorReason { + APPCACHE_MANIFEST_ERROR, + APPCACHE_SIGNATURE_ERROR, + APPCACHE_RESOURCE_ERROR, + APPCACHE_CHANGED_ERROR, + APPCACHE_ABORT_ERROR, + APPCACHE_QUOTA_ERROR, + APPCACHE_POLICY_ERROR, + APPCACHE_UNKNOWN_ERROR, + APPCACHE_ERROR_REASON_LAST = APPCACHE_UNKNOWN_ERROR +}; + +// Type to hold information about a single appcache resource. +struct CONTENT_EXPORT AppCacheResourceInfo { + AppCacheResourceInfo(); + ~AppCacheResourceInfo(); + + GURL url; + int64 size; + bool is_master; + bool is_manifest; + bool is_intercept; + bool is_fallback; + bool is_foreign; + bool is_explicit; + int64 response_id; +}; + +struct CONTENT_EXPORT AppCacheErrorDetails { + AppCacheErrorDetails(); + AppCacheErrorDetails(std::string message, + AppCacheErrorReason reason, + GURL url, + int status, + bool is_cross_origin); + ~AppCacheErrorDetails(); + + std::string message; + AppCacheErrorReason reason; + GURL url; + int status; + bool is_cross_origin; +}; + +typedef std::vector<AppCacheResourceInfo> AppCacheResourceInfoVector; + +struct CONTENT_EXPORT AppCacheNamespace { + AppCacheNamespace(); // Type is APPCACHE_FALLBACK_NAMESPACE by default. + AppCacheNamespace(AppCacheNamespaceType type, const GURL& url, + const GURL& target, bool is_pattern); + AppCacheNamespace(AppCacheNamespaceType type, const GURL& url, + const GURL& target, bool is_pattern, bool is_executable); + ~AppCacheNamespace(); + + bool IsMatch(const GURL& url) const; + + AppCacheNamespaceType type; + GURL namespace_url; + GURL target_url; + bool is_pattern; + bool is_executable; +}; + +typedef std::vector<AppCacheNamespace> AppCacheNamespaceVector; + +// Interface used by backend (browser-process) to talk to frontend (renderer). +class CONTENT_EXPORT AppCacheFrontend { + public: + virtual void OnCacheSelected( + int host_id, const AppCacheInfo& info) = 0; + virtual void OnStatusChanged(const std::vector<int>& host_ids, + AppCacheStatus status) = 0; + virtual void OnEventRaised(const std::vector<int>& host_ids, + AppCacheEventID event_id) = 0; + virtual void OnProgressEventRaised(const std::vector<int>& host_ids, + const GURL& url, + int num_total, int num_complete) = 0; + virtual void OnErrorEventRaised( + const std::vector<int>& host_ids, + const AppCacheErrorDetails& details) = 0; + virtual void OnContentBlocked(int host_id, + const GURL& manifest_url) = 0; + virtual void OnLogMessage(int host_id, AppCacheLogLevel log_level, + const std::string& message) = 0; + virtual ~AppCacheFrontend() {} +}; + +// Interface used by frontend (renderer) to talk to backend (browser-process). +class CONTENT_EXPORT AppCacheBackend { + public: + virtual void RegisterHost(int host_id) = 0; + virtual void UnregisterHost(int host_id) = 0; + virtual void SetSpawningHostId(int host_id, int spawning_host_id) = 0; + virtual void SelectCache(int host_id, + const GURL& document_url, + const int64 cache_document_was_loaded_from, + const GURL& manifest_url) = 0; + virtual void SelectCacheForWorker( + int host_id, + int parent_process_id, + int parent_host_id) = 0; + virtual void SelectCacheForSharedWorker( + int host_id, + int64 appcache_id) = 0; + virtual void MarkAsForeignEntry(int host_id, const GURL& document_url, + int64 cache_document_was_loaded_from) = 0; + virtual AppCacheStatus GetStatus(int host_id) = 0; + virtual bool StartUpdate(int host_id) = 0; + virtual bool SwapCache(int host_id) = 0; + virtual void GetResourceList( + int host_id, std::vector<AppCacheResourceInfo>* resource_infos) = 0; + + protected: + virtual ~AppCacheBackend() {} +}; + +// Useful string constants. +// Note: These are also defined elsewhere in the chrome code base in +// url_contants.h .cc, however the content library can not have +// any dependencies on the chrome library, so we can't use them in here. +CONTENT_EXPORT extern const char kHttpScheme[]; +CONTENT_EXPORT extern const char kHttpsScheme[]; +CONTENT_EXPORT extern const char kHttpGETMethod[]; +CONTENT_EXPORT extern const char kHttpHEADMethod[]; + +// CommandLine flag to turn this experimental feature on. +CONTENT_EXPORT extern const char kEnableExecutableHandlers[]; + +CONTENT_EXPORT bool IsSchemeSupportedForAppCache(const GURL& url); +CONTENT_EXPORT bool IsMethodSupportedForAppCache( + const std::string& method); +CONTENT_EXPORT bool IsSchemeAndMethodSupportedForAppCache( + const net::URLRequest* request); + +CONTENT_EXPORT extern const base::FilePath::CharType + kAppCacheDatabaseName[]; + +} // namespace + +#endif // CONTENT_COMMON_APPCACHE_APPCACHE_INTERFACES_H_ diff --git a/content/common/appcache_messages.h b/content/common/appcache_messages.h index 8ad8fd5..15d3758 100644 --- a/content/common/appcache_messages.h +++ b/content/common/appcache_messages.h @@ -5,18 +5,18 @@ // Multiply-included message file, hence no include guard. #include "ipc/ipc_message_macros.h" -#include "webkit/common/appcache/appcache_interfaces.h" +#include "content/common/appcache_interfaces.h" #define IPC_MESSAGE_START AppCacheMsgStart -IPC_ENUM_TRAITS_MAX_VALUE(appcache::AppCacheEventID, - appcache::APPCACHE_EVENT_ID_LAST) -IPC_ENUM_TRAITS_MAX_VALUE(appcache::AppCacheStatus, - appcache::APPCACHE_STATUS_LAST) -IPC_ENUM_TRAITS_MAX_VALUE(appcache::AppCacheErrorReason, - appcache::APPCACHE_ERROR_REASON_LAST) +IPC_ENUM_TRAITS_MAX_VALUE(content::AppCacheEventID, + content::APPCACHE_EVENT_ID_LAST) +IPC_ENUM_TRAITS_MAX_VALUE(content::AppCacheStatus, + content::APPCACHE_STATUS_LAST) +IPC_ENUM_TRAITS_MAX_VALUE(content::AppCacheErrorReason, + content::APPCACHE_ERROR_REASON_LAST) -IPC_STRUCT_TRAITS_BEGIN(appcache::AppCacheInfo) +IPC_STRUCT_TRAITS_BEGIN(content::AppCacheInfo) IPC_STRUCT_TRAITS_MEMBER(manifest_url) IPC_STRUCT_TRAITS_MEMBER(creation_time) IPC_STRUCT_TRAITS_MEMBER(last_update_time) @@ -28,7 +28,7 @@ IPC_STRUCT_TRAITS_BEGIN(appcache::AppCacheInfo) IPC_STRUCT_TRAITS_MEMBER(is_complete) IPC_STRUCT_TRAITS_END() -IPC_STRUCT_TRAITS_BEGIN(appcache::AppCacheResourceInfo) +IPC_STRUCT_TRAITS_BEGIN(content::AppCacheResourceInfo) IPC_STRUCT_TRAITS_MEMBER(url) IPC_STRUCT_TRAITS_MEMBER(size) IPC_STRUCT_TRAITS_MEMBER(is_master) @@ -38,7 +38,7 @@ IPC_STRUCT_TRAITS_BEGIN(appcache::AppCacheResourceInfo) IPC_STRUCT_TRAITS_MEMBER(is_explicit) IPC_STRUCT_TRAITS_END() -IPC_STRUCT_TRAITS_BEGIN(appcache::AppCacheErrorDetails) +IPC_STRUCT_TRAITS_BEGIN(content::AppCacheErrorDetails) IPC_STRUCT_TRAITS_MEMBER(message) IPC_STRUCT_TRAITS_MEMBER(reason) IPC_STRUCT_TRAITS_MEMBER(url) @@ -96,7 +96,7 @@ IPC_MESSAGE_CONTROL3(AppCacheHostMsg_MarkAsForeignEntry, // Returns the status of the appcache associated with host_id. IPC_SYNC_MESSAGE_CONTROL1_1(AppCacheHostMsg_GetStatus, int /* host_id */, - appcache::AppCacheStatus) + content::AppCacheStatus) // Initiates an update of the appcache associated with host_id. IPC_SYNC_MESSAGE_CONTROL1_1(AppCacheHostMsg_StartUpdate, @@ -111,7 +111,7 @@ IPC_SYNC_MESSAGE_CONTROL1_1(AppCacheHostMsg_SwapCache, // Gets resource list from appcache synchronously. IPC_SYNC_MESSAGE_CONTROL1_1(AppCacheHostMsg_GetResourceList, int /* host_id in*/, - std::vector<appcache::AppCacheResourceInfo> + std::vector<content::AppCacheResourceInfo> /* resources out */) @@ -121,18 +121,18 @@ IPC_SYNC_MESSAGE_CONTROL1_1(AppCacheHostMsg_GetResourceList, // a particular host. This is sent in reply to AppCacheHostMsg_SelectCache. IPC_MESSAGE_CONTROL2(AppCacheMsg_CacheSelected, int /* host_id */, - appcache::AppCacheInfo) + content::AppCacheInfo) // Notifies the renderer of an AppCache status change. IPC_MESSAGE_CONTROL2(AppCacheMsg_StatusChanged, std::vector<int> /* host_ids */, - appcache::AppCacheStatus) + content::AppCacheStatus) // Notifies the renderer of an AppCache event other than the // progress event which has a seperate message. IPC_MESSAGE_CONTROL2(AppCacheMsg_EventRaised, std::vector<int> /* host_ids */, - appcache::AppCacheEventID) + content::AppCacheEventID) // Notifies the renderer of an AppCache progress event. IPC_MESSAGE_CONTROL4(AppCacheMsg_ProgressEventRaised, @@ -144,7 +144,7 @@ IPC_MESSAGE_CONTROL4(AppCacheMsg_ProgressEventRaised, // Notifies the renderer of an AppCache error event. IPC_MESSAGE_CONTROL2(AppCacheMsg_ErrorEventRaised, std::vector<int> /* host_ids */, - appcache::AppCacheErrorDetails) + content::AppCacheErrorDetails) // Notifies the renderer of an AppCache logging message. IPC_MESSAGE_CONTROL3(AppCacheMsg_LogMessage, diff --git a/content/content_browser.gypi b/content/content_browser.gypi index 66db276..a0d87b8 100644 --- a/content/content_browser.gypi +++ b/content/content_browser.gypi @@ -55,6 +55,7 @@ 'public/browser/android/synchronous_compositor.h', 'public/browser/android/ui_resource_client_android.h', 'public/browser/android/ui_resource_provider.h', + 'public/browser/appcache/appcache_service.h', 'public/browser/ax_event_notification_details.cc', 'public/browser/ax_event_notification_details.h', 'public/browser/blob_handle.h', @@ -312,16 +313,54 @@ 'browser/android/web_contents_observer_android.h', 'browser/android/ui_resource_provider_impl.cc', 'browser/android/ui_resource_provider_impl.h', + 'browser/appcache/appcache.cc', + 'browser/appcache/appcache.h', + 'browser/appcache/appcache_backend_impl.cc', + 'browser/appcache/appcache_backend_impl.h', + 'browser/appcache/appcache_database.cc', + 'browser/appcache/appcache_database.h', + 'browser/appcache/appcache_disk_cache.cc', + 'browser/appcache/appcache_disk_cache.h', 'browser/appcache/appcache_dispatcher_host.cc', 'browser/appcache/appcache_dispatcher_host.h', + 'browser/appcache/appcache_entry.h', + 'browser/appcache/appcache_executable_handler.h', 'browser/appcache/appcache_frontend_proxy.cc', 'browser/appcache/appcache_frontend_proxy.h', + 'browser/appcache/appcache_group.cc', + 'browser/appcache/appcache_group.h', + 'browser/appcache/appcache_histograms.cc', + 'browser/appcache/appcache_histograms.h', + 'browser/appcache/appcache_host.cc', + 'browser/appcache/appcache_host.h', 'browser/appcache/appcache_interceptor.cc', 'browser/appcache/appcache_interceptor.h', + 'browser/appcache/appcache_policy.h', + 'browser/appcache/appcache_quota_client.cc', + 'browser/appcache/appcache_quota_client.h', + 'browser/appcache/appcache_request_handler.cc', + 'browser/appcache/appcache_request_handler.h', + 'browser/appcache/appcache_response.cc', + 'browser/appcache/appcache_response.h', + 'browser/appcache/appcache_service_impl.cc', + 'browser/appcache/appcache_service_impl.h', + 'browser/appcache/appcache_storage.cc', + 'browser/appcache/appcache_storage.h', + 'browser/appcache/appcache_storage_impl.cc', + 'browser/appcache/appcache_storage_impl.h', + 'browser/appcache/appcache_update_job.cc', + 'browser/appcache/appcache_update_job.h', + 'browser/appcache/appcache_url_request_job.cc', + 'browser/appcache/appcache_url_request_job.h', + 'browser/appcache/appcache_working_set.cc', + 'browser/appcache/appcache_working_set.h', 'browser/appcache/chrome_appcache_service.cc', 'browser/appcache/chrome_appcache_service.h', - 'browser/appcache/view_appcache_internals_job.h', + 'browser/appcache/manifest_parser.cc', + 'browser/appcache/manifest_parser.h', 'browser/appcache/view_appcache_internals_job.cc', + 'browser/appcache/view_appcache_internals_job.h', + 'browser/battery_status/battery_status_manager_android.cc', 'browser/battery_status/battery_status_manager_default.cc', 'browser/battery_status/battery_status_manager.h', @@ -1652,6 +1691,10 @@ '../media/media.gyp:media', 'content.gyp:content_jni_headers', ], + 'defines': ['APPCACHE_USE_SIMPLE_CACHE'], + 'direct_dependent_settings': { + 'defines': ['APPCACHE_USE_SIMPLE_CACHE'], + }, 'link_settings': { 'libraries': [ '-ljnigraphics', diff --git a/content/content_common.gypi b/content/content_common.gypi index 7bc6b8ce..a67b53a 100644 --- a/content/content_common.gypi +++ b/content/content_common.gypi @@ -32,6 +32,7 @@ ], 'variables': { 'public_common_sources': [ + 'public/common/appcache_info.h', 'public/common/bindings_policy.h', 'public/common/child_process_host.h', 'public/common/child_process_host_delegate.cc', @@ -146,6 +147,8 @@ 'common/android/surface_texture_lookup.h', 'common/android/surface_texture_peer.cc', 'common/android/surface_texture_peer.h', + 'common/appcache_interfaces.cc', + 'common/appcache_interfaces.h', 'common/appcache_messages.h', 'common/battery_status_messages.h', 'common/browser_plugin/browser_plugin_constants.cc', diff --git a/content/public/browser/appcache_service.h b/content/public/browser/appcache_service.h new file mode 100644 index 0000000..7742abd --- /dev/null +++ b/content/public/browser/appcache_service.h @@ -0,0 +1,65 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_PUBLIC_BROWSER_APPCACHE_SERVICE_H_ +#define CONTENT_PUBLIC_BROWSER_APPCACHE_SERVICE_H_ + +#include <map> +#include <set> + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "content/common/content_export.h" +#include "content/public/common/appcache_info.h" +#include "net/base/completion_callback.h" + +namespace content { + +class AppCacheStorage; + +// Refcounted container to avoid copying the collection in callbacks. +struct CONTENT_EXPORT AppCacheInfoCollection + : public base::RefCountedThreadSafe<AppCacheInfoCollection> { + AppCacheInfoCollection(); + + std::map<GURL, AppCacheInfoVector> infos_by_origin; + + private: + friend class base::RefCountedThreadSafe<AppCacheInfoCollection>; + virtual ~AppCacheInfoCollection(); +}; + +// Exposes a limited interface to the AppCacheService. +// Call these methods only on the IO thread. +class CONTENT_EXPORT AppCacheService { + public: + // Determines if a request for 'url' can be satisfied while offline. + // This method always completes asynchronously. + virtual void CanHandleMainResourceOffline(const GURL& url, + const GURL& first_party, + const net::CompletionCallback& + callback) = 0; + + // Populates 'collection' with info about all of the appcaches stored + // within the service, 'callback' is invoked upon completion. The service + // acquires a reference to the 'collection' until until completion. + // This method always completes asynchronously. + virtual void GetAllAppCacheInfo(AppCacheInfoCollection* collection, + const net::CompletionCallback& callback) = 0; + + // Deletes the group identified by 'manifest_url', 'callback' is + // invoked upon completion. Upon completion, the cache group and + // any resources within the group are no longer loadable and all + // subresource loads for pages associated with a deleted group + // will fail. This method always completes asynchronously. + virtual void DeleteAppCacheGroup(const GURL& manifest_url, + const net::CompletionCallback& callback) = 0; + + protected: + virtual ~AppCacheService() {} +}; + +} // namespace content + +#endif // CONTENT_PUBLIC_BROWSER_APPCACHE_SERVICE_H_ diff --git a/content/public/browser/resource_context.h b/content/public/browser/resource_context.h index 58eb298..1c21de0 100644 --- a/content/public/browser/resource_context.h +++ b/content/public/browser/resource_context.h @@ -16,7 +16,7 @@ class GURL; -namespace appcache { +namespace content { class AppCacheService; } diff --git a/content/public/browser/resource_dispatcher_host_delegate.cc b/content/public/browser/resource_dispatcher_host_delegate.cc index b7b96c3..ceed430 100644 --- a/content/public/browser/resource_dispatcher_host_delegate.cc +++ b/content/public/browser/resource_dispatcher_host_delegate.cc @@ -21,7 +21,7 @@ bool ResourceDispatcherHostDelegate::ShouldBeginRequest( void ResourceDispatcherHostDelegate::RequestBeginning( net::URLRequest* request, ResourceContext* resource_context, - appcache::AppCacheService* appcache_service, + AppCacheService* appcache_service, ResourceType::Type resource_type, int child_id, int route_id, diff --git a/content/public/browser/resource_dispatcher_host_delegate.h b/content/public/browser/resource_dispatcher_host_delegate.h index 45796ba..d16a11f 100644 --- a/content/public/browser/resource_dispatcher_host_delegate.h +++ b/content/public/browser/resource_dispatcher_host_delegate.h @@ -15,7 +15,7 @@ class GURL; template <class T> class ScopedVector; -namespace appcache { +namespace content { class AppCacheService; } @@ -58,7 +58,7 @@ class CONTENT_EXPORT ResourceDispatcherHostDelegate { virtual void RequestBeginning( net::URLRequest* request, ResourceContext* resource_context, - appcache::AppCacheService* appcache_service, + AppCacheService* appcache_service, ResourceType::Type resource_type, int child_id, int route_id, diff --git a/content/public/browser/storage_partition.h b/content/public/browser/storage_partition.h index 3a4139d..ea8baa3 100644 --- a/content/public/browser/storage_partition.h +++ b/content/public/browser/storage_partition.h @@ -12,7 +12,7 @@ class GURL; -namespace appcache { +namespace content { class AppCacheService; } @@ -52,7 +52,7 @@ class StoragePartition { virtual net::URLRequestContextGetter* GetURLRequestContext() = 0; virtual net::URLRequestContextGetter* GetMediaURLRequestContext() = 0; virtual quota::QuotaManager* GetQuotaManager() = 0; - virtual appcache::AppCacheService* GetAppCacheService() = 0; + virtual AppCacheService* GetAppCacheService() = 0; virtual fileapi::FileSystemContext* GetFileSystemContext() = 0; virtual webkit_database::DatabaseTracker* GetDatabaseTracker() = 0; virtual DOMStorageContext* GetDOMStorageContext() = 0; diff --git a/content/public/common/appcache_info.h b/content/public/common/appcache_info.h new file mode 100644 index 0000000..f37e015 --- /dev/null +++ b/content/public/common/appcache_info.h @@ -0,0 +1,50 @@ +// Copyright (c) 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_PUBLIC_COMMON_APPCACHE_INFO_H_ +#define CONTENT_PUBLIC_COMMON_APPCACHE_INFO_H_ + +#include <vector> + +#include "base/time/time.h" +#include "content/common/content_export.h" +#include "url/gurl.h" + +namespace content { + +static const int kAppCacheNoHostId = 0; +static const int64 kAppCacheNoCacheId = 0; +static const int64 kAppCacheNoResponseId = 0; +static const int64 kAppCacheUnknownCacheId = -1; + +enum AppCacheStatus { + APPCACHE_STATUS_UNCACHED, + APPCACHE_STATUS_IDLE, + APPCACHE_STATUS_CHECKING, + APPCACHE_STATUS_DOWNLOADING, + APPCACHE_STATUS_UPDATE_READY, + APPCACHE_STATUS_OBSOLETE, + APPCACHE_STATUS_LAST = APPCACHE_STATUS_OBSOLETE +}; + +struct CONTENT_EXPORT AppCacheInfo { + AppCacheInfo(); + ~AppCacheInfo(); + + GURL manifest_url; + base::Time creation_time; + base::Time last_update_time; + base::Time last_access_time; + int64 cache_id; + int64 group_id; + AppCacheStatus status; + int64 size; + bool is_complete; +}; + +typedef std::vector<AppCacheInfo> AppCacheInfoVector; + +} // namespace + +#endif // CONTENT_PUBLIC_COMMON_APPCACHE_INFO_H_ diff --git a/content/public/common/resource_response_info.cc b/content/public/common/resource_response_info.cc index 70dbc50..e827f3e 100644 --- a/content/public/common/resource_response_info.cc +++ b/content/public/common/resource_response_info.cc @@ -4,15 +4,15 @@ #include "content/public/common/resource_response_info.h" +#include "content/public/common/appcache_info.h" #include "net/http/http_response_headers.h" -#include "webkit/common/appcache/appcache_interfaces.h" namespace content { ResourceResponseInfo::ResourceResponseInfo() : content_length(-1), encoded_data_length(-1), - appcache_id(appcache::kAppCacheNoCacheId), + appcache_id(kAppCacheNoCacheId), was_fetched_via_spdy(false), was_npn_negotiated(false), was_alternate_protocol_available(false), diff --git a/content/renderer/renderer_webapplicationcachehost_impl.cc b/content/renderer/renderer_webapplicationcachehost_impl.cc index 5b781d4..42aae61 100644 --- a/content/renderer/renderer_webapplicationcachehost_impl.cc +++ b/content/renderer/renderer_webapplicationcachehost_impl.cc @@ -10,7 +10,6 @@ #include "third_party/WebKit/public/web/WebFrame.h" #include "third_party/WebKit/public/web/WebView.h" -using appcache::AppCacheBackend; using blink::WebApplicationCacheHostClient; using blink::WebConsoleMessage; @@ -25,7 +24,7 @@ RendererWebApplicationCacheHostImpl::RendererWebApplicationCacheHostImpl( } void RendererWebApplicationCacheHostImpl::OnLogMessage( - appcache::AppCacheLogLevel log_level, const std::string& message) { + AppCacheLogLevel log_level, const std::string& message) { if (RenderThreadImpl::current()->layout_test_mode()) return; @@ -47,7 +46,7 @@ void RendererWebApplicationCacheHostImpl::OnContentBlocked( } void RendererWebApplicationCacheHostImpl::OnCacheSelected( - const appcache::AppCacheInfo& info) { + const AppCacheInfo& info) { if (!info.manifest_url.is_empty()) { RenderThreadImpl::current()->Send(new ViewHostMsg_AppCacheAccessed( routing_id_, info.manifest_url, false)); diff --git a/content/renderer/renderer_webapplicationcachehost_impl.h b/content/renderer/renderer_webapplicationcachehost_impl.h index e7b2bc8..a752c08 100644 --- a/content/renderer/renderer_webapplicationcachehost_impl.h +++ b/content/renderer/renderer_webapplicationcachehost_impl.h @@ -15,13 +15,13 @@ class RendererWebApplicationCacheHostImpl : public WebApplicationCacheHostImpl { RendererWebApplicationCacheHostImpl( RenderViewImpl* render_view, blink::WebApplicationCacheHostClient* client, - appcache::AppCacheBackend* backend); + AppCacheBackend* backend); // WebApplicationCacheHostImpl: - virtual void OnLogMessage(appcache::AppCacheLogLevel log_level, + virtual void OnLogMessage(AppCacheLogLevel log_level, const std::string& message) OVERRIDE; virtual void OnContentBlocked(const GURL& manifest_url) OVERRIDE; - virtual void OnCacheSelected(const appcache::AppCacheInfo& info) OVERRIDE; + virtual void OnCacheSelected(const AppCacheInfo& info) OVERRIDE; private: RenderViewImpl* GetRenderView(); diff --git a/content/test/appcache_test_helper.cc b/content/test/appcache_test_helper.cc index 087c721..1a95c05 100644 --- a/content/test/appcache_test_helper.cc +++ b/content/test/appcache_test_helper.cc @@ -7,11 +7,11 @@ #include "base/bind.h" #include "base/bind_helpers.h" #include "base/message_loop/message_loop.h" +#include "content/browser/appcache/appcache.h" +#include "content/browser/appcache/appcache_entry.h" +#include "content/browser/appcache/appcache_group.h" +#include "content/browser/appcache/appcache_service_impl.h" #include "testing/gtest/include/gtest/gtest.h" -#include "webkit/browser/appcache/appcache.h" -#include "webkit/browser/appcache/appcache_entry.h" -#include "webkit/browser/appcache/appcache_group.h" -#include "webkit/browser/appcache/appcache_service_impl.h" namespace content { @@ -24,23 +24,23 @@ AppCacheTestHelper::AppCacheTestHelper() AppCacheTestHelper::~AppCacheTestHelper() {} void AppCacheTestHelper::OnGroupAndNewestCacheStored( - appcache::AppCacheGroup* /*group*/, - appcache::AppCache* /*newest_cache*/, + AppCacheGroup* /*group*/, + AppCache* /*newest_cache*/, bool success, bool /*would_exceed_quota*/) { ASSERT_TRUE(success); base::MessageLoop::current()->Quit(); } -void AppCacheTestHelper::AddGroupAndCache(appcache::AppCacheServiceImpl* +void AppCacheTestHelper::AddGroupAndCache(AppCacheServiceImpl* appcache_service, const GURL& manifest_url) { - appcache::AppCacheGroup* appcache_group = - new appcache::AppCacheGroup(appcache_service->storage(), + AppCacheGroup* appcache_group = + new AppCacheGroup(appcache_service->storage(), manifest_url, ++group_id_); - appcache::AppCache* appcache = new appcache::AppCache( + AppCache* appcache = new AppCache( appcache_service->storage(), ++appcache_id_); - appcache::AppCacheEntry entry(appcache::AppCacheEntry::MANIFEST, + AppCacheEntry entry(AppCacheEntry::MANIFEST, ++response_id_); appcache->AddEntry(manifest_url, entry); appcache->set_complete(true); @@ -52,9 +52,9 @@ void AppCacheTestHelper::AddGroupAndCache(appcache::AppCacheServiceImpl* base::MessageLoop::current()->Run(); } -void AppCacheTestHelper::GetOriginsWithCaches(appcache::AppCacheServiceImpl* +void AppCacheTestHelper::GetOriginsWithCaches(AppCacheServiceImpl* appcache_service, std::set<GURL>* origins) { - appcache_info_ = new appcache::AppCacheInfoCollection; + appcache_info_ = new AppCacheInfoCollection; origins_ = origins; appcache_service->GetAllAppCacheInfo( appcache_info_.get(), @@ -66,7 +66,7 @@ void AppCacheTestHelper::GetOriginsWithCaches(appcache::AppCacheServiceImpl* } void AppCacheTestHelper::OnGotAppCacheInfo(int rv) { - typedef std::map<GURL, appcache::AppCacheInfoVector> InfoByOrigin; + typedef std::map<GURL, AppCacheInfoVector> InfoByOrigin; origins_->clear(); for (InfoByOrigin::const_iterator origin = diff --git a/content/test/appcache_test_helper.h b/content/test/appcache_test_helper.h index f93424b..3b6f6c9 100644 --- a/content/test/appcache_test_helper.h +++ b/content/test/appcache_test_helper.h @@ -7,9 +7,9 @@ #include <set> -#include "webkit/browser/appcache/appcache_storage.h" +#include "content/browser/appcache/appcache_storage.h" -namespace appcache { +namespace content { class AppCacheServiceImpl; } @@ -17,19 +17,19 @@ namespace content { // Helper class for inserting data into a ChromeAppCacheService and reading it // back. -class AppCacheTestHelper : public appcache::AppCacheStorage::Delegate { +class AppCacheTestHelper : public AppCacheStorage::Delegate { public: AppCacheTestHelper(); virtual ~AppCacheTestHelper(); - void AddGroupAndCache(appcache::AppCacheServiceImpl* appcache_service, + void AddGroupAndCache(AppCacheServiceImpl* appcache_service, const GURL& manifest_url); - void GetOriginsWithCaches(appcache::AppCacheServiceImpl* appcache_service, + void GetOriginsWithCaches(AppCacheServiceImpl* appcache_service, std::set<GURL>* origins); private: virtual void OnGroupAndNewestCacheStored( - appcache::AppCacheGroup* group, - appcache::AppCache* newest_cache, + AppCacheGroup* group, + AppCache* newest_cache, bool success, bool would_exceed_quota) OVERRIDE; void OnGotAppCacheInfo(int rv); @@ -37,7 +37,7 @@ class AppCacheTestHelper : public appcache::AppCacheStorage::Delegate { int group_id_; int appcache_id_; int response_id_; - scoped_refptr<appcache::AppCacheInfoCollection> appcache_info_; + scoped_refptr<AppCacheInfoCollection> appcache_info_; std::set<GURL>* origins_; // not owned DISALLOW_COPY_AND_ASSIGN(AppCacheTestHelper); |