diff options
author | michaeln@google.com <michaeln@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-09-15 22:07:15 +0000 |
---|---|---|
committer | michaeln@google.com <michaeln@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-09-15 22:07:15 +0000 |
commit | 97e3edc23314c476859c22c8db601f9b3dec552d (patch) | |
tree | 9c589f47545b88c1453c2481a7844a921636485c /webkit/appcache | |
parent | 34fce2a5030cb53a1e2decb37b5f7517f98457f7 (diff) | |
download | chromium_src-97e3edc23314c476859c22c8db601f9b3dec552d.zip chromium_src-97e3edc23314c476859c22c8db601f9b3dec552d.tar.gz chromium_src-97e3edc23314c476859c22c8db601f9b3dec552d.tar.bz2 |
* Fleshed out AppCacheHost class a fair amount, in particular the cache selection algorithm.
* Added some AppCacheHost unit tests.
* Introduced AppCacheRequestHandler class, which replaces the clunkyApp
CacheInterceptor::ExtraInfo struct. This impl is entirely skeletal
stubs for now.
TEST=appcache_unittest.cc, but really needs more
BUG=none
Review URL: http://codereview.chromium.org/192043
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@26275 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'webkit/appcache')
21 files changed, 988 insertions, 232 deletions
diff --git a/webkit/appcache/appcache.h b/webkit/appcache/appcache.h index 15944b6..fcfa9d1 100644 --- a/webkit/appcache/appcache.h +++ b/webkit/appcache/appcache.h @@ -30,14 +30,16 @@ class AppCache : public base::RefCounted<AppCache> { AppCache(AppCacheService *service, int64 cache_id); ~AppCache(); - int64 cache_id() { return cache_id_; } + int64 cache_id() const { return cache_id_; } - AppCacheGroup* owning_group() { return owning_group_; } + AppCacheGroup* owning_group() const { return owning_group_; } void set_owning_group(AppCacheGroup* group) { owning_group_ = group; } - bool is_complete() { return is_complete_; } + bool is_complete() const { return is_complete_; } void set_complete(bool value) { is_complete_ = value; } + AppCacheService* service() const { return service_; } + // Adds a new entry. Entry must not already be in cache. void AddEntry(const GURL& url, const AppCacheEntry& entry); @@ -49,9 +51,9 @@ class AppCache : public base::RefCounted<AppCache> { AppCacheEntry* GetEntry(const GURL& url); typedef std::map<GURL, AppCacheEntry> EntryMap; - const EntryMap& entries() { return entries_; } + const EntryMap& entries() const { return entries_; } - bool IsNewerThan(AppCache* cache) { + bool IsNewerThan(AppCache* cache) const { return update_time_ > cache->update_time_; } @@ -62,13 +64,13 @@ class AppCache : public base::RefCounted<AppCache> { private: friend class AppCacheHost; - // Use AppCacheHost::set_selected_cache() to manipulate host association. + // Use AppCacheHost::AssociateCache() to manipulate host association. void AssociateHost(AppCacheHost* host) { associated_hosts_.insert(host); } void UnassociateHost(AppCacheHost* host); - int64 cache_id_; + const int64 cache_id_; AppCacheEntry* manifest_; // also in entry map AppCacheGroup* owning_group_; std::set<AppCacheHost*> associated_hosts_; diff --git a/webkit/appcache/appcache_backend_impl.cc b/webkit/appcache/appcache_backend_impl.cc index 22e4e0f..f7788ae 100644 --- a/webkit/appcache/appcache_backend_impl.cc +++ b/webkit/appcache/appcache_backend_impl.cc @@ -4,7 +4,7 @@ #include "webkit/appcache/appcache_backend_impl.h" -#include "webkit/appcache/appcache_host.h" +#include "base/stl_util-inl.h" #include "webkit/appcache/appcache.h" #include "webkit/appcache/appcache_group.h" #include "webkit/appcache/appcache_service.h" @@ -13,6 +13,7 @@ namespace appcache { AppCacheBackendImpl::~AppCacheBackendImpl() { + STLDeleteValues(&hosts_); if (service_) service_->UnregisterBackend(this); } @@ -27,47 +28,79 @@ void AppCacheBackendImpl::Initialize(AppCacheService* service, service_->RegisterBackend(this); } -void AppCacheBackendImpl::RegisterHost(int id) { - DCHECK(hosts_.find(id) == hosts_.end()); - hosts_.insert(HostMap::value_type(id, AppCacheHost(id, frontend_))); +bool AppCacheBackendImpl::RegisterHost(int id) { + if (GetHost(id)) + return false; + + hosts_.insert( + HostMap::value_type(id, new AppCacheHost(id, frontend_, service_))); + return true; } -void AppCacheBackendImpl::UnregisterHost(int id) { - hosts_.erase(id); +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; } -void AppCacheBackendImpl::SelectCache( +bool AppCacheBackendImpl::SelectCache( int host_id, const GURL& document_url, const int64 cache_document_was_loaded_from, const GURL& manifest_url) { - // TODO(michaeln): write me - frontend_->OnCacheSelected(host_id, kNoCacheId, UNCACHED); + AppCacheHost* host = GetHost(host_id); + if (!host) + return false; + + host->SelectCache(document_url, cache_document_was_loaded_from, + manifest_url); + return true; } -void AppCacheBackendImpl::MarkAsForeignEntry( +bool AppCacheBackendImpl::MarkAsForeignEntry( int host_id, const GURL& document_url, int64 cache_document_was_loaded_from) { - // TODO(michaeln): write me + AppCacheHost* host = GetHost(host_id); + if (!host) + return false; + + host->MarkAsForeignEntry(document_url, cache_document_was_loaded_from); + return true; } -void AppCacheBackendImpl::GetStatusWithCallback( +bool AppCacheBackendImpl::GetStatusWithCallback( int host_id, GetStatusCallback* callback, void* callback_param) { - // TODO(michaeln): write me - callback->Run(UNCACHED, callback_param); + AppCacheHost* host = GetHost(host_id); + if (!host) + return false; + + host->GetStatusWithCallback(callback, callback_param); + return true; } -void AppCacheBackendImpl::StartUpdateWithCallback( +bool AppCacheBackendImpl::StartUpdateWithCallback( int host_id, StartUpdateCallback* callback, void* callback_param) { - // TODO(michaeln): write me - callback->Run(false, callback_param); + AppCacheHost* host = GetHost(host_id); + if (!host) + return false; + + host->StartUpdateWithCallback(callback, callback_param); + return true; } -void AppCacheBackendImpl::SwapCacheWithCallback( +bool AppCacheBackendImpl::SwapCacheWithCallback( int host_id, SwapCacheCallback* callback, void* callback_param) { - // TODO(michaeln): write me - callback->Run(false, callback_param); + AppCacheHost* host = GetHost(host_id); + if (!host) + return false; + + host->SwapCacheWithCallback(callback, callback_param); + return true; } } // namespace appcache diff --git a/webkit/appcache/appcache_backend_impl.h b/webkit/appcache/appcache_backend_impl.h index abf4247..4ae4e49 100644 --- a/webkit/appcache/appcache_backend_impl.h +++ b/webkit/appcache/appcache_backend_impl.h @@ -5,22 +5,15 @@ #ifndef WEBKIT_APPCACHE_APPCACHE_BACKEND_IMPL_H_ #define WEBKIT_APPCACHE_APPCACHE_BACKEND_IMPL_H_ -#include <map> - #include "base/logging.h" -#include "base/task.h" +#include "base/hash_tables.h" #include "webkit/appcache/appcache_host.h" -#include "webkit/appcache/appcache_interfaces.h" namespace appcache { class AppCacheService; -typedef Callback2<Status, void*>::Type GetStatusCallback; -typedef Callback2<bool, void*>::Type StartUpdateCallback; -typedef Callback2<bool, void*>::Type SwapCacheCallback; - -class AppCacheBackendImpl : public AppCacheBackend { +class AppCacheBackendImpl { public: AppCacheBackendImpl() : service_(NULL), frontend_(NULL), process_id_(0) {} ~AppCacheBackendImpl(); @@ -31,38 +24,33 @@ class AppCacheBackendImpl : public AppCacheBackend { 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 SelectCache(int host_id, + const GURL& document_url, + const int64 cache_document_was_loaded_from, + const GURL& manifest_url); + bool MarkAsForeignEntry(int host_id, const GURL& document_url, + int64 cache_document_was_loaded_from); + bool GetStatusWithCallback(int host_id, GetStatusCallback* callback, + void* callback_param); + bool StartUpdateWithCallback(int host_id, StartUpdateCallback* callback, + void* callback_param); + bool SwapCacheWithCallback(int host_id, 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; + return (it != hosts_.end()) ? (it->second) : NULL; } - typedef std::map<int, AppCacheHost> HostMap; + typedef base::hash_map<int, AppCacheHost*> HostMap; const HostMap& hosts() { return hosts_; } - // AppCacheBackend methods - virtual void RegisterHost(int host_id); - virtual void UnregisterHost(int host_id); - virtual void SelectCache(int host_id, - const GURL& document_url, - const int64 cache_document_was_loaded_from, - const GURL& manifest_url); - virtual void MarkAsForeignEntry(int host_id, const GURL& document_url, - int64 cache_document_was_loaded_from); - - // We don't use the sync variants in the backend, would block the IO thread. - virtual Status GetStatus(int host_id) { NOTREACHED(); return UNCACHED; } - virtual bool StartUpdate(int host_id) { NOTREACHED(); return false; } - virtual bool SwapCache(int host_id) { NOTREACHED(); return false; } - - // Async variants of the sync methods defined in the backend interface. - void GetStatusWithCallback(int host_id, GetStatusCallback* callback, - void* callback_param); - void StartUpdateWithCallback(int host_id, StartUpdateCallback* callback, - void* callback_param); - void SwapCacheWithCallback(int host_id, SwapCacheCallback* callback, - void* callback_param); - private: AppCacheService* service_; AppCacheFrontend* frontend_; diff --git a/webkit/appcache/appcache_entry.h b/webkit/appcache/appcache_entry.h index 5629cc2..434568a 100644 --- a/webkit/appcache/appcache_entry.h +++ b/webkit/appcache/appcache_entry.h @@ -5,8 +5,6 @@ #ifndef WEBKIT_APPCACHE_APPCACHE_ENTRY_H_ #define WEBKIT_APPCACHE_APPCACHE_ENTRY_H_ -#include "googleurl/src/gurl.h" - namespace appcache { // A cached entry is identified by a URL and is classified into one @@ -28,11 +26,11 @@ class AppCacheEntry { int types() const { return types_; } void add_types(int added_types) { types_ |= added_types; } - bool IsMaster() const { return types_ & MASTER; } - bool IsManifest() const { return types_ & MANIFEST; } - bool IsExplicit() const { return types_ & EXPLICIT; } - bool IsForeign() const { return types_ & FOREIGN; } - bool IsFallback() const { return types_ & FALLBACK; } + 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; } private: int types_; diff --git a/webkit/appcache/appcache_frontend_impl.h b/webkit/appcache/appcache_frontend_impl.h index e4ed922..cc10c27 100644 --- a/webkit/appcache/appcache_frontend_impl.h +++ b/webkit/appcache/appcache_frontend_impl.h @@ -12,7 +12,7 @@ namespace appcache { class AppCacheFrontendImpl : public AppCacheFrontend { public: - virtual void OnCacheSelected(int host_id, int64 cache_id , + virtual void OnCacheSelected(int host_id, int64 cache_id, Status status); virtual void OnStatusChanged(const std::vector<int>& host_ids, Status status); diff --git a/webkit/appcache/appcache_group.cc b/webkit/appcache/appcache_group.cc index 4299a67..10307c2 100644 --- a/webkit/appcache/appcache_group.cc +++ b/webkit/appcache/appcache_group.cc @@ -8,6 +8,7 @@ #include "base/logging.h" #include "webkit/appcache/appcache.h" +#include "webkit/appcache/appcache_host.h" #include "webkit/appcache/appcache_service.h" namespace appcache { @@ -61,7 +62,7 @@ bool AppCacheGroup::RemoveCache(AppCache* cache) { } else { // Unused old cache can always be removed. Caches::iterator it = - std::find(old_caches_.begin(), old_caches_.end(), cache); + std::find(old_caches_.begin(), old_caches_.end(), cache); if (it != old_caches_.end()) { (*it)->set_owning_group(NULL); old_caches_.erase(it); @@ -71,4 +72,9 @@ bool AppCacheGroup::RemoveCache(AppCache* cache) { return true; } +void AppCacheGroup::StartUpdateWithNewMasterEntry( + AppCacheHost* host, const GURL& master_entry_url) { + // TODO(michaeln): use the real AppCacheUpdateJob +} + } // namespace appcache diff --git a/webkit/appcache/appcache_group.h b/webkit/appcache/appcache_group.h index 49ba788..20422ae 100644 --- a/webkit/appcache/appcache_group.h +++ b/webkit/appcache/appcache_group.h @@ -13,6 +13,7 @@ namespace appcache { class AppCache; +class AppCacheHost; class AppCacheService; // Collection of application caches identified by the same manifest URL. @@ -45,6 +46,22 @@ class AppCacheGroup : public base::RefCounted<AppCacheGroup> { // cannot be removed as long as the group is still in use. bool RemoveCache(AppCache* cache); + // 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::EmptyGURL()); + } + + // 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); + private: GURL manifest_url_; UpdateStatus update_status_; diff --git a/webkit/appcache/appcache_group_unittest.cc b/webkit/appcache/appcache_group_unittest.cc index c4e0cd1..bcfbce0 100644 --- a/webkit/appcache/appcache_group_unittest.cc +++ b/webkit/appcache/appcache_group_unittest.cc @@ -4,26 +4,50 @@ #include "testing/gtest/include/gtest/gtest.h" #include "webkit/appcache/appcache.h" -#include "webkit/appcache/appcache_host.h" #include "webkit/appcache/appcache_group.h" +#include "webkit/appcache/appcache_host.h" #include "webkit/appcache/appcache_service.h" -using appcache::AppCache; -using appcache::AppCacheHost; -using appcache::AppCacheGroup; -using appcache::AppCacheService; - namespace { -class AppCacheGroupTest : public testing::Test { +class TestAppCacheFrontend : public appcache::AppCacheFrontend { + public: + TestAppCacheFrontend() + : last_host_id_(-1), last_cache_id_(-1), + last_status_(appcache::OBSOLETE) { + } + + virtual void OnCacheSelected(int host_id, int64 cache_id , + appcache::Status status) { + last_host_id_ = host_id; + last_cache_id_ = cache_id; + last_status_ = status; + } + + virtual void OnStatusChanged(const std::vector<int>& host_ids, + appcache::Status status) { + } + + virtual void OnEventRaised(const std::vector<int>& host_ids, + appcache::EventID event_id) { + } + + int last_host_id_; + int64 last_cache_id_; + appcache::Status last_status_; }; -} // namespace +} // namespace anon + +namespace appcache { + +class AppCacheGroupTest : public testing::Test { +}; TEST(AppCacheGroupTest, AddRemoveCache) { AppCacheService service; scoped_refptr<AppCacheGroup> group = - new AppCacheGroup(&service, GURL::EmptyGURL()); + new AppCacheGroup(&service, GURL::EmptyGURL()); base::TimeTicks ticks = base::TimeTicks::Now(); @@ -66,10 +90,11 @@ TEST(AppCacheGroupTest, AddRemoveCache) { TEST(AppCacheGroupTest, CleanupUnusedGroup) { AppCacheService service; + TestAppCacheFrontend frontend; AppCacheGroup* group = new AppCacheGroup(&service, GURL::EmptyGURL()); - AppCacheHost host1(1, NULL); - AppCacheHost host2(2, NULL); + AppCacheHost host1(1, &frontend, &service); + AppCacheHost host2(2, &frontend, &service); base::TimeTicks ticks = base::TimeTicks::Now(); @@ -80,8 +105,15 @@ TEST(AppCacheGroupTest, CleanupUnusedGroup) { group->AddCache(cache1); EXPECT_EQ(cache1, group->newest_complete_cache()); - host1.set_selected_cache(cache1); - host2.set_selected_cache(cache1); + host1.AssociateCache(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::IDLE); + + host2.AssociateCache(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::IDLE); AppCache* cache2 = new AppCache(&service, 222); cache2->set_complete(true); @@ -91,6 +123,11 @@ TEST(AppCacheGroupTest, CleanupUnusedGroup) { EXPECT_EQ(cache2, group->newest_complete_cache()); // Unassociate all hosts from older cache. - host1.set_selected_cache(NULL); - host2.set_selected_cache(NULL); + host1.AssociateCache(NULL); + host2.AssociateCache(NULL); + EXPECT_EQ(frontend.last_host_id_, host2.host_id()); + EXPECT_EQ(frontend.last_cache_id_, appcache::kNoCacheId); + EXPECT_EQ(frontend.last_status_, appcache::UNCACHED); } + +} // namespace appcache diff --git a/webkit/appcache/appcache_host.cc b/webkit/appcache/appcache_host.cc index 99f0544..252c618 100644 --- a/webkit/appcache/appcache_host.cc +++ b/webkit/appcache/appcache_host.cc @@ -8,33 +8,254 @@ #include "webkit/appcache/appcache.h" #include "webkit/appcache/appcache_group.h" #include "webkit/appcache/appcache_interfaces.h" +#include "webkit/appcache/appcache_request_handler.h" +#include "webkit/appcache/appcache_service.h" namespace appcache { -AppCacheHost::AppCacheHost(int host_id, AppCacheFrontend* frontend) - : host_id_(host_id), - selected_cache_(NULL), - group_(NULL), - frontend_(frontend) { +AppCacheHost::AppCacheHost(int host_id, AppCacheFrontend* frontend, + AppCacheService* service) + : host_id_(host_id), pending_selected_cache_id_(kNoCacheId), + frontend_(frontend), service_(service), + pending_get_status_callback_(NULL), pending_start_update_callback_(NULL), + pending_swap_cache_callback_(NULL), pending_callback_param_(NULL) { } AppCacheHost::~AppCacheHost() { - if (selected_cache_) - set_selected_cache(NULL); - DCHECK(!group_); + if (associated_cache_.get()) + associated_cache_->UnassociateHost(this); + service_->CancelLoads(this); } -void AppCacheHost::set_selected_cache(AppCache *cache) { - if (selected_cache_) - selected_cache_->UnassociateHost(this); +void AppCacheHost::SelectCache(const GURL& document_url, + const int64 cache_document_was_loaded_from, + const GURL& manifest_url) { + DCHECK(!pending_start_update_callback_ && + !pending_swap_cache_callback_ && + !pending_get_status_callback_); - selected_cache_ = cache; + // First we handle an unusual case of SelectCache being called a second + // time. Generally this shouldn't happen, but with bad content I think + // this can occur... <html manifest=foo> <html manifest=bar></html></html> + // We handle this by killing whatever loading we have initiated, and by + // unassociating any hosts we currently have associated... and starting + // anew with the inputs to this SelectCache call. + // TODO(michaeln): at some point determine what behavior the algorithms + // described in the HTML5 draft produce and have our impl produce those + // results (or suggest changes to the algorihtms described in the spec + // if the resulting behavior is just too insane). + if (is_selection_pending()) { + service_->CancelLoads(this); + pending_selected_manifest_url_ = GURL::EmptyGURL(); + pending_selected_cache_id_ = kNoCacheId; + } else if (associated_cache()) { + AssociateCache(NULL); + } + new_master_entry_url_ = GURL::EmptyGURL(); + + // 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: foriegn entries are detected on the client side and + // MarkAsForeignEntry is called in that case, so that detection + // step is skipped here. + + if (cache_document_was_loaded_from != kNoCacheId) { + LoadCache(cache_document_was_loaded_from); + return; + } + + if (!manifest_url.is_empty() && + (manifest_url.GetOrigin() == document_url.GetOrigin())) { + 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::MarkAsForeignEntry(const GURL& document_url, + int64 cache_document_was_loaded_from) { + service_->MarkAsForeignEntry(document_url, cache_document_was_loaded_from); + SelectCache(document_url, kNoCacheId, GURL::EmptyGURL()); +} + +void AppCacheHost::GetStatusWithCallback(GetStatusCallback* callback, + void* callback_param) { + DCHECK(!pending_start_update_callback_ && + !pending_swap_cache_callback_ && + !pending_get_status_callback_); + + pending_get_status_callback_ = callback; + pending_callback_param_ = callback_param; + if (is_selection_pending()) + return; + + DoPendingGetStatus(); +} + +void AppCacheHost::DoPendingGetStatus() { + DCHECK(pending_get_status_callback_); + + pending_get_status_callback_->Run( + GetStatus(), pending_callback_param_); + + pending_get_status_callback_ = NULL; + pending_callback_param_ = NULL; +} + +void AppCacheHost::StartUpdateWithCallback(StartUpdateCallback* callback, + void* callback_param) { + DCHECK(!pending_start_update_callback_ && + !pending_swap_cache_callback_ && + !pending_get_status_callback_); + + pending_start_update_callback_ = callback; + pending_callback_param_ = callback_param; + if (is_selection_pending()) + return; + + DoPendingStartUpdate(); +} + +void AppCacheHost::DoPendingStartUpdate() { + DCHECK(pending_start_update_callback_); + + // TODO(michaeln): start an update if appropiate to do so + pending_start_update_callback_->Run( + false, pending_callback_param_); + + pending_start_update_callback_ = NULL; + pending_callback_param_ = NULL; +} + +void AppCacheHost::SwapCacheWithCallback(SwapCacheCallback* callback, + void* callback_param) { + DCHECK(!pending_start_update_callback_ && + !pending_swap_cache_callback_ && + !pending_get_status_callback_); + + pending_swap_cache_callback_ = callback; + pending_callback_param_ = callback_param; + if (is_selection_pending()) + return; + + DoPendingSwapCache(); +} + +void AppCacheHost::DoPendingSwapCache() { + DCHECK(pending_swap_cache_callback_); + + // TODO(michaeln): swap if we have a cache that can be swapped. + pending_swap_cache_callback_->Run( + false, pending_callback_param_); + + pending_swap_cache_callback_ = NULL; + pending_callback_param_ = NULL; +} + +AppCacheRequestHandler* AppCacheHost::CreateRequestHandler( + URLRequest* request, + bool is_main_request) { + if (is_main_request) + return new AppCacheRequestHandler(this); + + if (associated_cache() && associated_cache()->is_complete()) + return new AppCacheRequestHandler(associated_cache()); + + return NULL; +} + +Status AppCacheHost::GetStatus() { + // TODO(michaeln): determine a real status value + Status status = associated_cache() ? IDLE : UNCACHED; + return status; +} + +void AppCacheHost::LoadOrCreateGroup(const GURL& manifest_url) { + DCHECK(manifest_url.is_valid()); + pending_selected_manifest_url_ = manifest_url; + service_->LoadOrCreateGroup(manifest_url, this); +} + +void AppCacheHost::GroupLoadedCallback( + AppCacheGroup* group, const GURL& manifest_url) { + DCHECK(manifest_url == pending_selected_manifest_url_); + pending_selected_manifest_url_ = GURL::EmptyGURL(); + FinishCacheSelection(NULL, group); +} + +void AppCacheHost::LoadCache(int64 cache_id) { + DCHECK(cache_id != kNoCacheId); + pending_selected_cache_id_ = cache_id; + service_->LoadCache(cache_id, this); +} + +void AppCacheHost::CacheLoadedCallback(AppCache* cache, int64 cache_id) { + DCHECK(cache_id == pending_selected_cache_id_); + pending_selected_cache_id_ = kNoCacheId; + if (cache) + FinishCacheSelection(cache, NULL); + else + FinishCacheSelection(NULL, 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()); + AssociateCache(cache); + cache->owning_group()->StartUpdateWithHost(this); + + } else if (group) { + // 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(new_master_entry_url_.is_valid()); + AssociateCache(NULL); // The UpdateJob may produce one for us later. + group->StartUpdateWithNewMasterEntry(this, new_master_entry_url_); + + } else { + // Otherwise, the Document is not associated with any application cache. + AssociateCache(NULL); + } + + // Respond to pending callbacks now that we have a selection. + if (pending_get_status_callback_) + DoPendingGetStatus(); + else if (pending_start_update_callback_) + DoPendingStartUpdate(); + else if (pending_swap_cache_callback_) + DoPendingSwapCache(); +} + +void AppCacheHost::AssociateCache(AppCache* cache) { + if (associated_cache_.get()) { + associated_cache_->UnassociateHost(this); + group_ = NULL; + } + + associated_cache_ = cache; if (cache) { cache->AssociateHost(this); group_ = cache->owning_group(); + frontend_->OnCacheSelected(host_id_, cache->cache_id(), GetStatus()); } else { - group_ = NULL; + frontend_->OnCacheSelected(host_id_, kNoCacheId, UNCACHED); } } diff --git a/webkit/appcache/appcache_host.h b/webkit/appcache/appcache_host.h index 420e3d4..d8ee843 100644 --- a/webkit/appcache/appcache_host.h +++ b/webkit/appcache/appcache_host.h @@ -6,42 +6,126 @@ #define WEBKIT_APPCACHE_APPCACHE_HOST_H_ #include "base/ref_counted.h" +#include "base/task.h" +#include "base/weak_ptr.h" +#include "googleurl/src/gurl.h" +#include "testing/gtest/include/gtest/gtest_prod.h" +#include "webkit/appcache/appcache.h" +#include "webkit/appcache/appcache_interfaces.h" +#include "webkit/appcache/appcache_service.h" + +class URLRequest; namespace appcache { class AppCache; class AppCacheFrontend; class AppCacheGroup; +class AppCacheRequestHandler; + +typedef Callback2<Status, void*>::Type GetStatusCallback; +typedef Callback2<bool, void*>::Type StartUpdateCallback; +typedef Callback2<bool, void*>::Type SwapCacheCallback; // Server-side representation of an application cache host. -class AppCacheHost { +class AppCacheHost : public base::SupportsWeakPtr<AppCacheHost>, + public AppCacheService::LoadClient { public: - AppCacheHost(int host_id, AppCacheFrontend* frontend); + AppCacheHost(int host_id, AppCacheFrontend* frontend, + AppCacheService* service); ~AppCacheHost(); - int host_id() { return host_id_; } - AppCacheFrontend* frontend() { return frontend_; } + // 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 MarkAsForeignEntry(const GURL& document_url, + int64 cache_document_was_loaded_from); + void GetStatusWithCallback(GetStatusCallback* callback, + void* callback_param); + void StartUpdateWithCallback(StartUpdateCallback* callback, + void* callback_param); + void SwapCacheWithCallback(SwapCacheCallback* callback, + void* callback_param); - AppCache* selected_cache() { return selected_cache_; } - void set_selected_cache(AppCache* cache); + // Support for loading resources out of the appcache. + // Returns NULL if the host is not associated with a complete cache. + AppCacheRequestHandler* CreateRequestHandler(URLRequest* request, + bool is_main_request); - bool is_selection_pending() { - return false; // TODO(michaeln) - } + // Establishes an association between this host and a cache. 'cache' may be + // NULL to break any existing association. Associations are established + // either thru the cache selection algorithm implemented (in this class), + // or by the update algorithm (see AppCacheUpdateJob). + void AssociateCache(AppCache* cache); + + int host_id() const { return host_id_; } + AppCacheService* service() const { return service_; } + AppCacheFrontend* frontend() const { return frontend_; } + AppCache* associated_cache() const { return associated_cache_.get(); } private: - // identifies the corresponding appcache host in the child process + bool is_selection_pending() const { + return pending_selected_cache_id_ != kNoCacheId || + !pending_selected_manifest_url_.is_empty(); + } + Status GetStatus(); + void LoadCache(int64 cache_id); + void LoadOrCreateGroup(const GURL& manifest_url); + + // LoadClient impl + virtual void CacheLoadedCallback(AppCache* cache, int64 cache_id); + virtual void GroupLoadedCallback(AppCacheGroup* group, + const GURL& manifest_url); + + void FinishCacheSelection(AppCache* cache, AppCacheGroup* group); + void DoPendingGetStatus(); + void DoPendingStartUpdate(); + void DoPendingSwapCache(); + + // Identifies the corresponding appcache host in the child process. int host_id_; - // application cache associated with this host, if any - scoped_refptr<AppCache> selected_cache_; + // The cache associated with this host, if any. + scoped_refptr<AppCache> associated_cache_; - // The reference to the appcache group ensures the group exists as long - // as there is a host using a cache belonging to that group. + // The reference to the group ensures the group exists + // while we have an association with a cache in the group. scoped_refptr<AppCacheGroup> group_; - // frontend to deliver notifications about this host to child process + // 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. + AppCacheService* service_; + + // 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_; + + FRIEND_TEST(AppCacheTest, CleanupUnusedCache); + FRIEND_TEST(AppCacheGroupTest, CleanupUnusedGroup); + FRIEND_TEST(AppCacheHostTest, Basic); + FRIEND_TEST(AppCacheHostTest, SelectNoCache); + FRIEND_TEST(AppCacheHostTest, ForeignEntry); + FRIEND_TEST(AppCacheHostTest, FailedCacheLoad); + FRIEND_TEST(AppCacheHostTest, FailedGroupLoad); + DISALLOW_COPY_AND_ASSIGN(AppCacheHost); }; } // namespace appcache diff --git a/webkit/appcache/appcache_host_unittest.cc b/webkit/appcache/appcache_host_unittest.cc new file mode 100644 index 0000000..cd1497b --- /dev/null +++ b/webkit/appcache/appcache_host_unittest.cc @@ -0,0 +1,230 @@ +// Copyright (c) 2009 The Chromium Authos. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/scoped_ptr.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/appcache/appcache.h" +#include "webkit/appcache/appcache_group.h" +#include "webkit/appcache/appcache_host.h" +#include "webkit/appcache/appcache_service.h" + +namespace appcache { + +class AppCacheHostTest : public testing::Test { + public: + AppCacheHostTest() { + get_status_callback_.reset( + NewCallback(this, &AppCacheHostTest::GetStatusCallback)); + start_update_callback_.reset( + NewCallback(this, &AppCacheHostTest::StartUpdateCallback)); + swap_cache_callback_.reset( + NewCallback(this, &AppCacheHostTest::SwapCacheCallback)); + } + + class MockFrontend : public AppCacheFrontend { + public: + MockFrontend() + : last_host_id_(-222), last_cache_id_(-222), + last_status_(appcache::OBSOLETE) { + } + + virtual void OnCacheSelected(int host_id, int64 cache_id , + appcache::Status status) { + last_host_id_ = host_id; + last_cache_id_ = cache_id; + last_status_ = status; + } + + virtual void OnStatusChanged(const std::vector<int>& host_ids, + appcache::Status status) { + } + + virtual void OnEventRaised(const std::vector<int>& host_ids, + appcache::EventID event_id) { + } + + int last_host_id_; + int64 last_cache_id_; + appcache::Status last_status_; + }; + + void GetStatusCallback(Status status, void* param) { + last_status_result_ = status; + last_callback_param_ = param; + } + + void StartUpdateCallback(bool result, void* param) { + last_start_result_ = result; + last_callback_param_ = param; + } + + void SwapCacheCallback(bool result, void* param) { + last_swap_result_ = result; + last_callback_param_ = param; + } + + // Mock classes for the 'host' to work with + AppCacheService service_; // TODO(michaeln): make service mockable? + MockFrontend mock_frontend_; + + // Mock callbacks we expect to receive from the 'host' + scoped_ptr<appcache::GetStatusCallback> get_status_callback_; + scoped_ptr<appcache::StartUpdateCallback> start_update_callback_; + scoped_ptr<appcache::SwapCacheCallback> swap_cache_callback_; + + Status last_status_result_; + bool last_swap_result_; + bool last_start_result_; + void* last_callback_param_; +}; + +TEST_F(AppCacheHostTest, Basic) { + // Construct a host and test what state it appears to be in. + AppCacheHost host(1, &mock_frontend_, &service_); + EXPECT_EQ(1, host.host_id()); + EXPECT_EQ(&service_, host.service()); + EXPECT_EQ(&mock_frontend_, host.frontend()); + EXPECT_EQ(NULL, host.associated_cache()); + EXPECT_FALSE(host.is_selection_pending()); + + // See that the callbacks are delivered immediately + // and respond as if there is no cache selected. + last_status_result_ = OBSOLETE; + host.GetStatusWithCallback(get_status_callback_.get(), + reinterpret_cast<void*>(1)); + EXPECT_EQ(UNCACHED, last_status_result_); + EXPECT_EQ(reinterpret_cast<void*>(1), last_callback_param_); + + last_start_result_ = true; + host.StartUpdateWithCallback(start_update_callback_.get(), + reinterpret_cast<void*>(2)); + EXPECT_FALSE(last_start_result_); + EXPECT_EQ(reinterpret_cast<void*>(2), last_callback_param_); + + last_swap_result_ = true; + host.SwapCacheWithCallback(swap_cache_callback_.get(), + reinterpret_cast<void*>(3)); + EXPECT_FALSE(last_swap_result_); + EXPECT_EQ(reinterpret_cast<void*>(3), last_callback_param_); +} + +TEST_F(AppCacheHostTest, SelectNoCache) { + // Reset our mock frontend + mock_frontend_.last_cache_id_ = -333; + mock_frontend_.last_host_id_ = -333; + mock_frontend_.last_status_ = OBSOLETE; + + AppCacheHost host(1, &mock_frontend_, &service_); + host.SelectCache(GURL("http://whatever/"), kNoCacheId, GURL::EmptyGURL()); + + // We should have received an OnCacheSelected msg + EXPECT_EQ(1, mock_frontend_.last_host_id_); + EXPECT_EQ(kNoCacheId, mock_frontend_.last_cache_id_); + EXPECT_EQ(UNCACHED, mock_frontend_.last_status_); + + // Otherwise, see that it respond as if there is no cache selected. + EXPECT_EQ(1, host.host_id()); + EXPECT_EQ(&service_, host.service()); + EXPECT_EQ(&mock_frontend_, host.frontend()); + EXPECT_EQ(NULL, host.associated_cache()); + EXPECT_FALSE(host.is_selection_pending()); +} + +TEST_F(AppCacheHostTest, ForeignEntry) { + // Reset our mock frontend + mock_frontend_.last_cache_id_ = -333; + mock_frontend_.last_host_id_ = -333; + mock_frontend_.last_status_ = OBSOLETE; + + AppCacheHost host(1, &mock_frontend_, &service_); + host.MarkAsForeignEntry(GURL("http://whatever/"), 22); + + // We should have received an OnCacheSelected msg for kNoCacheId. + EXPECT_EQ(1, mock_frontend_.last_host_id_); + EXPECT_EQ(kNoCacheId, mock_frontend_.last_cache_id_); + EXPECT_EQ(UNCACHED, mock_frontend_.last_status_); + + // See that it respond as if there is no cache selected. + EXPECT_EQ(1, host.host_id()); + EXPECT_EQ(&service_, host.service()); + EXPECT_EQ(&mock_frontend_, host.frontend()); + EXPECT_EQ(NULL, host.associated_cache()); + EXPECT_FALSE(host.is_selection_pending()); +} + + +TEST_F(AppCacheHostTest, FailedCacheLoad) { + // Reset our mock frontend + mock_frontend_.last_cache_id_ = -333; + mock_frontend_.last_host_id_ = -333; + mock_frontend_.last_status_ = OBSOLETE; + + AppCacheHost host(1, &mock_frontend_, &service_); + EXPECT_FALSE(host.is_selection_pending()); + + const int kMockCacheId = 333; + + // Put it in a state where we're waiting on a cache + // load prior to finishing cache selection. + host.pending_selected_cache_id_ = kMockCacheId; + EXPECT_TRUE(host.is_selection_pending()); + + // The callback should not occur until we finish cache selection. + last_status_result_ = OBSOLETE; + last_callback_param_ = reinterpret_cast<void*>(-1); + host.GetStatusWithCallback(get_status_callback_.get(), + reinterpret_cast<void*>(1)); + EXPECT_EQ(OBSOLETE, last_status_result_); + EXPECT_EQ(reinterpret_cast<void*>(-1), last_callback_param_); + + // Satisfy the load with NULL, a failure. + host.CacheLoadedCallback(NULL, kMockCacheId); + + // Cache selection should have finished + EXPECT_FALSE(host.is_selection_pending()); + EXPECT_EQ(1, mock_frontend_.last_host_id_); + EXPECT_EQ(kNoCacheId, mock_frontend_.last_cache_id_); + EXPECT_EQ(UNCACHED, mock_frontend_.last_status_); + + // Callback should have fired upon completing the cache load too. + EXPECT_EQ(UNCACHED, last_status_result_); + EXPECT_EQ(reinterpret_cast<void*>(1), last_callback_param_); +} + +TEST_F(AppCacheHostTest, FailedGroupLoad) { + AppCacheHost host(1, &mock_frontend_, &service_); + + const GURL kMockManifestUrl("http://foo.bar/baz"); + + // Put it in a state where we're waiting on a cache + // load prior to finishing cache selection. + host.pending_selected_manifest_url_ = kMockManifestUrl; + EXPECT_TRUE(host.is_selection_pending()); + + // The callback should not occur until we finish cache selection. + last_status_result_ = OBSOLETE; + last_callback_param_ = reinterpret_cast<void*>(-1); + host.GetStatusWithCallback(get_status_callback_.get(), + reinterpret_cast<void*>(1)); + EXPECT_EQ(OBSOLETE, last_status_result_); + EXPECT_EQ(reinterpret_cast<void*>(-1), last_callback_param_); + + // Satisfy the load will NULL, a failure. + host.GroupLoadedCallback(NULL, kMockManifestUrl); + + // Cache selection should have finished + EXPECT_FALSE(host.is_selection_pending()); + EXPECT_EQ(1, mock_frontend_.last_host_id_); + EXPECT_EQ(kNoCacheId, mock_frontend_.last_cache_id_); + EXPECT_EQ(UNCACHED, mock_frontend_.last_status_); + + // Callback should have fired upon completing the group load. + EXPECT_EQ(UNCACHED, last_status_result_); + EXPECT_EQ(reinterpret_cast<void*>(1), last_callback_param_); +} + +// TODO(michaeln): Flesh these tests out more. + +} // namespace appcache + diff --git a/webkit/appcache/appcache_interceptor.cc b/webkit/appcache/appcache_interceptor.cc index a66fea8..88bcd56 100644 --- a/webkit/appcache/appcache_interceptor.cc +++ b/webkit/appcache/appcache_interceptor.cc @@ -7,84 +7,51 @@ #include "webkit/appcache/appcache_backend_impl.h" #include "webkit/appcache/appcache_host.h" #include "webkit/appcache/appcache_interfaces.h" +#include "webkit/appcache/appcache_request_handler.h" #include "webkit/appcache/appcache_service.h" namespace appcache { -// Extra info we associate with requests for use at MaybeIntercept time. This -// info is deleted when the URLRequest is deleted which occurs after the -// request is complete and all data has been read. -struct AppCacheInterceptor::ExtraInfo : public URLRequest::UserData { - // Inputs, extra request info - AppCacheService* service; - int process_id; - int host_id; - ResourceType::Type resource_type; - - // Outputs, extra response info - int64 cache_id; - GURL manifest_url; - - // The host associated with the request - // TODO(michaeln): Be careful with this data member, its not clear - // if a URLRequest can outlive the associated host. As we get further - // along, we'll need to notify reqeust waiting on cache selection to - // allow them to continue upon completion of selection. But we also need - // to handle navigating away from the page away prior to selection being - // complete. - AppCacheHost* host_; - - ExtraInfo(AppCacheService* service, int process_id, int host_id, - ResourceType::Type resource_type, AppCacheHost* host) - : service(service), process_id(process_id), host_id(host_id), - resource_type(resource_type), cache_id(kNoCacheId), host_(host) { - } - - static void SetInfo(URLRequest* request, ExtraInfo* info) { - request->SetUserData(instance(), info); // request takes ownership - } - - static ExtraInfo* GetInfo(URLRequest* request) { - return static_cast<ExtraInfo*>(request->GetUserData(instance())); - } -}; - -static bool IsMainRequest(ResourceType::Type type) { - // TODO(michaeln): SHARED_WORKER type? - return ResourceType::IsFrame(type); +void AppCacheInterceptor::SetHandler( + URLRequest* request, AppCacheRequestHandler* handler) { + request->SetUserData(instance(), handler); // request takes ownership +} + +AppCacheRequestHandler* AppCacheInterceptor::GetHandler(URLRequest* request) { + return reinterpret_cast<AppCacheRequestHandler*>( + request->GetUserData(instance())); } void AppCacheInterceptor::SetExtraRequestInfo( URLRequest* request, AppCacheService* service, int process_id, int host_id, ResourceType::Type resource_type) { - if (service && (host_id != kNoHostId)) { - AppCacheHost* host = service->GetBackend(process_id)->GetHost(host_id); - DCHECK(host); - if (IsMainRequest(resource_type) || host->selected_cache() || - host->is_selection_pending()) { - ExtraInfo* info = new ExtraInfo(service, process_id, - host_id, resource_type, host); - ExtraInfo::SetInfo(request, info); - } - } + if (!service || (host_id == kNoHostId)) + return; + + // TODO(michaeln): An invalid host id is indicative of bad data + // from a child process. How should we handle that here? + AppCacheHost* host = service->GetBackend(process_id)->GetHost(host_id); + if (!host) + return; + + // TODO(michaeln): SHARED_WORKER type too + bool is_main_request = ResourceType::IsFrame(resource_type); + + // Create a handler for this request and associate it with the request. + AppCacheRequestHandler* handler = + host->CreateRequestHandler(request, is_main_request); + if (handler) + SetHandler(request, handler); } void AppCacheInterceptor::GetExtraResponseInfo(URLRequest* request, int64* cache_id, GURL* manifest_url) { - ExtraInfo* info = ExtraInfo::GetInfo(request); - if (info) { - // TODO(michaeln): If this is a main request and it was retrieved from - // an appcache, ensure that appcache survives the frame navigation. The - // AppCacheHost should hold reference to that cache to prevent it from - // being dropped from the in-memory collection of AppCaches. When cache - // selection occurs, that extra reference should be dropped. - *cache_id = info->cache_id; - *manifest_url = info->manifest_url; - } else { - DCHECK(*cache_id == kNoCacheId); - DCHECK(manifest_url->is_empty()); - } + DCHECK(*cache_id == kNoCacheId); + DCHECK(manifest_url->is_empty()); + AppCacheRequestHandler* handler = GetHandler(request); + if (handler) + handler->GetExtraResponseInfo(cache_id, manifest_url); } AppCacheInterceptor::AppCacheInterceptor() { @@ -96,30 +63,27 @@ AppCacheInterceptor::~AppCacheInterceptor() { } URLRequestJob* AppCacheInterceptor::MaybeIntercept(URLRequest* request) { - ExtraInfo* info = ExtraInfo::GetInfo(request); - if (!info) + AppCacheRequestHandler* handler = GetHandler(request); + if (!handler) return NULL; - // TODO(michaeln): write me - return NULL; + return handler->MaybeLoadResource(request); } URLRequestJob* AppCacheInterceptor::MaybeInterceptRedirect( URLRequest* request, const GURL& location) { - ExtraInfo* info = ExtraInfo::GetInfo(request); - if (!info) + AppCacheRequestHandler* handler = GetHandler(request); + if (!handler) return NULL; - // TODO(michaeln): write me - return NULL; + return handler->MaybeLoadFallbackForRedirect(request, location); } URLRequestJob* AppCacheInterceptor::MaybeInterceptResponse( URLRequest* request) { - ExtraInfo* info = ExtraInfo::GetInfo(request); - if (!info) + AppCacheRequestHandler* handler = GetHandler(request); + if (!handler) return NULL; - // TODO(michaeln): write me - return NULL; + return handler->MaybeLoadFallbackForResponse(request); } } // namespace appcache diff --git a/webkit/appcache/appcache_interceptor.h b/webkit/appcache/appcache_interceptor.h index 51b9a89..7af9aed 100644 --- a/webkit/appcache/appcache_interceptor.h +++ b/webkit/appcache/appcache_interceptor.h @@ -12,6 +12,7 @@ namespace appcache { +class AppCacheRequestHandler; class AppCacheService; // An interceptor to hijack requests and potentially service them out of @@ -46,12 +47,17 @@ class AppCacheInterceptor : public URLRequest::Interceptor { private: friend struct DefaultSingletonTraits<AppCacheInterceptor>; + static AppCacheInterceptor* instance() { return Singleton<AppCacheInterceptor>::get(); } - struct ExtraInfo; + AppCacheInterceptor(); virtual ~AppCacheInterceptor(); + + static void SetHandler(URLRequest* request, AppCacheRequestHandler* handler); + static AppCacheRequestHandler* GetHandler(URLRequest* request); + DISALLOW_COPY_AND_ASSIGN(AppCacheInterceptor); }; diff --git a/webkit/appcache/appcache_request_handler.cc b/webkit/appcache/appcache_request_handler.cc new file mode 100644 index 0000000..322e8bc --- /dev/null +++ b/webkit/appcache/appcache_request_handler.cc @@ -0,0 +1,63 @@ +// 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 "webkit/appcache/appcache_request_handler.h" + +#include "net/url_request/url_request.h" +#include "net/url_request/url_request_job.h" +#include "webkit/appcache/appcache.h" + +namespace appcache { + +// AppCacheRequestHandler ----------------------------------------------------- + +static bool IsHttpOrHttpsGetOrEquivalent(URLRequest* request) { + return false; // TODO(michaeln): write me +} + +AppCacheRequestHandler::AppCacheRequestHandler(AppCacheHost* host) + : is_main_request_(true), cache_id_(kNoCacheId), + host_(host->AsWeakPtr()), service_(host->service()) { +} + +AppCacheRequestHandler::AppCacheRequestHandler(AppCache* cache) + : is_main_request_(false), cache_id_(kNoCacheId), + cache_(cache), service_(cache->service()) { +} + +void AppCacheRequestHandler::GetExtraResponseInfo( + int64* cache_id, GURL* manifest_url) { + // TODO(michaeln): If this is a main request and it was retrieved from + // an appcache, ensure that appcache survives the frame navigation. The + // AppCacheHost should hold reference to that cache to prevent it from + // being dropped from the in-memory collection of AppCaches. When cache + // selection occurs, that extra reference should be dropped. Perhaps + // maybe: if (is_main) host->LoadCacheOfMainResource(cache_id); +} + +URLRequestJob* AppCacheRequestHandler::MaybeLoadResource(URLRequest* request) { + if (!IsHttpOrHttpsGetOrEquivalent(request)) + return NULL; + // TODO(michaeln): write me + return NULL; +} + +URLRequestJob* AppCacheRequestHandler::MaybeLoadFallbackForRedirect( + URLRequest* request, const GURL& location) { + if (!IsHttpOrHttpsGetOrEquivalent(request)) + return NULL; + // TODO(michaeln): write me + return NULL; +} + +URLRequestJob* AppCacheRequestHandler::MaybeLoadFallbackForResponse( + URLRequest* request) { + if (!IsHttpOrHttpsGetOrEquivalent(request)) + return NULL; + // TODO(michaeln): write me + return NULL; +} + +} // namespace appcache + diff --git a/webkit/appcache/appcache_request_handler.h b/webkit/appcache/appcache_request_handler.h new file mode 100644 index 0000000..78fe9c3 --- /dev/null +++ b/webkit/appcache/appcache_request_handler.h @@ -0,0 +1,53 @@ +// 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 WEBKIT_APPCACHE_APPCACHE_REQUEST_HANDLER_H_ +#define WEBKIT_APPCACHE_APPCACHE_REQUEST_HANDLER_H_ + +#include "net/url_request/url_request.h" +#include "webkit/appcache/appcache_host.h" + +class URLRequest; +class URLRequestJob; + +namespace appcache { + +// An instance is created for each URLRequest. The instance survives all +// http transactions involved in the processing of its 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 AppCacheRequestHandler : public URLRequest::UserData { + public: + // Should be called on each request intercept opportunity. + URLRequestJob* MaybeLoadResource(URLRequest* request); + URLRequestJob* MaybeLoadFallbackForRedirect(URLRequest* request, + const GURL& location); + URLRequestJob* MaybeLoadFallbackForResponse(URLRequest* request); + + void GetExtraResponseInfo(int64* cache_id, GURL* manifest_url); + + private: + friend class AppCacheHost; + + // Ctor for main resource loads. + explicit AppCacheRequestHandler(AppCacheHost* host); + + // Ctor for subresource loads when the cache is loaded. + explicit AppCacheRequestHandler(AppCache* cache); + + // Main vs subresource loads are very different. + // TODO(michaeln): maybe have two derived classes? + bool is_main_request_; + int64 cache_id_; + scoped_refptr<AppCache> cache_; + base::WeakPtr<AppCacheHost> host_; + scoped_refptr<URLRequestJob> job_; + AppCacheService* service_; +}; + +} // namespace appcache + +#endif // WEBKIT_APPCACHE_APPCACHE_REQUEST_HANDLER_H_ + diff --git a/webkit/appcache/appcache_service.cc b/webkit/appcache/appcache_service.cc index 029c4ef..6de7332 100644 --- a/webkit/appcache/appcache_service.cc +++ b/webkit/appcache/appcache_service.cc @@ -5,8 +5,10 @@ #include "webkit/appcache/appcache_service.h" #include "base/logging.h" +#include "base/ref_counted.h" #include "webkit/appcache/appcache.h" #include "webkit/appcache/appcache_backend_impl.h" +#include "webkit/appcache/appcache_entry.h" #include "webkit/appcache/appcache_group.h" namespace appcache { @@ -60,4 +62,39 @@ void AppCacheService::RemoveGroup(AppCacheGroup* group) { groups_.erase(group->manifest_url()); } +void AppCacheService::LoadCache(int64 id, LoadClient* client) { + // TODO(michaeln): actually retrieve from storage if needed + client->CacheLoadedCallback(GetCache(id), id); +} + +void AppCacheService::LoadOrCreateGroup(const GURL& manifest_url, + LoadClient* client) { + // TODO(michaeln): actually retrieve from storage + scoped_refptr<AppCacheGroup> group = GetGroup(manifest_url); + if (!group.get()) { + group = new AppCacheGroup(this, manifest_url); + DCHECK(GetGroup(manifest_url)); + } + client->GroupLoadedCallback(group.get(), manifest_url); +} + +void AppCacheService::CancelLoads(LoadClient* client) { + // TODO(michaeln): remove client from loading lists +} + +void AppCacheService::MarkAsForeignEntry(const GURL& entry_url, + int64 cache_id) { + // Update the in-memory cache. + AppCache* cache = GetCache(cache_id); + if (cache) { + AppCacheEntry* entry = cache->GetEntry(entry_url); + DCHECK(entry); + if (entry) + entry->add_types(AppCacheEntry::FOREIGN); + } + + // TODO(michaeln): actually update in storage, and if this cache is + // being loaded be sure to update the memory cache upon load completion. +} + } // namespace appcache diff --git a/webkit/appcache/appcache_service.h b/webkit/appcache/appcache_service.h index 0ccad80..0d7609d 100644 --- a/webkit/appcache/appcache_service.h +++ b/webkit/appcache/appcache_service.h @@ -12,6 +12,7 @@ #include "base/hash_tables.h" #include "base/file_path.h" #include "base/ref_counted.h" +#include "base/task.h" #include "net/url_request/url_request_context.h" #include "googleurl/src/gurl.h" @@ -26,6 +27,17 @@ class AppCacheGroup; // exclusive access to it's cache_directory on disk. class AppCacheService { public: + + class LoadClient { + public: + virtual ~LoadClient() {} + + // If a load fails the object pointer will be NULL. + virtual void CacheLoadedCallback(AppCache* cache, int64 cache_id) = 0; + virtual void GroupLoadedCallback(AppCacheGroup* cache, + const GURL& manifest_url) = 0; + }; + AppCacheService(); virtual ~AppCacheService(); @@ -42,30 +54,41 @@ class AppCacheService { // TODO(jennb): API to set service settings, like file paths for storage - // track which processes are using this appcache service + // Track which processes are using this appcache service. void RegisterBackend(AppCacheBackendImpl* backend_impl); void UnregisterBackend(AppCacheBackendImpl* backend_impl); - - void AddCache(AppCache* cache); - void RemoveCache(AppCache* cache); - void AddGroup(AppCacheGroup* group); - void RemoveGroup(AppCacheGroup* group); - AppCacheBackendImpl* GetBackend(int id) { BackendMap::iterator it = backends_.find(id); return (it != backends_.end()) ? it->second : NULL; } + // Track what we have in or in-memory cache. + void AddCache(AppCache* cache); + void RemoveCache(AppCache* cache); + void AddGroup(AppCacheGroup* group); + void RemoveGroup(AppCacheGroup* group); AppCache* GetCache(int64 id) { CacheMap::iterator it = caches_.find(id); return (it != caches_.end()) ? it->second : NULL; } - AppCacheGroup* GetGroup(const GURL& manifest_url) { GroupMap::iterator it = groups_.find(manifest_url); return (it != groups_.end()) ? it->second : NULL; } + // Load caches and groups from storage. If the request object + // is already in memory, the client is called immediately + // without returning to the message loop. + void LoadCache(int64 id, LoadClient* client); + void LoadOrCreateGroup(const GURL& manifest_url, + LoadClient* client); + + // Cancels pending callbacks for this client. + void CancelLoads(LoadClient* client); + + // Updates in memory and persistent storage. + void MarkAsForeignEntry(const GURL& entry_url, int64 cache_id); + // The service generates unique storage ids for different object types. int64 NewCacheId() { return ++last_cache_id_; } int64 NewGroupId() { return ++last_group_id_; } @@ -90,15 +113,14 @@ class AppCacheService { typedef std::map<int, AppCacheBackendImpl*> BackendMap; BackendMap backends_; + // Where we save our data. FilePath cache_directory_; // Context for use during cache updates. scoped_refptr<URLRequestContext> request_context_; - // TODO(jennb): info about appcache storage - // AppCacheDatabase db_; - // DiskCache response_storage_; - + // TODO(michaeln): cache and group loading book keeping. + // TODO(michaeln): database and response storage // TODO(jennb): service state: e.g. reached quota? }; diff --git a/webkit/appcache/appcache_service_unittest.cc b/webkit/appcache/appcache_service_unittest.cc index c58374f..d142e8b 100644 --- a/webkit/appcache/appcache_service_unittest.cc +++ b/webkit/appcache/appcache_service_unittest.cc @@ -7,17 +7,11 @@ #include "webkit/appcache/appcache_group.h" #include "webkit/appcache/appcache_service.h" -using appcache::AppCache; -using appcache::AppCacheGroup; -using appcache::AppCacheService; - -namespace { +namespace appcache { class AppCacheServiceTest : public testing::Test { }; -} // namespace - TEST(AppCacheServiceTest, AddRemoveCache) { AppCacheService service; scoped_refptr<AppCache> cache = new AppCache(&service, 111); @@ -31,10 +25,12 @@ TEST(AppCacheServiceTest, AddRemoveCache) { TEST(AppCacheServiceTest, AddRemoveGroup) { AppCacheService service; scoped_refptr<AppCacheGroup> group = - new AppCacheGroup(&service, GURL::EmptyGURL()); + new AppCacheGroup(&service, GURL::EmptyGURL()); service.RemoveGroup(group); // Removing non-existing group from service should not fail. AppCacheService dummy; dummy.RemoveGroup(group); } + +} // namespace appcache diff --git a/webkit/appcache/appcache_unittest.cc b/webkit/appcache/appcache_unittest.cc index 951cb8b..b5c1772 100644 --- a/webkit/appcache/appcache_unittest.cc +++ b/webkit/appcache/appcache_unittest.cc @@ -4,33 +4,29 @@ #include "testing/gtest/include/gtest/gtest.h" #include "webkit/appcache/appcache.h" +#include "webkit/appcache/appcache_frontend_impl.h" #include "webkit/appcache/appcache_host.h" #include "webkit/appcache/appcache_service.h" -using appcache::AppCache; -using appcache::AppCacheEntry; -using appcache::AppCacheHost; -using appcache::AppCacheService; - -namespace { +namespace appcache { class AppCacheTest : public testing::Test { }; -} // namespace - TEST(AppCacheTest, CleanupUnusedCache) { AppCacheService service; + AppCacheFrontendImpl frontend; AppCache* cache = new AppCache(&service, 111); + cache->set_complete(true); - AppCacheHost host1(1, NULL); - AppCacheHost host2(2, NULL); + AppCacheHost host1(1, &frontend, &service); + AppCacheHost host2(2, &frontend, &service); - host1.set_selected_cache(cache); - host2.set_selected_cache(cache); + host1.AssociateCache(cache); + host2.AssociateCache(cache); - host1.set_selected_cache(NULL); - host2.set_selected_cache(NULL); + host1.AssociateCache(NULL); + host2.AssociateCache(NULL); } TEST(AppCacheTest, AddModifyEntry) { @@ -53,3 +49,6 @@ TEST(AppCacheTest, AddModifyEntry) { cache->GetEntry(kUrl1)->types()); EXPECT_EQ(entry2.types(), cache->GetEntry(kUrl2)->types()); // unchanged } + +} // namespace appacache + diff --git a/webkit/appcache/manifest_parser_unittest.cc b/webkit/appcache/manifest_parser_unittest.cc index bcc145b..9fb42e0 100644 --- a/webkit/appcache/manifest_parser_unittest.cc +++ b/webkit/appcache/manifest_parser_unittest.cc @@ -8,17 +8,11 @@ #include "testing/gtest/include/gtest/gtest.h" #include "webkit/appcache/manifest_parser.h" -using appcache::FallbackNamespace; -using appcache::Manifest; -using appcache::ParseManifest; - -namespace { +namespace appcache { class ManifestParserTest : public testing::Test { }; -} // namespace - TEST(ManifestParserTest, NoData) { GURL url; Manifest manifest; @@ -306,3 +300,6 @@ TEST(ManifestParserTest, UnusualUtf8) { EXPECT_TRUE(urls.find("http://bad.com/%EF%BF%BDinvalidutf8") != urls.end()); EXPECT_TRUE(urls.find("http://bad.com/nonbmp%F1%84%AB%BC") != urls.end()); } + +} // namespace appcache + diff --git a/webkit/appcache/web_application_cache_host_impl.cc b/webkit/appcache/web_application_cache_host_impl.cc index 379a80d..dfba39b 100644 --- a/webkit/appcache/web_application_cache_host_impl.cc +++ b/webkit/appcache/web_application_cache_host_impl.cc @@ -104,12 +104,15 @@ bool WebApplicationCacheHostImpl::selectCacheWithManifest( return true; } + DCHECK(should_capture_main_response_ == NO); + // Check for 'foreign' entries. GURL main_response_manifest_gurl(main_response_.appCacheManifestURL()); if (main_response_manifest_gurl != manifest_gurl) { backend_->MarkAsForeignEntry(host_id_, main_response_url_, main_response_.appCacheID()); - selectCacheWithoutManifest(); + has_cached_status_ = true; + cached_status_ = UNCACHED; return false; // the navigation will be restarted } |