diff options
author | jennb@chromium.org <jennb@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-10-06 17:19:37 +0000 |
---|---|---|
committer | jennb@chromium.org <jennb@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-10-06 17:19:37 +0000 |
commit | ae3c0b266d4a3f7f2f121589e24e76528d44f4f7 (patch) | |
tree | 85af04a19120db4a75b1ad07ca2a6e1473f1767e /webkit/appcache | |
parent | ff9cfce7ec2ae06df4ac413cfde3bfd7ae503677 (diff) | |
download | chromium_src-ae3c0b266d4a3f7f2f121589e24e76528d44f4f7.zip chromium_src-ae3c0b266d4a3f7f2f121589e24e76528d44f4f7.tar.gz chromium_src-ae3c0b266d4a3f7f2f121589e24e76528d44f4f7.tar.bz2 |
Implementation of application cache update algorithm.
Does not include storage nor processing of pending master entries.
TEST=verify update functionality
BUG=none
Review URL: http://codereview.chromium.org/201070
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@28123 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'webkit/appcache')
32 files changed, 2255 insertions, 28 deletions
diff --git a/webkit/appcache/appcache.cc b/webkit/appcache/appcache.cc index 5e7bca63..450f978 100644 --- a/webkit/appcache/appcache.cc +++ b/webkit/appcache/appcache.cc @@ -7,17 +7,17 @@ #include "base/logging.h" #include "webkit/appcache/appcache_group.h" #include "webkit/appcache/appcache_host.h" +#include "webkit/appcache/appcache_interfaces.h" #include "webkit/appcache/appcache_service.h" namespace appcache { AppCache::AppCache(AppCacheService *service, int64 cache_id) - : cache_id_(cache_id), - manifest_(NULL), - owning_group_(NULL), - online_whitelist_all_(false), - is_complete_(false), - service_(service) { + : cache_id_(cache_id), + owning_group_(NULL), + online_whitelist_all_(false), + is_complete_(false), + service_(service) { service_->AddCache(this); } @@ -42,7 +42,7 @@ void AppCache::AddEntry(const GURL& url, const AppCacheEntry& entry) { void AppCache::AddOrModifyEntry(const GURL& url, const AppCacheEntry& entry) { std::pair<EntryMap::iterator, bool> ret = - entries_.insert(EntryMap::value_type(url, entry)); + entries_.insert(EntryMap::value_type(url, entry)); // Entry already exists. Merge the types of the new and existing entries. if (!ret.second) @@ -54,4 +54,11 @@ AppCacheEntry* AppCache::GetEntry(const GURL& url) { return (it != entries_.end()) ? &(it->second) : NULL; } +void AppCache::InitializeWithManifest(Manifest* manifest) { + DCHECK(manifest); + fallback_namespaces_.swap(manifest->fallback_namespaces); + online_whitelist_namespaces_.swap(manifest->online_whitelist_namespaces); + online_whitelist_all_ = manifest->online_whitelist_all; +} + } // namespace appcache diff --git a/webkit/appcache/appcache.h b/webkit/appcache/appcache.h index fcfa9d1..08fdd1b 100644 --- a/webkit/appcache/appcache.h +++ b/webkit/appcache/appcache.h @@ -13,6 +13,7 @@ #include "base/ref_counted.h" #include "base/time.h" #include "googleurl/src/gurl.h" +#include "testing/gtest/include/gtest/gtest_prod.h" #include "webkit/appcache/appcache_entry.h" #include "webkit/appcache/manifest_parser.h" @@ -27,6 +28,8 @@ class AppCacheService; // cache is being created during an appcache upate. class AppCache : public base::RefCounted<AppCache> { public: + typedef std::map<GURL, AppCacheEntry> EntryMap; + AppCache(AppCacheService *service, int64 cache_id); ~AppCache(); @@ -50,19 +53,27 @@ class AppCache : public base::RefCounted<AppCache> { // Do not store the returned object as it could be deleted anytime. AppCacheEntry* GetEntry(const GURL& url); - typedef std::map<GURL, AppCacheEntry> EntryMap; const EntryMap& entries() const { return entries_; } + const std::set<AppCacheHost*>& associated_hosts() const { + return associated_hosts_; + } + bool IsNewerThan(AppCache* cache) const { return update_time_ > cache->update_time_; } - void set_update_time(base::TimeTicks ticks = base::TimeTicks::Now()) { + void set_update_time(base::TimeTicks ticks) { update_time_ = ticks; } + // Initializes the cache with information in the manifest. + // Do not use the manifest after this call. + void InitializeWithManifest(Manifest* manifest); + private: friend class AppCacheHost; + friend class AppCacheUpdateJobTest; // Use AppCacheHost::AssociateCache() to manipulate host association. void AssociateHost(AppCacheHost* host) { @@ -71,7 +82,6 @@ class AppCache : public base::RefCounted<AppCache> { void UnassociateHost(AppCacheHost* host); const int64 cache_id_; - AppCacheEntry* manifest_; // also in entry map AppCacheGroup* owning_group_; std::set<AppCacheHost*> associated_hosts_; @@ -88,6 +98,9 @@ class AppCache : public base::RefCounted<AppCache> { // to notify service when cache is deleted AppCacheService* service_; + + FRIEND_TEST(AppCacheTest, InitializeWithManifest); + DISALLOW_COPY_AND_ASSIGN(AppCache); }; } // namespace appcache diff --git a/webkit/appcache/appcache_group.cc b/webkit/appcache/appcache_group.cc index 10307c2..67ca4cc 100644 --- a/webkit/appcache/appcache_group.cc +++ b/webkit/appcache/appcache_group.cc @@ -10,21 +10,24 @@ #include "webkit/appcache/appcache.h" #include "webkit/appcache/appcache_host.h" #include "webkit/appcache/appcache_service.h" +#include "webkit/appcache/appcache_update_job.h" namespace appcache { AppCacheGroup::AppCacheGroup(AppCacheService* service, const GURL& manifest_url) - : manifest_url_(manifest_url), - update_status_(IDLE), - is_obsolete_(false), - newest_complete_cache_(NULL), - service_(service) { + : manifest_url_(manifest_url), + update_status_(IDLE), + is_obsolete_(false), + newest_complete_cache_(NULL), + update_job_(NULL), + service_(service) { service_->AddGroup(this); } AppCacheGroup::~AppCacheGroup() { DCHECK(old_caches_.empty()); + DCHECK(!update_job_); // Newest complete cache might never have been associated with a host // and thus would not be cleaned up by the backend impl during shutdown. @@ -34,6 +37,14 @@ AppCacheGroup::~AppCacheGroup() { service_->RemoveGroup(this); } +void AppCacheGroup::AddObserver(Observer* observer) { + observers_.AddObserver(observer); +} + +void AppCacheGroup::RemoveObserver(Observer* observer) { + observers_.RemoveObserver(observer); +} + void AppCacheGroup::AddCache(AppCache* complete_cache) { DCHECK(complete_cache->is_complete()); if (!newest_complete_cache_) { @@ -51,7 +62,6 @@ void AppCacheGroup::AddCache(AppCache* complete_cache) { bool AppCacheGroup::RemoveCache(AppCache* cache) { if (cache == newest_complete_cache_) { - // Cannot remove newest cache if there are older caches as those may // eventually be swapped to the newest cache. if (!old_caches_.empty()) @@ -73,8 +83,27 @@ bool AppCacheGroup::RemoveCache(AppCache* cache) { } void AppCacheGroup::StartUpdateWithNewMasterEntry( - AppCacheHost* host, const GURL& master_entry_url) { - // TODO(michaeln): use the real AppCacheUpdateJob + AppCacheHost* host, const GURL& new_master_resource) { + /* TODO(jennb): enable after have logic for cancelling an update + if (!update_job_) + update_job_ = new AppCacheUpdateJob(service_, this); + + update_job_->StartUpdate(host, new_master_resource); + */ +} + +void AppCacheGroup::SetUpdateStatus(UpdateStatus status) { + if (status == update_status_) + return; + + update_status_ = status; + + if (status != IDLE) { + DCHECK(update_job_); + } else { + update_job_ = NULL; + FOR_EACH_OBSERVER(Observer, observers_, OnUpdateComplete(this)); + } } } // namespace appcache diff --git a/webkit/appcache/appcache_group.h b/webkit/appcache/appcache_group.h index 20422ae..4230e8d 100644 --- a/webkit/appcache/appcache_group.h +++ b/webkit/appcache/appcache_group.h @@ -7,20 +7,30 @@ #include <vector> +#include "base/observer_list.h" #include "base/ref_counted.h" #include "googleurl/src/gurl.h" +#include "testing/gtest/include/gtest/gtest_prod.h" namespace appcache { class AppCache; class AppCacheHost; class AppCacheService; +class AppCacheUpdateJob; // 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 AppCacheGroup : public base::RefCounted<AppCacheGroup> { public: + class Observer { + public: + // Called just after an appcache update has completed. + virtual void OnUpdateComplete(AppCacheGroup* group) = 0; + virtual ~Observer() { } + }; + enum UpdateStatus { IDLE, CHECKING, @@ -30,10 +40,12 @@ class AppCacheGroup : public base::RefCounted<AppCacheGroup> { AppCacheGroup(AppCacheService* service, const GURL& manifest_url); ~AppCacheGroup(); - const GURL& manifest_url() { return manifest_url_; } + // Adds/removes an observer, the AppCacheGroup does not take + // ownership of the observer. + void AddObserver(Observer* observer); + void RemoveObserver(Observer* observer); - UpdateStatus update_status() { return update_status_; } - void set_update_status(UpdateStatus status) { update_status_ = status; } + const GURL& manifest_url() { return manifest_url_; } bool is_obsolete() { return is_obsolete_; } void set_obsolete(bool value) { is_obsolete_ = value; } @@ -46,6 +58,10 @@ class AppCacheGroup : public base::RefCounted<AppCacheGroup> { // cannot be removed as long as the group is still in use. bool RemoveCache(AppCache* cache); + bool HasCache() { return newest_complete_cache_ || !old_caches_.empty(); } + + UpdateStatus update_status() { return update_status_; } + // Starts an update via update() javascript API. void StartUpdate() { StartUpdateWithHost(NULL); @@ -63,19 +79,39 @@ class AppCacheGroup : public base::RefCounted<AppCacheGroup> { const GURL& new_master_resource); private: + friend class AppCacheUpdateJob; + friend class AppCacheUpdateJobTest; + + typedef std::vector<scoped_refptr<AppCache> > Caches; + + AppCacheUpdateJob* update_job() { return update_job_; } + void SetUpdateStatus(UpdateStatus status); + + const Caches& old_caches() { return old_caches_; } + GURL manifest_url_; UpdateStatus update_status_; bool is_obsolete_; - // old complete app caches - typedef std::vector<scoped_refptr<AppCache> > Caches; + // Old complete app caches. Caches old_caches_; - // newest cache in this group to be complete, aka relevant cache + // Newest cache in this group to be complete, aka relevant cache. scoped_refptr<AppCache> newest_complete_cache_; - // to notify service when group is no longer needed + // Current update job for this group, if any. + AppCacheUpdateJob* update_job_; + + // Central service object. AppCacheService* service_; + + // List of objects observing this group. + ObserverList<Observer> observers_; + + FRIEND_TEST(AppCacheGroupTest, StartUpdate); + FRIEND_TEST(AppCacheUpdateJobTest, AlreadyChecking); + FRIEND_TEST(AppCacheUpdateJobTest, AlreadyDownloading); + DISALLOW_COPY_AND_ASSIGN(AppCacheGroup); }; } // namespace appcache diff --git a/webkit/appcache/appcache_group_unittest.cc b/webkit/appcache/appcache_group_unittest.cc index bcfbce0..74846af 100644 --- a/webkit/appcache/appcache_group_unittest.cc +++ b/webkit/appcache/appcache_group_unittest.cc @@ -7,6 +7,7 @@ #include "webkit/appcache/appcache_group.h" #include "webkit/appcache/appcache_host.h" #include "webkit/appcache/appcache_service.h" +#include "webkit/appcache/appcache_update_job.h" namespace { @@ -130,4 +131,27 @@ TEST(AppCacheGroupTest, CleanupUnusedGroup) { EXPECT_EQ(frontend.last_status_, appcache::UNCACHED); } +TEST(AppCacheGroupTest, StartUpdate) { + /* TODO(jennb) - uncomment after AppCacheGroup::StartUpdate does something. + AppCacheService service; + scoped_refptr<AppCacheGroup> group = + new AppCacheGroup(&service, GURL("http://foo.com")); + + // Set state to checking to prevent update job from executing fetches. + group->update_status_ = AppCacheGroup::CHECKING; + group->StartUpdate(); + AppCacheUpdateJob* update = group->update_job_; + EXPECT_TRUE(update != NULL); + + // Start another update, check that same update job is in use. + group->StartUpdateWithHost(NULL); + EXPECT_EQ(update, group->update_job_); + + // Remove update job's reference to this group. + delete update; + EXPECT_TRUE(group->update_job_ == NULL); + EXPECT_EQ(AppCacheGroup::IDLE, group->update_status()); + */ +} + } // namespace appcache diff --git a/webkit/appcache/appcache_interfaces.cc b/webkit/appcache/appcache_interfaces.cc index 30bb208..5b24597 100644 --- a/webkit/appcache/appcache_interfaces.cc +++ b/webkit/appcache/appcache_interfaces.cc @@ -12,6 +12,8 @@ using WebKit::WebApplicationCacheHost; namespace appcache { +const char kManifestMimeType[] = "text/cache-manifest"; + const char kHttpScheme[] = "http"; const char kHttpsScheme[] = "https"; const char kHttpGETMethod[] = "GET"; diff --git a/webkit/appcache/appcache_interfaces.h b/webkit/appcache/appcache_interfaces.h index db38bcd..fcba1d8 100644 --- a/webkit/appcache/appcache_interfaces.h +++ b/webkit/appcache/appcache_interfaces.h @@ -16,6 +16,7 @@ namespace appcache { // Defines constants, types, and abstract classes used in the main // process and in child processes. +extern const char kManifestMimeType[]; static const int kNoHostId = 0; static const int64 kNoCacheId = 0; diff --git a/webkit/appcache/appcache_service.cc b/webkit/appcache/appcache_service.cc index e524c62..c36ad1e 100644 --- a/webkit/appcache/appcache_service.cc +++ b/webkit/appcache/appcache_service.cc @@ -61,6 +61,8 @@ void AppCacheService::AddGroup(AppCacheGroup* group) { void AppCacheService::RemoveGroup(AppCacheGroup* group) { groups_.erase(group->manifest_url()); + + // TODO(jennb): if group is obsolete, delete from storage. } void AppCacheService::LoadCache(int64 id, LoadClient* client) { diff --git a/webkit/appcache/appcache_service.h b/webkit/appcache/appcache_service.h index 9a504d9..4dff28e 100644 --- a/webkit/appcache/appcache_service.h +++ b/webkit/appcache/appcache_service.h @@ -53,8 +53,6 @@ class AppCacheService { request_context_ = context; } - // TODO(jennb): API to set service settings, like file paths for storage - // Track which processes are using this appcache service. void RegisterBackend(AppCacheBackendImpl* backend_impl); void UnregisterBackend(AppCacheBackendImpl* backend_impl); diff --git a/webkit/appcache/appcache_unittest.cc b/webkit/appcache/appcache_unittest.cc index b5c1772..3d33358 100644 --- a/webkit/appcache/appcache_unittest.cc +++ b/webkit/appcache/appcache_unittest.cc @@ -46,9 +46,46 @@ TEST(AppCacheTest, AddModifyEntry) { AppCacheEntry entry3(AppCacheEntry::EXPLICIT); cache->AddOrModifyEntry(kUrl1, entry3); EXPECT_EQ((AppCacheEntry::MASTER | AppCacheEntry::EXPLICIT), - cache->GetEntry(kUrl1)->types()); + cache->GetEntry(kUrl1)->types()); EXPECT_EQ(entry2.types(), cache->GetEntry(kUrl2)->types()); // unchanged } +TEST(AppCacheTest, InitializeWithManifest) { + AppCacheService service; + + scoped_refptr<AppCache> cache = new AppCache(&service, 1234); + EXPECT_TRUE(cache->fallback_namespaces_.empty()); + EXPECT_TRUE(cache->online_whitelist_namespaces_.empty()); + EXPECT_FALSE(cache->online_whitelist_all_); + + Manifest manifest; + manifest.explicit_urls.insert("http://one.com"); + manifest.explicit_urls.insert("http://two.com"); + manifest.fallback_namespaces.push_back( + FallbackNamespace(GURL("http://fb1.com"), GURL("http://fbone.com"))); + manifest.online_whitelist_namespaces.push_back(GURL("http://w1.com")); + manifest.online_whitelist_namespaces.push_back(GURL("http://w2.com")); + manifest.online_whitelist_all = true; + + cache->InitializeWithManifest(&manifest); + const std::vector<FallbackNamespace>& fallbacks = + cache->fallback_namespaces_; + size_t expected = 1; + EXPECT_EQ(expected, fallbacks.size()); + EXPECT_EQ(GURL("http://fb1.com"), fallbacks[0].first); + EXPECT_EQ(GURL("http://fbone.com"), fallbacks[0].second); + const std::vector<GURL>& whitelist = cache->online_whitelist_namespaces_; + expected = 2; + EXPECT_EQ(expected, whitelist.size()); + EXPECT_EQ(GURL("http://w1.com"), whitelist[0]); + EXPECT_EQ(GURL("http://w2.com"), whitelist[1]); + EXPECT_TRUE(cache->online_whitelist_all_); + + // Ensure collections in manifest were taken over by the cache rather than + // copied. + EXPECT_TRUE(manifest.fallback_namespaces.empty()); + EXPECT_TRUE(manifest.online_whitelist_namespaces.empty()); +} + } // namespace appacache diff --git a/webkit/appcache/appcache_update_job.cc b/webkit/appcache/appcache_update_job.cc new file mode 100644 index 0000000..7732faf --- /dev/null +++ b/webkit/appcache/appcache_update_job.cc @@ -0,0 +1,671 @@ +// 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_update_job.h" + +#include "base/compiler_specific.h" +#include "base/message_loop.h" +#include "net/base/io_buffer.h" +#include "net/base/load_flags.h" +#include "webkit/appcache/appcache_group.h" +#include "webkit/appcache/appcache_host.h" + +namespace appcache { + +static const int kBufferSize = 4096; +static const size_t kMaxConcurrentUrlFetches = 2; + +// Extra info associated with requests for use during response processing. +// This info is deleted when the URLRequest is deleted. +struct UpdateJobInfo : public URLRequest::UserData { + enum RequestType { + MANIFEST_FETCH, + URL_FETCH, + MANIFEST_REFETCH, + }; + + explicit UpdateJobInfo(RequestType request_type) + : type(request_type), + buffer(new net::IOBuffer(kBufferSize)) { + } + + RequestType type; + scoped_refptr<net::IOBuffer> buffer; + // TODO(jennb): need storage info to stream response data to storage +}; + +// 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(EventID 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); + } + } + + private: + NotifyHostMap hosts_to_notify; +}; + +AppCacheUpdateJob::AppCacheUpdateJob(AppCacheService* service, + AppCacheGroup* group) + : ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)), + service_(service), + group_(group), + update_type_(UNKNOWN_TYPE), + internal_state_(FETCH_MANIFEST), + master_entries_completed_(0), + url_fetches_completed_(0), + manifest_url_request_(NULL) { + DCHECK(group_); + manifest_url_ = group_->manifest_url(); +} + +AppCacheUpdateJob::~AppCacheUpdateJob() { + Cancel(); + + DCHECK(!manifest_url_request_); + DCHECK(pending_url_fetches_.empty()); + + group_->SetUpdateStatus(AppCacheGroup::IDLE); +} + +void AppCacheUpdateJob::StartUpdate(AppCacheHost* host, + const GURL& new_master_resource) { + DCHECK(group_->update_job() == this); + + if (!new_master_resource.is_empty()) { + /* TODO(jennb): uncomment when processing master entries is implemented + std::pair<PendingMasters::iterator, bool> ret = + pending_master_entries_.insert( + PendingMasters::value_type(new_master_resource, PendingHosts())); + ret.first->second.push_back(host); + */ + } + + // Notify host (if any) if already checking or downloading. + appcache::AppCacheGroup::UpdateStatus update_status = group_->update_status(); + if (update_status == AppCacheGroup::CHECKING || + update_status == AppCacheGroup::DOWNLOADING) { + if (host) { + NotifySingleHost(host, CHECKING_EVENT); + if (update_status == AppCacheGroup::DOWNLOADING) + NotifySingleHost(host, DOWNLOADING_EVENT); + } + return; + } + + // Begin update process for the group. + group_->SetUpdateStatus(AppCacheGroup::CHECKING); + if (group_->HasCache()) { + update_type_ = UPGRADE_ATTEMPT; + NotifyAllAssociatedHosts(CHECKING_EVENT); + } else { + update_type_ = CACHE_ATTEMPT; + DCHECK(host); + NotifySingleHost(host, CHECKING_EVENT); + } + + FetchManifest(true); +} + +void AppCacheUpdateJob::FetchManifest(bool is_first_fetch) { + DCHECK(!manifest_url_request_); + manifest_url_request_ = new URLRequest(manifest_url_, this); + UpdateJobInfo::RequestType fetch_type = is_first_fetch ? + UpdateJobInfo::MANIFEST_FETCH : UpdateJobInfo::MANIFEST_REFETCH; + manifest_url_request_->SetUserData(this, new UpdateJobInfo(fetch_type)); + manifest_url_request_->set_context(service_->request_context()); + // TODO(jennb): add "If-Modified-Since" if have previous date + manifest_url_request_->set_load_flags( + manifest_url_request_->load_flags() | net::LOAD_DISABLE_INTERCEPT); + manifest_url_request_->Start(); +} + +void AppCacheUpdateJob::OnResponseStarted(URLRequest *request) { + if (request->status().is_success()) + ReadResponseData(request); + else + OnResponseCompleted(request); +} + +void AppCacheUpdateJob::ReadResponseData(URLRequest* request) { + if (internal_state_ == CACHE_FAILURE) + return; + + int bytes_read = 0; + UpdateJobInfo* info = + static_cast<UpdateJobInfo*>(request->GetUserData(this)); + request->Read(info->buffer, kBufferSize, &bytes_read); + OnReadCompleted(request, bytes_read); +} + +void AppCacheUpdateJob::OnReadCompleted(URLRequest* request, int bytes_read) { + bool data_consumed = true; + if (request->status().is_success() && bytes_read > 0) { + UpdateJobInfo* info = + static_cast<UpdateJobInfo*>(request->GetUserData(this)); + + data_consumed = ConsumeResponseData(request, info, bytes_read); + if (data_consumed) { + bytes_read = 0; + while (request->Read(info->buffer, kBufferSize, &bytes_read)) { + if (bytes_read > 0) { + data_consumed = ConsumeResponseData(request, info, bytes_read); + if (!data_consumed) + break; // wait for async data processing, then read more + } else { + break; + } + } + } + } + + if (data_consumed && !request->status().is_io_pending()) + OnResponseCompleted(request); +} + +bool AppCacheUpdateJob::ConsumeResponseData(URLRequest* request, + UpdateJobInfo* info, + int bytes_read) { + switch (info->type) { + case UpdateJobInfo::MANIFEST_FETCH: + manifest_data_.append(info->buffer->data(), bytes_read); + break; + case UpdateJobInfo::URL_FETCH: + // TODO(jennb): stream data to storage. will be async so need to wait + // for callback before reading next chunk. + // For now, schedule a task to continue reading to simulate async-ness. + MessageLoop::current()->PostTask(FROM_HERE, + method_factory_.NewRunnableMethod( + &AppCacheUpdateJob::ReadResponseData, request)); + return false; + case UpdateJobInfo::MANIFEST_REFETCH: + manifest_refetch_data_.append(info->buffer->data(), bytes_read); + break; + default: + NOTREACHED(); + } + return true; +} + +void AppCacheUpdateJob::OnReceivedRedirect(URLRequest* request, + const GURL& new_url, + bool* defer_redirect) { + // Redirect is not allowed by the update process. + request->Cancel(); + OnResponseCompleted(request); +} + +void AppCacheUpdateJob::OnResponseCompleted(URLRequest* request) { + // TODO(jennb): think about retrying for 503s where retry-after is 0 + UpdateJobInfo* info = + static_cast<UpdateJobInfo*>(request->GetUserData(this)); + switch (info->type) { + case UpdateJobInfo::MANIFEST_FETCH: + HandleManifestFetchCompleted(request); + break; + case UpdateJobInfo::URL_FETCH: + HandleUrlFetchCompleted(request); + break; + case UpdateJobInfo::MANIFEST_REFETCH: + HandleManifestRefetchCompleted(request); + break; + default: + NOTREACHED(); + } + + delete request; +} + +void AppCacheUpdateJob::HandleManifestFetchCompleted(URLRequest* request) { + DCHECK(internal_state_ == FETCH_MANIFEST); + manifest_url_request_ = NULL; + + if (!request->status().is_success()) { + LOG(INFO) << "Request non-success, status: " << request->status().status() + << " os_error: " << request->status().os_error(); + internal_state_ = CACHE_FAILURE; + MaybeCompleteUpdate(); // if not done, run async cache failure steps + return; + } + + int response_code = request->GetResponseCode(); + std::string mime_type; + request->GetMimeType(&mime_type); + + if (response_code == 200 && mime_type == kManifestMimeType) { + 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) { + group_->set_obsolete(true); + NotifyAllAssociatedHosts(OBSOLETE_EVENT); + NotifyAllPendingMasterHosts(ERROR_EVENT); + DeleteSoon(); + } else { + LOG(INFO) << "Cache failure, response code: " << response_code; + internal_state_ = CACHE_FAILURE; + MaybeCompleteUpdate(); // if not done, run async cache failure steps + } +} + +void AppCacheUpdateJob::ContinueHandleManifestFetchCompleted(bool changed) { + DCHECK(internal_state_ == FETCH_MANIFEST); + + if (!changed) { + DCHECK(update_type_ == UPGRADE_ATTEMPT); + internal_state_ = NO_UPDATE; + 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)) { + LOG(INFO) << "Failed to parse manifest: " << manifest_url_; + internal_state_ = CACHE_FAILURE; + MaybeCompleteUpdate(); // if not done, run async cache failure steps + return; + } + + // Proceed with update process. Section 6.9.4 steps 8-20. + internal_state_ = DOWNLOADING; + inprogress_cache_ = new AppCache(service_, service_->NewCacheId()); + inprogress_cache_->set_owning_group(group_); + 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) { + AppCacheHost* host = *host_it; + host->AssociateCache(inprogress_cache_); + } + } + + group_->SetUpdateStatus(AppCacheGroup::DOWNLOADING); + NotifyAllAssociatedHosts(DOWNLOADING_EVENT); + FetchUrls(); + MaybeCompleteUpdate(); // if not done, continues when async fetches complete +} + +void AppCacheUpdateJob::HandleUrlFetchCompleted(URLRequest* request) { + DCHECK(internal_state_ == DOWNLOADING); + + const GURL& url = request->original_url(); + pending_url_fetches_.erase(url); + ++url_fetches_completed_; + + int response_code = request->GetResponseCode(); + AppCacheEntry& entry = url_file_list_.find(url)->second; + + if (request->status().is_success() && response_code == 200) { + // TODO(jennb): associate storage with the new entry + inprogress_cache_->AddEntry(url, entry); + + // 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 { + LOG(INFO) << "Request status: " << request->status().status() + << " os_error: " << request->status().os_error() + << " response code: " << response_code; + + // TODO(jennb): discard any stored data for this entry + if (entry.IsExplicit() || entry.IsFallback()) { + internal_state_ = CACHE_FAILURE; + + // Cancel any pending URL requests. + for (PendingUrlFetches::iterator it = pending_url_fetches_.begin(); + it != pending_url_fetches_.end(); ++it) { + it->second->Cancel(); + delete it->second; + } + + url_fetches_completed_ += + pending_url_fetches_.size() + urls_to_fetch_.size(); + pending_url_fetches_.clear(); + urls_to_fetch_.clear(); + } else if (response_code == 404 || response_code == 410) { + // Entry is skipped. They are dropped from the cache. + } else if (update_type_ == UPGRADE_ATTEMPT) { + // Copy the resource and its metadata from the newest complete cache. + AppCache* cache = group_->newest_complete_cache(); + AppCacheEntry* copy = cache->GetEntry(url); + if (copy) + CopyEntryToCache(url, *copy, &entry); + } + } + + // Fetch another URL now that one request has completed. + if (internal_state_ != CACHE_FAILURE) + FetchUrls(); + + MaybeCompleteUpdate(); +} + +void AppCacheUpdateJob::HandleManifestRefetchCompleted(URLRequest* request) { + DCHECK(internal_state_ == REFETCH_MANIFEST); + manifest_url_request_ = NULL; + + int response_code = request->GetResponseCode(); + if (response_code == 304 || manifest_data_ == manifest_refetch_data_) { + AppCacheEntry entry(AppCacheEntry::MANIFEST); + // TODO(jennb): add manifest_data_ to storage and put storage key in entry + // Also store response headers from request for HTTP cache control. + inprogress_cache_->AddOrModifyEntry(manifest_url_, entry); + inprogress_cache_->set_update_time(base::TimeTicks::Now()); + + // TODO(jennb): start of part to make async (cache storage may fail; + // group storage may fail) + inprogress_cache_->set_complete(true); + + // TODO(jennb): write new cache to storage here + group_->AddCache(inprogress_cache_); + // TODO(jennb): write new group to storage here + inprogress_cache_ = NULL; + + if (update_type_ == CACHE_ATTEMPT) { + NotifyAllAssociatedHosts(CACHED_EVENT); + } else { + NotifyAllAssociatedHosts(UPDATE_READY_EVENT); + } + DeleteSoon(); + // TODO(jennb): end of part that needs to be made async. + } else { + LOG(INFO) << "Request status: " << request->status().status() + << " os_error: " << request->status().os_error() + << " response code: " << response_code; + ScheduleUpdateRetry(kRerunDelayMs); + internal_state_ = CACHE_FAILURE; + HandleCacheFailure(); + } +} + +void AppCacheUpdateJob::NotifySingleHost(AppCacheHost* host, + EventID event_id) { + std::vector<int> ids(1, host->host_id()); + host->frontend()->OnEventRaised(ids, event_id); +} + +void AppCacheUpdateJob::NotifyAllPendingMasterHosts(EventID event_id) { + // Collect hosts so we only send one notification per frontend. + // A host can only be associated with a single pending master entry + // so no need to worry about duplicate hosts being added to the notifier. + HostNotifier host_notifier; + 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) { + AppCacheHost* host = *host_it; + host_notifier.AddHost(host); + } + } + + host_notifier.SendNotifications(event_id); +} + +void AppCacheUpdateJob::NotifyAllAssociatedHosts(EventID event_id) { + // 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. + HostNotifier host_notifier; + if (inprogress_cache_) { + 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()); + + // TODO(jennb): if progress event, also pass params lengthComputable=true, + // total = url_file_list_.size(), loaded=url_fetches_completed_. + host_notifier.SendNotifications(event_id); +} + +void AppCacheUpdateJob::CheckIfManifestChanged() { + DCHECK(update_type_ == UPGRADE_ATTEMPT); + /* + AppCacheEntry* entry = + group_->newest_complete_cache()->GetEntry(manifest_url_); + */ + // TODO(jennb): load manifest data from entry (async), continues in callback + // callback invokes ContinueCheckIfManifestChanged + // For now, schedule a task to continue checking with fake loaded data + MessageLoop::current()->PostTask(FROM_HERE, + method_factory_.NewRunnableMethod( + &AppCacheUpdateJob::ContinueCheckIfManifestChanged, + simulate_manifest_changed_ ? "different" : manifest_data_)); +} + +void AppCacheUpdateJob::ContinueCheckIfManifestChanged( + const std::string& loaded_manifest) { + ContinueHandleManifestFetchCompleted(manifest_data_ != loaded_manifest); +} + +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<FallbackNamespace>& fallbacks = + manifest.fallback_namespaces; + for (std::vector<FallbackNamespace>::const_iterator it = fallbacks.begin(); + it != fallbacks.end(); ++it) { + AddUrlToFileList(it->second, 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)); + 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()) { + // Notify about progress first to ensure it starts from 0% in case any + // entries are skipped. + NotifyAllAssociatedHosts(PROGRESS_EVENT); + + const GURL url = urls_to_fetch_.front().first; + bool storage_checked = urls_to_fetch_.front().second; + urls_to_fetch_.pop_front(); + + AppCache::EntryMap::iterator it = url_file_list_.find(url); + DCHECK(it != url_file_list_.end()); + AppCacheEntry& entry = it->second; + if (ShouldSkipUrlFetch(entry)) { + ++url_fetches_completed_; + } else if (!storage_checked && MaybeLoadFromNewestCache(url, entry)) { + // Continues asynchronously after data is loaded from newest cache. + } else { + // Send URL request for the resource. + URLRequest* request = new URLRequest(url, this); + request->SetUserData(this, new UpdateJobInfo(UpdateJobInfo::URL_FETCH)); + request->set_context(service_->request_context()); + request->set_load_flags( + request->load_flags() | net::LOAD_DISABLE_INTERCEPT); + request->Start(); + pending_url_fetches_.insert(PendingUrlFetches::value_type(url, request)); + } + } +} + +bool AppCacheUpdateJob::ShouldSkipUrlFetch(const AppCacheEntry& entry) { + if (entry.IsExplicit() || entry.IsFallback()) { + return false; + } + + // TODO(jennb): decide if entry should be skipped to expire it from cache + return false; +} + +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) + return false; + + // TODO(jennb): load HTTP headers for copy_me and wait for callback + // In callback: + // if HTTP caching semantics for entry allows its use, + // CopyEntryData(url, copy_me, entry) + // ++urls_fetches_completed_; + // Else, add url back to front of urls_to_fetch and call FetchUrls(). + // flag url somehow so that FetchUrls() doesn't end up here again. + // For now: post a message to pretend entry could not be copied + MessageLoop::current()->PostTask(FROM_HERE, + method_factory_.NewRunnableMethod( + &AppCacheUpdateJob::SimulateFailedLoadFromNewestCache, url)); + return true; +} + +// TODO(jennb): delete this after have real storage code +void AppCacheUpdateJob::SimulateFailedLoadFromNewestCache(const GURL& url) { + if (internal_state_ == CACHE_FAILURE) + return; + + // Re-insert url at front of fetch list. Indicate storage has been checked. + urls_to_fetch_.push_front(AppCacheUpdateJob::UrlToFetch(url, true)); + FetchUrls(); +} + +void AppCacheUpdateJob::CopyEntryToCache(const GURL& url, + const AppCacheEntry& src, + AppCacheEntry* dest) { + DCHECK(dest); + // TODO(jennb): copy storage key from src to dest + inprogress_cache_->AddEntry(url, *dest); +} + +bool AppCacheUpdateJob::MaybeCompleteUpdate() { + if (master_entries_completed_ != pending_master_entries_.size() || + url_fetches_completed_ != url_file_list_.size() ) { + return false; + } + + switch (internal_state_) { + case NO_UPDATE: + // 6.9.4 steps 7.3-7.7. + NotifyAllAssociatedHosts(NO_UPDATE_EVENT); + pending_master_entries_.clear(); + DeleteSoon(); + break; + case DOWNLOADING: + internal_state_ = REFETCH_MANIFEST; + FetchManifest(false); + return false; + case CACHE_FAILURE: + HandleCacheFailure(); + break; + default: + NOTREACHED(); + } + return true; +} + +void AppCacheUpdateJob::HandleCacheFailure() { + // 6.9.4 cache failure steps 2-8. + NotifyAllAssociatedHosts(ERROR_EVENT); + pending_master_entries_.clear(); + + // Discard the inprogress cache. + // TODO(jennb): cleanup possible storage for entries in the cache + if (inprogress_cache_) { + inprogress_cache_->set_owning_group(NULL); + inprogress_cache_ = NULL; + } + + // For a CACHE_ATTEMPT, group will be discarded when this update + // job removes its reference to the group. Nothing more to do here. + + 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() { + if (manifest_url_request_) { + delete manifest_url_request_; + manifest_url_request_ = NULL; + } + + // TODO(jennb): code other cancel cleanup (pending url requests, storage) +} + +void AppCacheUpdateJob::DeleteSoon() { + // TODO(jennb): revisit if update should be deleting itself + MessageLoop::current()->DeleteSoon(FROM_HERE, this); +} +} // namespace appcache diff --git a/webkit/appcache/appcache_update_job.h b/webkit/appcache/appcache_update_job.h new file mode 100644 index 0000000..160e27e --- /dev/null +++ b/webkit/appcache/appcache_update_job.h @@ -0,0 +1,184 @@ +// 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_UPDATE_JOB_H_ +#define WEBKIT_APPCACHE_APPCACHE_UPDATE_JOB_H_ + +#include <deque> +#include <map> + +#include "base/ref_counted.h" +#include "base/task.h" +#include "googleurl/src/gurl.h" +#include "net/url_request/url_request.h" +#include "webkit/appcache/appcache.h" +#include "webkit/appcache/appcache_interfaces.h" + +namespace appcache { + +struct UpdateJobInfo; + +// Application cache Update algorithm and state. +class AppCacheUpdateJob : public URLRequest::Delegate { + public: + AppCacheUpdateJob(AppCacheService* service, AppCacheGroup* group); + ~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); + + + // TODO(jennb): add callback method for writing data to storage + // TODO(jennb): add callback method for reading data from storage + + private: + friend class ScopedRunnableMethodFactory<AppCacheUpdateJob>; + friend class AppCacheUpdateJobTest; + + // Master entries have multiple hosts, for example, the same page is opened + // in different tabs. + // TODO(jennb): detect when hosts are deleted + typedef std::vector<AppCacheHost*> PendingHosts; + typedef std::map<GURL, PendingHosts> PendingMasters; + typedef std::map<GURL, URLRequest*> PendingUrlFetches; + typedef std::pair<GURL, bool> UrlToFetch; // flag TRUE if storage checked + + static const int kRerunDelayMs = 1000; + + enum UpdateType { + UNKNOWN_TYPE, + UPGRADE_ATTEMPT, + CACHE_ATTEMPT, + }; + + enum InternalUpdateState { + FETCH_MANIFEST, + NO_UPDATE, + DOWNLOADING, + REFETCH_MANIFEST, + CACHE_FAILURE, + CANCELLED, + }; + + // Methods for URLRequest::Delegate. + void OnResponseStarted(URLRequest* request); + void OnReadCompleted(URLRequest* request, int bytes_read); + void OnReceivedRedirect(URLRequest* request, + const GURL& new_url, + bool* defer_redirect); + // TODO(jennb): any other delegate callbacks to handle? certificate? + + void FetchManifest(bool is_first_fetch); + + void OnResponseCompleted(URLRequest* request); + + void ReadResponseData(URLRequest* request); + + // Returns false if response data is processed asynchronously, in which + // case a callback will be invoked when it is safe to continue reading + // more response data from the request. + bool ConsumeResponseData(URLRequest* request, + UpdateJobInfo* info, + int bytes_read); + + void HandleManifestFetchCompleted(URLRequest* request); + void ContinueHandleManifestFetchCompleted(bool changed); + void HandleUrlFetchCompleted(URLRequest* request); + void HandleManifestRefetchCompleted(URLRequest* request); + + void NotifySingleHost(AppCacheHost* host, EventID event_id); + void NotifyAllPendingMasterHosts(EventID event_id); + void NotifyAllAssociatedHosts(EventID event_id); + + // Checks if manifest is byte for byte identical with the manifest + // in the newest application cache. + void CheckIfManifestChanged(); + void ContinueCheckIfManifestChanged(const std::string& loaded_manifest); + + // TODO(jennb): delete when able to mock storage behavior + void SimulateManifestChanged(bool changed) { + simulate_manifest_changed_ = changed; + } + + // 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(); + bool ShouldSkipUrlFetch(const AppCacheEntry& entry); + + // 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); + + // TODO(jennb): delete me after storage code added + void SimulateFailedLoadFromNewestCache(const GURL& url); + + // Copies the data from src entry to dest entry and adds the modified + // entry to the inprogress cache. + void CopyEntryToCache(const GURL& url, const AppCacheEntry& src, + AppCacheEntry* dest); + + // Does nothing if update process is still waiting for pending master + // entries or URL fetches to complete downloading. Otherwise, completes + // the update process. + // Returns true if update process is completed. + bool MaybeCompleteUpdate(); + + // Runs the cache failure steps after all pending master entry downloads + // have completed. + void HandleCacheFailure(); + + // 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(); + + // Deletes this object after letting the stack unwind. + void DeleteSoon(); + + // This factory will be used to schedule invocations of various methods. + ScopedRunnableMethodFactory<AppCacheUpdateJob> method_factory_; + + GURL manifest_url_; // here for easier access + AppCacheService* service_; + scoped_refptr<AppCache> inprogress_cache_; + scoped_refptr<AppCacheGroup> group_; + + UpdateType update_type_; + InternalUpdateState internal_state_; + + PendingMasters pending_master_entries_; + size_t master_entries_completed_; + + // 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_; + + // Keep track of pending URL requests so we can cancel them if necessary. + URLRequest* manifest_url_request_; + PendingUrlFetches pending_url_fetches_; + + // Temporary storage of manifest response data for parsing and comparison. + std::string manifest_data_; + std::string manifest_refetch_data_; + + // TODO(jennb): delete when able to mock storage behavior + bool simulate_manifest_changed_; + + DISALLOW_COPY_AND_ASSIGN(AppCacheUpdateJob); +}; + +} // namespace appcache + +#endif // WEBKIT_APPCACHE_APPCACHE_UPDATE_JOB_H_ diff --git a/webkit/appcache/appcache_update_job_unittest.cc b/webkit/appcache/appcache_update_job_unittest.cc new file mode 100644 index 0000000..9fda1c6 --- /dev/null +++ b/webkit/appcache/appcache_update_job_unittest.cc @@ -0,0 +1,1172 @@ +// 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 "testing/gtest/include/gtest/gtest.h" + +#include "base/stl_util-inl.h" +#include "base/thread.h" +#include "base/waitable_event.h" +#include "net/url_request/url_request_test_job.h" +#include "net/url_request/url_request_unittest.h" +#include "webkit/appcache/appcache_group.h" +#include "webkit/appcache/appcache_host.h" +#include "webkit/appcache/appcache_service.h" +#include "webkit/appcache/appcache_update_job.h" + +namespace appcache { +class AppCacheUpdateJobTest; + +const wchar_t kDocRoot[] = L"webkit/appcache/data/appcache_unittest"; + +class MockFrontend : public AppCacheFrontend { + public: + virtual void OnCacheSelected(int host_id, int64 cache_id, + Status status) { + } + + virtual void OnStatusChanged(const std::vector<int>& host_ids, + Status status) { + } + + virtual void OnEventRaised(const std::vector<int>& host_ids, + EventID event_id) { + raised_events_.push_back(RaisedEvent(host_ids, event_id)); + } + + void AddExpectedEvent(const std::vector<int>& host_ids, EventID event_id) { + expected_events_.push_back(RaisedEvent(host_ids, event_id)); + } + + typedef std::vector<int> HostIds; + typedef std::pair<HostIds, EventID> RaisedEvent; + typedef std::vector<RaisedEvent> RaisedEvents; + RaisedEvents raised_events_; + + // Set the expected events if verification needs to happen asynchronously. + RaisedEvents expected_events_; +}; + +// Helper class to let us call methods of AppCacheUpdateJobTest on a +// thread of our choice. +template <class Method> +class WrapperTask : public Task { + public: + WrapperTask(AppCacheUpdateJobTest* test, Method method) + : test_(test), + method_(method) { + } + + virtual void Run() { + (test_->*method_)( ); + } + + private: + AppCacheUpdateJobTest* test_; + Method method_; +}; + +// Helper factories to simulate redirected URL responses for tests. +static URLRequestJob* RedirectFactory(URLRequest* request, + const std::string& scheme) { + return new URLRequestTestJob(request, + URLRequestTestJob::test_redirect_headers(), + URLRequestTestJob::test_data_1(), + true); +} + +class AppCacheUpdateJobTest : public testing::Test, + public AppCacheGroup::Observer { + public: + AppCacheUpdateJobTest() + : ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)), + do_checks_after_update_finished_(false), + expect_group_obsolete_(false), + expect_group_has_cache_(false), + expect_old_cache_(NULL), + expect_newest_cache_(NULL), + tested_manifest_(NONE) { + } + + static void SetUpTestCase() { + io_thread_.reset(new base::Thread("AppCacheUpdateJob IO test thread")); + base::Thread::Options options(MessageLoop::TYPE_IO, 0); + io_thread_->StartWithOptions(options); + + http_server_ = + HTTPTestServer::CreateServer(kDocRoot, io_thread_->message_loop()); + ASSERT_TRUE(http_server_); + } + + static void TearDownTestCase() { + http_server_ = NULL; + io_thread_.reset(NULL); + } + + // Use a separate IO thread to run a test. Thread will be destroyed + // when it goes out of scope. + template <class Method> + void RunTestOnIOThread(Method method) { + event_ .reset(new base::WaitableEvent(false, false)); + io_thread_->message_loop()->PostTask( + FROM_HERE, new WrapperTask<Method>(this, method)); + + // Wait until task is done before exiting the test. + event_->Wait(); + } + + void StartCacheAttemptTest() { + ASSERT_EQ(MessageLoop::TYPE_IO, MessageLoop::current()->type()); + + MakeService(); + group_ = new AppCacheGroup(service_.get(), GURL("http://failme")); + + AppCacheUpdateJob* update = new AppCacheUpdateJob(service_.get(), group_); + group_->update_job_ = update; + + MockFrontend mock_frontend; + AppCacheHost host(1, &mock_frontend, service_.get()); + + update->StartUpdate(&host, GURL::EmptyGURL()); + + // Verify state. + EXPECT_EQ(AppCacheUpdateJob::CACHE_ATTEMPT, update->update_type_); + EXPECT_EQ(AppCacheUpdateJob::FETCH_MANIFEST, update->internal_state_); + EXPECT_EQ(AppCacheGroup::CHECKING, group_->update_status()); + + // Verify notifications. + MockFrontend::RaisedEvents& events = mock_frontend.raised_events_; + size_t expected = 1; + EXPECT_EQ(expected, events.size()); + EXPECT_EQ(expected, events[0].first.size()); + EXPECT_EQ(host.host_id(), events[0].first[0]); + EXPECT_EQ(CHECKING_EVENT, events[0].second); + + // Abort as we're not testing actual URL fetches in this test. + delete update; + UpdateFinished(); + } + + void StartUpgradeAttemptTest() { + ASSERT_EQ(MessageLoop::TYPE_IO, MessageLoop::current()->type()); + + { + MakeService(); + group_ = new AppCacheGroup(service_.get(), GURL("http://failme")); + + // Give the group some existing caches. + AppCache* cache1 = MakeCacheForGroup(1); + AppCache* cache2 = MakeCacheForGroup(2); + + // Associate some hosts with caches in the group. + MockFrontend mock_frontend1; + MockFrontend mock_frontend2; + MockFrontend mock_frontend3; + + AppCacheHost host1(1, &mock_frontend1, service_.get()); + host1.AssociateCache(cache1); + + AppCacheHost host2(2, &mock_frontend2, service_.get()); + host2.AssociateCache(cache2); + + AppCacheHost host3(3, &mock_frontend1, service_.get()); + host3.AssociateCache(cache1); + + AppCacheHost host4(4, &mock_frontend3, service_.get()); + + AppCacheUpdateJob* update = new AppCacheUpdateJob(service_.get(), group_); + group_->update_job_ = update; + update->StartUpdate(&host4, GURL::EmptyGURL()); + + // Verify state after starting an update. + EXPECT_EQ(AppCacheUpdateJob::UPGRADE_ATTEMPT, update->update_type_); + EXPECT_EQ(AppCacheUpdateJob::FETCH_MANIFEST, update->internal_state_); + EXPECT_EQ(AppCacheGroup::CHECKING, group_->update_status()); + + // Verify notifications. + MockFrontend::RaisedEvents& events = mock_frontend1.raised_events_; + size_t expected = 1; + EXPECT_EQ(expected, events.size()); + expected = 2; // 2 hosts using frontend1 + EXPECT_EQ(expected, events[0].first.size()); + MockFrontend::HostIds& host_ids = events[0].first; + EXPECT_TRUE(std::find(host_ids.begin(), host_ids.end(), host1.host_id()) + != host_ids.end()); + EXPECT_TRUE(std::find(host_ids.begin(), host_ids.end(), host3.host_id()) + != host_ids.end()); + EXPECT_EQ(CHECKING_EVENT, events[0].second); + + events = mock_frontend2.raised_events_; + expected = 1; + EXPECT_EQ(expected, events.size()); + EXPECT_EQ(expected, events[0].first.size()); // 1 host using frontend2 + EXPECT_EQ(host2.host_id(), events[0].first[0]); + EXPECT_EQ(CHECKING_EVENT, events[0].second); + + events = mock_frontend3.raised_events_; + EXPECT_TRUE(events.empty()); + + // Abort as we're not testing actual URL fetches in this test. + delete update; + } + UpdateFinished(); + } + + void CacheAttemptFetchManifestFailTest() { + ASSERT_EQ(MessageLoop::TYPE_IO, MessageLoop::current()->type()); + + MakeService(); + group_ = new AppCacheGroup(service_.get(), GURL("http://failme")); + AppCacheUpdateJob* update = new AppCacheUpdateJob(service_.get(), group_); + group_->update_job_ = update; + + MockFrontend* frontend = MakeMockFrontend(); + AppCacheHost* host = MakeHost(1, frontend); + update->StartUpdate(host, GURL::EmptyGURL()); + EXPECT_TRUE(update->manifest_url_request_ != NULL); + + update->manifest_url_request_->SimulateError(-100); + + // Set up checks for when update job finishes. + do_checks_after_update_finished_ = true; + expect_group_obsolete_ = false; + expect_group_has_cache_ = false; + frontend->AddExpectedEvent(MockFrontend::HostIds(1, host->host_id()), + CHECKING_EVENT); + + WaitForUpdateToFinish(); + } + + void UpgradeFetchManifestFailTest() { + ASSERT_EQ(MessageLoop::TYPE_IO, MessageLoop::current()->type()); + + MakeService(); + group_ = new AppCacheGroup(service_.get(), GURL("http://failme")); + AppCacheUpdateJob* update = new AppCacheUpdateJob(service_.get(), group_); + group_->update_job_ = update; + + AppCache* cache = MakeCacheForGroup(1); + MockFrontend* frontend1 = MakeMockFrontend(); + MockFrontend* frontend2 = MakeMockFrontend(); + AppCacheHost* host1 = MakeHost(1, frontend1); + AppCacheHost* host2 = MakeHost(2, frontend2); + host1->AssociateCache(cache); + host2->AssociateCache(cache); + + update->StartUpdate(NULL, GURL::EmptyGURL()); + EXPECT_TRUE(update->manifest_url_request_ != NULL); + + update->manifest_url_request_->SimulateError(-100); + + // Set up checks for when update job finishes. + do_checks_after_update_finished_ = true; + expect_group_obsolete_ = false; + expect_group_has_cache_ = true; + expect_newest_cache_ = cache; // newest cache unaffected by update + MockFrontend::HostIds ids1(1, host1->host_id()); + frontend1->AddExpectedEvent(ids1, CHECKING_EVENT); + frontend1->AddExpectedEvent(ids1, ERROR_EVENT); + MockFrontend::HostIds ids2(1, host2->host_id()); + frontend2->AddExpectedEvent(ids2, CHECKING_EVENT); + frontend2->AddExpectedEvent(ids2, ERROR_EVENT); + + WaitForUpdateToFinish(); + } + + void ManifestRedirectTest() { + ASSERT_EQ(MessageLoop::TYPE_IO, MessageLoop::current()->type()); + + URLRequest::RegisterProtocolFactory("http", RedirectFactory); + + MakeService(); + group_ = new AppCacheGroup(service_.get(), GURL("http://testme")); + AppCacheUpdateJob* update = new AppCacheUpdateJob(service_.get(), group_); + group_->update_job_ = update; + + MockFrontend* frontend = MakeMockFrontend(); + AppCacheHost* host = MakeHost(1, frontend); + update->StartUpdate(host, GURL::EmptyGURL()); + EXPECT_TRUE(update->manifest_url_request_ != NULL); + + // Set up checks for when update job finishes. + do_checks_after_update_finished_ = true; + expect_group_obsolete_ = false; + expect_group_has_cache_ = false; // redirect is like a failed request + frontend->AddExpectedEvent(MockFrontend::HostIds(1, host->host_id()), + CHECKING_EVENT); + + WaitForUpdateToFinish(); + } + + void ManifestWrongMimeTypeTest() { + ASSERT_EQ(MessageLoop::TYPE_IO, MessageLoop::current()->type()); + + MakeService(); + group_ = new AppCacheGroup( + service_.get(), http_server_->TestServerPage("defaultresponse")); + AppCacheUpdateJob* update = new AppCacheUpdateJob(service_.get(), group_); + group_->update_job_ = update; + + MockFrontend* frontend = MakeMockFrontend(); + AppCacheHost* host = MakeHost(1, frontend); + update->StartUpdate(host, GURL::EmptyGURL()); + EXPECT_TRUE(update->manifest_url_request_ != NULL); + + // Set up checks for when update job finishes. + do_checks_after_update_finished_ = true; + expect_group_obsolete_ = false; + expect_group_has_cache_ = false; // bad mime type is like a failed request + frontend->AddExpectedEvent(MockFrontend::HostIds(1, host->host_id()), + CHECKING_EVENT); + + WaitForUpdateToFinish(); + } + + void ManifestNotFoundTest() { + ASSERT_EQ(MessageLoop::TYPE_IO, MessageLoop::current()->type()); + + MakeService(); + group_ = new AppCacheGroup( + service_.get(), http_server_->TestServerPage("files/nosuchfile")); + AppCacheUpdateJob* update = new AppCacheUpdateJob(service_.get(), group_); + group_->update_job_ = update; + + AppCache* cache = MakeCacheForGroup(1); + MockFrontend* frontend1 = MakeMockFrontend(); + MockFrontend* frontend2 = MakeMockFrontend(); + AppCacheHost* host1 = MakeHost(1, frontend1); + AppCacheHost* host2 = MakeHost(2, frontend2); + host1->AssociateCache(cache); + host2->AssociateCache(cache); + + update->StartUpdate(NULL, GURL::EmptyGURL()); + EXPECT_TRUE(update->manifest_url_request_ != NULL); + + // Set up checks for when update job finishes. + do_checks_after_update_finished_ = true; + expect_group_obsolete_ = true; + expect_group_has_cache_ = true; + expect_newest_cache_ = cache; // newest cache unaffected by update + MockFrontend::HostIds ids1(1, host1->host_id()); + frontend1->AddExpectedEvent(ids1, CHECKING_EVENT); + frontend1->AddExpectedEvent(ids1, OBSOLETE_EVENT); + MockFrontend::HostIds ids2(1, host2->host_id()); + frontend2->AddExpectedEvent(ids2, CHECKING_EVENT); + frontend2->AddExpectedEvent(ids2, OBSOLETE_EVENT); + + WaitForUpdateToFinish(); + } + + void ManifestGoneTest() { + ASSERT_EQ(MessageLoop::TYPE_IO, MessageLoop::current()->type()); + + MakeService(); + group_ = new AppCacheGroup( + service_.get(), http_server_->TestServerPage("files/gone")); + AppCacheUpdateJob* update = new AppCacheUpdateJob(service_.get(), group_); + group_->update_job_ = update; + + MockFrontend* frontend = MakeMockFrontend(); + AppCacheHost* host = MakeHost(1, frontend); + update->StartUpdate(host, GURL::EmptyGURL()); + EXPECT_TRUE(update->manifest_url_request_ != NULL); + + // Set up checks for when update job finishes. + do_checks_after_update_finished_ = true; + expect_group_obsolete_ = true; + expect_group_has_cache_ = false; + frontend->AddExpectedEvent(MockFrontend::HostIds(1, host->host_id()), + CHECKING_EVENT); + + WaitForUpdateToFinish(); + } + + void CacheAttemptNotModifiedTest() { + ASSERT_EQ(MessageLoop::TYPE_IO, MessageLoop::current()->type()); + + MakeService(); + group_ = new AppCacheGroup( + service_.get(), http_server_->TestServerPage("files/notmodified")); + AppCacheUpdateJob* update = new AppCacheUpdateJob(service_.get(), group_); + group_->update_job_ = update; + + MockFrontend* frontend = MakeMockFrontend(); + AppCacheHost* host = MakeHost(1, frontend); + update->StartUpdate(host, GURL::EmptyGURL()); + EXPECT_TRUE(update->manifest_url_request_ != NULL); + + // Set up checks for when update job finishes. + do_checks_after_update_finished_ = true; + expect_group_obsolete_ = false; + expect_group_has_cache_ = false; // treated like cache failure + frontend->AddExpectedEvent(MockFrontend::HostIds(1, host->host_id()), + CHECKING_EVENT); + + WaitForUpdateToFinish(); + } + + void UpgradeNotModifiedTest() { + ASSERT_EQ(MessageLoop::TYPE_IO, MessageLoop::current()->type()); + + MakeService(); + group_ = new AppCacheGroup( + service_.get(), http_server_->TestServerPage("files/notmodified")); + AppCacheUpdateJob* update = new AppCacheUpdateJob(service_.get(), group_); + group_->update_job_ = update; + + AppCache* cache = MakeCacheForGroup(1); + MockFrontend* frontend1 = MakeMockFrontend(); + MockFrontend* frontend2 = MakeMockFrontend(); + AppCacheHost* host1 = MakeHost(1, frontend1); + AppCacheHost* host2 = MakeHost(2, frontend2); + host1->AssociateCache(cache); + host2->AssociateCache(cache); + + update->StartUpdate(NULL, GURL::EmptyGURL()); + EXPECT_TRUE(update->manifest_url_request_ != NULL); + + // Set up checks for when update job finishes. + do_checks_after_update_finished_ = true; + expect_group_obsolete_ = false; + expect_group_has_cache_ = true; + expect_newest_cache_ = cache; // newest cache unaffected by update + MockFrontend::HostIds ids1(1, host1->host_id()); + frontend1->AddExpectedEvent(ids1, CHECKING_EVENT); + frontend1->AddExpectedEvent(ids1, NO_UPDATE_EVENT); + MockFrontend::HostIds ids2(1, host2->host_id()); + frontend2->AddExpectedEvent(ids2, CHECKING_EVENT); + frontend2->AddExpectedEvent(ids2, NO_UPDATE_EVENT); + + WaitForUpdateToFinish(); + } + + void UpgradeManifestDataUnchangedTest() { + ASSERT_EQ(MessageLoop::TYPE_IO, MessageLoop::current()->type()); + + MakeService(); + group_ = new AppCacheGroup( + service_.get(), http_server_->TestServerPage("files/manifest1")); + AppCacheUpdateJob* update = new AppCacheUpdateJob(service_.get(), group_); + group_->update_job_ = update; + + AppCache* cache = MakeCacheForGroup(1); + MockFrontend* frontend1 = MakeMockFrontend(); + MockFrontend* frontend2 = MakeMockFrontend(); + AppCacheHost* host1 = MakeHost(1, frontend1); + AppCacheHost* host2 = MakeHost(2, frontend2); + host1->AssociateCache(cache); + host2->AssociateCache(cache); + + // TODO(jennb): simulate this by mocking storage behavior instead + update->SimulateManifestChanged(false); // unchanged + + update->StartUpdate(NULL, GURL::EmptyGURL()); + EXPECT_TRUE(update->manifest_url_request_ != NULL); + + // Set up checks for when update job finishes. + do_checks_after_update_finished_ = true; + expect_group_obsolete_ = false; + expect_group_has_cache_ = true; + expect_newest_cache_ = cache; // newest cache unaffected by update + MockFrontend::HostIds ids1(1, host1->host_id()); + frontend1->AddExpectedEvent(ids1, CHECKING_EVENT); + frontend1->AddExpectedEvent(ids1, NO_UPDATE_EVENT); + MockFrontend::HostIds ids2(1, host2->host_id()); + frontend2->AddExpectedEvent(ids2, CHECKING_EVENT); + frontend2->AddExpectedEvent(ids2, NO_UPDATE_EVENT); + + WaitForUpdateToFinish(); + } + + void BasicCacheAttemptSuccessTest() { + ASSERT_EQ(MessageLoop::TYPE_IO, MessageLoop::current()->type()); + + MakeService(); + group_ = new AppCacheGroup( + service_.get(), http_server_->TestServerPage("files/manifest1")); + AppCacheUpdateJob* update = new AppCacheUpdateJob(service_.get(), group_); + group_->update_job_ = update; + + MockFrontend* frontend = MakeMockFrontend(); + AppCacheHost* host = MakeHost(1, frontend); + update->StartUpdate(host, GURL::EmptyGURL()); + EXPECT_TRUE(update->manifest_url_request_ != NULL); + + // Set up checks for when update job finishes. + do_checks_after_update_finished_ = true; + expect_group_obsolete_ = false; + expect_group_has_cache_ = true; + tested_manifest_ = MANIFEST1; + frontend->AddExpectedEvent(MockFrontend::HostIds(1, host->host_id()), + CHECKING_EVENT); + + WaitForUpdateToFinish(); + } + + void BasicUpgradeSuccessTest() { + ASSERT_EQ(MessageLoop::TYPE_IO, MessageLoop::current()->type()); + + MakeService(); + group_ = new AppCacheGroup( + service_.get(), http_server_->TestServerPage("files/manifest1")); + AppCacheUpdateJob* update = new AppCacheUpdateJob(service_.get(), group_); + group_->update_job_ = update; + + AppCache* cache = MakeCacheForGroup(service_->NewCacheId()); + MockFrontend* frontend1 = MakeMockFrontend(); + MockFrontend* frontend2 = MakeMockFrontend(); + AppCacheHost* host1 = MakeHost(1, frontend1); + AppCacheHost* host2 = MakeHost(2, frontend2); + host1->AssociateCache(cache); + host2->AssociateCache(cache); + + // TODO(jennb): simulate this by mocking storage behavior instead + update->SimulateManifestChanged(true); // changed + + update->StartUpdate(NULL, GURL::EmptyGURL()); + EXPECT_TRUE(update->manifest_url_request_ != NULL); + + // Set up checks for when update job finishes. + do_checks_after_update_finished_ = true; + expect_group_obsolete_ = false; + expect_group_has_cache_ = true; + expect_old_cache_ = cache; + tested_manifest_ = MANIFEST1; + MockFrontend::HostIds ids1(1, host1->host_id()); + frontend1->AddExpectedEvent(ids1, CHECKING_EVENT); + frontend1->AddExpectedEvent(ids1, DOWNLOADING_EVENT); + frontend1->AddExpectedEvent(ids1, PROGRESS_EVENT); + frontend1->AddExpectedEvent(ids1, PROGRESS_EVENT); + frontend1->AddExpectedEvent(ids1, UPDATE_READY_EVENT); + MockFrontend::HostIds ids2(1, host2->host_id()); + frontend2->AddExpectedEvent(ids2, CHECKING_EVENT); + frontend2->AddExpectedEvent(ids2, DOWNLOADING_EVENT); + frontend2->AddExpectedEvent(ids2, PROGRESS_EVENT); + frontend2->AddExpectedEvent(ids2, PROGRESS_EVENT); + frontend2->AddExpectedEvent(ids2, UPDATE_READY_EVENT); + + WaitForUpdateToFinish(); + } + + void UpgradeSuccessMergedTypesTest() { + ASSERT_EQ(MessageLoop::TYPE_IO, MessageLoop::current()->type()); + + MakeService(); + group_ = new AppCacheGroup(service_.get(), + http_server_->TestServerPage("files/manifest-merged-types")); + AppCacheUpdateJob* update = new AppCacheUpdateJob(service_.get(), group_); + group_->update_job_ = update; + + AppCache* cache = MakeCacheForGroup(service_->NewCacheId()); + MockFrontend* frontend1 = MakeMockFrontend(); + MockFrontend* frontend2 = MakeMockFrontend(); + AppCacheHost* host1 = MakeHost(1, frontend1); + AppCacheHost* host2 = MakeHost(2, frontend2); + host1->AssociateCache(cache); + host2->AssociateCache(cache); + + // Give the newest cache a master entry that is also one of the explicit + // entries in the manifest. + cache->AddEntry(http_server_->TestServerPage("files/explicit1"), + AppCacheEntry(AppCacheEntry::MASTER)); + + // TODO(jennb): simulate this by mocking storage behavior instead + update->SimulateManifestChanged(true); // changed + + update->StartUpdate(NULL, GURL::EmptyGURL()); + EXPECT_TRUE(update->manifest_url_request_ != NULL); + + // Set up checks for when update job finishes. + do_checks_after_update_finished_ = true; + expect_group_obsolete_ = false; + expect_group_has_cache_ = true; + expect_old_cache_ = cache; + tested_manifest_ = MANIFEST_MERGED_TYPES; + MockFrontend::HostIds ids1(1, host1->host_id()); + frontend1->AddExpectedEvent(ids1, CHECKING_EVENT); + frontend1->AddExpectedEvent(ids1, DOWNLOADING_EVENT); + frontend1->AddExpectedEvent(ids1, PROGRESS_EVENT); // explicit1 (load) + frontend1->AddExpectedEvent(ids1, PROGRESS_EVENT); // explicit1 (fetch) + frontend1->AddExpectedEvent(ids1, PROGRESS_EVENT); // manifest + frontend1->AddExpectedEvent(ids1, UPDATE_READY_EVENT); + MockFrontend::HostIds ids2(1, host2->host_id()); + frontend2->AddExpectedEvent(ids2, CHECKING_EVENT); + frontend2->AddExpectedEvent(ids2, DOWNLOADING_EVENT); + frontend2->AddExpectedEvent(ids2, PROGRESS_EVENT); + frontend2->AddExpectedEvent(ids2, PROGRESS_EVENT); + frontend2->AddExpectedEvent(ids2, PROGRESS_EVENT); + frontend2->AddExpectedEvent(ids2, UPDATE_READY_EVENT); + + WaitForUpdateToFinish(); + } + + void CacheAttemptFailUrlFetchTest() { + ASSERT_EQ(MessageLoop::TYPE_IO, MessageLoop::current()->type()); + + MakeService(); + group_ = new AppCacheGroup( + service_.get(), http_server_->TestServerPage("files/manifest-with-404")); + AppCacheUpdateJob* update = new AppCacheUpdateJob(service_.get(), group_); + group_->update_job_ = update; + + MockFrontend* frontend = MakeMockFrontend(); + AppCacheHost* host = MakeHost(1, frontend); + update->StartUpdate(host, GURL::EmptyGURL()); + EXPECT_TRUE(update->manifest_url_request_ != NULL); + + // Set up checks for when update job finishes. + do_checks_after_update_finished_ = true; + expect_group_obsolete_ = false; + expect_group_has_cache_ = false; // 404 explicit url is cache failure + frontend->AddExpectedEvent(MockFrontend::HostIds(1, host->host_id()), + CHECKING_EVENT); + + WaitForUpdateToFinish(); + } + + void UpgradeFailUrlFetchTest() { + ASSERT_EQ(MessageLoop::TYPE_IO, MessageLoop::current()->type()); + + MakeService(); + group_ = new AppCacheGroup( + service_.get(), http_server_->TestServerPage("files/manifest-fb-404")); + AppCacheUpdateJob* update = new AppCacheUpdateJob(service_.get(), group_); + group_->update_job_ = update; + + AppCache* cache = MakeCacheForGroup(service_->NewCacheId()); + MockFrontend* frontend1 = MakeMockFrontend(); + MockFrontend* frontend2 = MakeMockFrontend(); + AppCacheHost* host1 = MakeHost(1, frontend1); + AppCacheHost* host2 = MakeHost(2, frontend2); + host1->AssociateCache(cache); + host2->AssociateCache(cache); + + // TODO(jennb): simulate this by mocking storage behavior instead + update->SimulateManifestChanged(true); // changed + + update->StartUpdate(NULL, GURL::EmptyGURL()); + EXPECT_TRUE(update->manifest_url_request_ != NULL); + + // Set up checks for when update job finishes. + do_checks_after_update_finished_ = true; + expect_group_obsolete_ = false; + expect_group_has_cache_ = true; + expect_newest_cache_ = cache; // newest cache unaffectd by failed update + MockFrontend::HostIds ids1(1, host1->host_id()); + frontend1->AddExpectedEvent(ids1, CHECKING_EVENT); + frontend1->AddExpectedEvent(ids1, DOWNLOADING_EVENT); + frontend1->AddExpectedEvent(ids1, PROGRESS_EVENT); + frontend1->AddExpectedEvent(ids1, PROGRESS_EVENT); + frontend1->AddExpectedEvent(ids1, PROGRESS_EVENT); + frontend1->AddExpectedEvent(ids1, ERROR_EVENT); + MockFrontend::HostIds ids2(1, host2->host_id()); + frontend2->AddExpectedEvent(ids2, CHECKING_EVENT); + frontend2->AddExpectedEvent(ids2, DOWNLOADING_EVENT); + frontend2->AddExpectedEvent(ids2, PROGRESS_EVENT); + frontend2->AddExpectedEvent(ids2, PROGRESS_EVENT); + frontend2->AddExpectedEvent(ids2, PROGRESS_EVENT); + frontend2->AddExpectedEvent(ids2, ERROR_EVENT); + + WaitForUpdateToFinish(); + } + + void UpgradeFailMasterUrlFetchTest() { + ASSERT_EQ(MessageLoop::TYPE_IO, MessageLoop::current()->type()); + + MakeService(); + group_ = new AppCacheGroup( + service_.get(), http_server_->TestServerPage("files/manifest1")); + AppCacheUpdateJob* update = new AppCacheUpdateJob(service_.get(), group_); + group_->update_job_ = update; + + AppCache* cache = MakeCacheForGroup(service_->NewCacheId()); + MockFrontend* frontend1 = MakeMockFrontend(); + MockFrontend* frontend2 = MakeMockFrontend(); + AppCacheHost* host1 = MakeHost(1, frontend1); + AppCacheHost* host2 = MakeHost(2, frontend2); + host1->AssociateCache(cache); + host2->AssociateCache(cache); + + // Give the newest cache some master entries; one will fail with a 404. + cache->AddEntry( + http_server_->TestServerPage("files/notfound"), + AppCacheEntry(AppCacheEntry::MASTER)); + cache->AddEntry( + http_server_->TestServerPage("files/explicit2"), + AppCacheEntry(AppCacheEntry::MASTER | AppCacheEntry::FOREIGN)); + cache->AddEntry( + http_server_->TestServerPage("files/servererror"), + AppCacheEntry(AppCacheEntry::MASTER)); + + // TODO(jennb): simulate this by mocking storage behavior instead + update->SimulateManifestChanged(true); // changed + + update->StartUpdate(NULL, GURL::EmptyGURL()); + EXPECT_TRUE(update->manifest_url_request_ != NULL); + + // Set up checks for when update job finishes. + do_checks_after_update_finished_ = true; + expect_group_obsolete_ = false; + expect_group_has_cache_ = true; + expect_old_cache_ = cache; + tested_manifest_ = MANIFEST1; + expect_extra_entries_.insert(AppCache::EntryMap::value_type( + http_server_->TestServerPage("files/explicit2"), + AppCacheEntry(AppCacheEntry::MASTER))); // foreign flag is dropped + expect_extra_entries_.insert(AppCache::EntryMap::value_type( + http_server_->TestServerPage("files/servererror"), + AppCacheEntry(AppCacheEntry::MASTER))); // foreign flag is dropped + MockFrontend::HostIds ids1(1, host1->host_id()); + frontend1->AddExpectedEvent(ids1, CHECKING_EVENT); + frontend1->AddExpectedEvent(ids1, DOWNLOADING_EVENT); + frontend1->AddExpectedEvent(ids1, PROGRESS_EVENT); // explicit1 + frontend1->AddExpectedEvent(ids1, PROGRESS_EVENT); // fallback1a + frontend1->AddExpectedEvent(ids1, PROGRESS_EVENT); // notfound (load) + frontend1->AddExpectedEvent(ids1, PROGRESS_EVENT); // notfound (fetch) + frontend1->AddExpectedEvent(ids1, PROGRESS_EVENT); // explicit2 (load) + frontend1->AddExpectedEvent(ids1, PROGRESS_EVENT); // explicit2 (fetch) + frontend1->AddExpectedEvent(ids1, PROGRESS_EVENT); // servererror (load) + frontend1->AddExpectedEvent(ids1, PROGRESS_EVENT); // servererror (fetch) + frontend1->AddExpectedEvent(ids1, UPDATE_READY_EVENT); + MockFrontend::HostIds ids2(1, host2->host_id()); + frontend2->AddExpectedEvent(ids2, CHECKING_EVENT); + frontend2->AddExpectedEvent(ids2, DOWNLOADING_EVENT); + frontend2->AddExpectedEvent(ids2, PROGRESS_EVENT); + frontend2->AddExpectedEvent(ids2, PROGRESS_EVENT); + frontend2->AddExpectedEvent(ids2, PROGRESS_EVENT); + frontend2->AddExpectedEvent(ids2, PROGRESS_EVENT); + frontend2->AddExpectedEvent(ids2, PROGRESS_EVENT); + frontend2->AddExpectedEvent(ids2, PROGRESS_EVENT); + frontend2->AddExpectedEvent(ids2, PROGRESS_EVENT); + frontend2->AddExpectedEvent(ids2, PROGRESS_EVENT); + frontend2->AddExpectedEvent(ids2, UPDATE_READY_EVENT); + + WaitForUpdateToFinish(); + } + + void EmptyManifestTest() { + ASSERT_EQ(MessageLoop::TYPE_IO, MessageLoop::current()->type()); + + MakeService(); + group_ = new AppCacheGroup( + service_.get(), http_server_->TestServerPage("files/empty-manifest")); + AppCacheUpdateJob* update = new AppCacheUpdateJob(service_.get(), group_); + group_->update_job_ = update; + + AppCache* cache = MakeCacheForGroup(service_->NewCacheId()); + MockFrontend* frontend1 = MakeMockFrontend(); + MockFrontend* frontend2 = MakeMockFrontend(); + AppCacheHost* host1 = MakeHost(1, frontend1); + AppCacheHost* host2 = MakeHost(2, frontend2); + host1->AssociateCache(cache); + host2->AssociateCache(cache); + + // TODO(jennb): simulate this by mocking storage behavior instead + update->SimulateManifestChanged(true); // changed + + update->StartUpdate(NULL, GURL::EmptyGURL()); + EXPECT_TRUE(update->manifest_url_request_ != NULL); + + // Set up checks for when update job finishes. + do_checks_after_update_finished_ = true; + expect_group_obsolete_ = false; + expect_group_has_cache_ = true; + expect_old_cache_ = cache; + tested_manifest_ = EMPTY_MANIFEST; + MockFrontend::HostIds ids1(1, host1->host_id()); + frontend1->AddExpectedEvent(ids1, CHECKING_EVENT); + frontend1->AddExpectedEvent(ids1, DOWNLOADING_EVENT); + frontend1->AddExpectedEvent(ids1, UPDATE_READY_EVENT); + MockFrontend::HostIds ids2(1, host2->host_id()); + frontend2->AddExpectedEvent(ids2, CHECKING_EVENT); + frontend2->AddExpectedEvent(ids2, DOWNLOADING_EVENT); + frontend2->AddExpectedEvent(ids2, UPDATE_READY_EVENT); + + WaitForUpdateToFinish(); + } + + void WaitForUpdateToFinish() { + if (group_->update_status() == AppCacheGroup::IDLE) + UpdateFinished(); + else + group_->AddObserver(this); + } + + void OnUpdateComplete(AppCacheGroup* group) { + ASSERT_EQ(group_, group); + + // Finish up outside of observer callback so that group can be deleted. + MessageLoop::current()->PostTask(FROM_HERE, + method_factory_.NewRunnableMethod( + &AppCacheUpdateJobTest::UpdateFinished)); + } + + void UpdateFinished() { + EXPECT_EQ(AppCacheGroup::IDLE, group_->update_status()); + EXPECT_TRUE(group_->update_job() == NULL); + if (do_checks_after_update_finished_) + VerifyExpectations(); + + // Clean up everything that was created on the IO thread. + group_ = NULL; + STLDeleteContainerPointers(hosts_.begin(), hosts_.end()); + STLDeleteContainerPointers(frontends_.begin(), frontends_.end()); + service_.reset(NULL); + URLRequest::RegisterProtocolFactory("http", NULL); + + event_->Signal(); + } + + void MakeService() { + service_.reset(new AppCacheService()); + request_context_ = new TestURLRequestContext(); + service_->set_request_context(request_context_); + } + + AppCache* MakeCacheForGroup(int64 cache_id) { + AppCache* cache = new AppCache(service_.get(), cache_id); + cache->set_complete(true); + cache->set_update_time(base::TimeTicks::Now()); + cache->set_owning_group(group_); + group_->AddCache(cache); + return cache; + } + + AppCacheHost* MakeHost(int host_id, AppCacheFrontend* frontend) { + AppCacheHost* host = new AppCacheHost(host_id, frontend, service_.get()); + hosts_.push_back(host); + return host; + } + + MockFrontend* MakeMockFrontend() { + MockFrontend* frontend = new MockFrontend(); + frontends_.push_back(frontend); + return frontend; + } + + // Verifies conditions about the group and notifications after an update + // has finished. Cannot verify update job internals as update is deleted. + void VerifyExpectations() { + EXPECT_EQ(expect_group_obsolete_, group_->is_obsolete()); + + if (expect_group_has_cache_) { + EXPECT_TRUE(group_->newest_complete_cache() != NULL); + if (expect_old_cache_) { + EXPECT_NE(expect_old_cache_, group_->newest_complete_cache()); + EXPECT_TRUE(group_->old_caches().end() != + std::find(group_->old_caches().begin(), + group_->old_caches().end(), expect_old_cache_)); + } + if (expect_newest_cache_) + EXPECT_EQ(expect_newest_cache_, group_->newest_complete_cache()); + switch (tested_manifest_) { + case MANIFEST1: + VerifyManifest1(group_->newest_complete_cache()); + break; + case MANIFEST_MERGED_TYPES: + VerifyManifestMergedTypes(group_->newest_complete_cache()); + break; + case EMPTY_MANIFEST: + VerifyEmptyManifest(group_->newest_complete_cache()); + break; + case NONE: + default: + break; + } + } else { + EXPECT_TRUE(group_->newest_complete_cache() == NULL); + } + + // Check expected events. + for (size_t i = 0; i < frontends_.size(); ++i) { + MockFrontend* frontend = frontends_[i]; + + MockFrontend::RaisedEvents& expected_events = frontend->expected_events_; + MockFrontend::RaisedEvents& actual_events = frontend->raised_events_; + EXPECT_EQ(expected_events.size(), actual_events.size()); + + // Check each expected event. + for (size_t j = 0; + j < expected_events.size() && j < actual_events.size(); ++j) { + EXPECT_EQ(expected_events[j].second, actual_events[j].second); + + MockFrontend::HostIds& expected_ids = expected_events[j].first; + MockFrontend::HostIds& actual_ids = actual_events[j].first; + EXPECT_EQ(expected_ids.size(), actual_ids.size()); + + for (size_t k = 0; k < expected_ids.size(); ++k) { + int id = expected_ids[k]; + EXPECT_TRUE(std::find(actual_ids.begin(), actual_ids.end(), id) != + actual_ids.end()); + } + } + } + } + + void VerifyManifest1(AppCache* cache) { + ASSERT_TRUE(cache != NULL); + EXPECT_EQ(group_, cache->owning_group()); + EXPECT_TRUE(cache->is_complete()); + + size_t expected = 3 + expect_extra_entries_.size(); + EXPECT_EQ(expected, cache->entries().size()); + AppCacheEntry* entry = + cache->GetEntry(http_server_->TestServerPage("files/manifest1")); + ASSERT_TRUE(entry); + EXPECT_EQ(AppCacheEntry::MANIFEST, entry->types()); + entry = cache->GetEntry(http_server_->TestServerPage("files/explicit1")); + ASSERT_TRUE(entry); + EXPECT_EQ(AppCacheEntry::EXPLICIT, entry->types()); + entry = cache->GetEntry( + http_server_->TestServerPage("files/fallback1a")); + ASSERT_TRUE(entry); + EXPECT_EQ(AppCacheEntry::FALLBACK, entry->types()); + + for (AppCache::EntryMap::iterator i = expect_extra_entries_.begin(); + i != expect_extra_entries_.end(); ++i) { + entry = cache->GetEntry(i->first); + ASSERT_TRUE(entry); + EXPECT_EQ(i->second.types(), entry->types()); + // TODO(jennb): if copied, check storage id in entry is as expected + } + + expected = 1; + EXPECT_EQ(expected, cache->fallback_namespaces_.size()); + EXPECT_TRUE(cache->fallback_namespaces_.end() != + std::find(cache->fallback_namespaces_.begin(), + cache->fallback_namespaces_.end(), + FallbackNamespace( + http_server_->TestServerPage("files/fallback1"), + http_server_->TestServerPage("files/fallback1a")))); + + EXPECT_TRUE(cache->online_whitelist_namespaces_.empty()); + EXPECT_TRUE(cache->online_whitelist_all_); + + EXPECT_TRUE(cache->update_time_ > base::TimeTicks()); + } + + void VerifyManifestMergedTypes(AppCache* cache) { + ASSERT_TRUE(cache != NULL); + EXPECT_EQ(group_, cache->owning_group()); + EXPECT_TRUE(cache->is_complete()); + + size_t expected = 2; + EXPECT_EQ(expected, cache->entries().size()); + AppCacheEntry* entry = cache->GetEntry( + http_server_->TestServerPage("files/manifest-merged-types")); + ASSERT_TRUE(entry); + EXPECT_EQ(AppCacheEntry::EXPLICIT | AppCacheEntry::MANIFEST, + entry->types()); + entry = cache->GetEntry(http_server_->TestServerPage("files/explicit1")); + ASSERT_TRUE(entry); + EXPECT_EQ(AppCacheEntry::EXPLICIT | AppCacheEntry::FALLBACK | + AppCacheEntry::MASTER, entry->types()); + + expected = 1; + EXPECT_EQ(expected, cache->fallback_namespaces_.size()); + EXPECT_TRUE(cache->fallback_namespaces_.end() != + std::find(cache->fallback_namespaces_.begin(), + cache->fallback_namespaces_.end(), + FallbackNamespace( + http_server_->TestServerPage("files/fallback1"), + http_server_->TestServerPage("files/explicit1")))); + + EXPECT_EQ(expected, cache->online_whitelist_namespaces_.size()); + EXPECT_TRUE(cache->online_whitelist_namespaces_.end() != + std::find(cache->online_whitelist_namespaces_.begin(), + cache->online_whitelist_namespaces_.end(), + http_server_->TestServerPage("files/online1"))); + EXPECT_FALSE(cache->online_whitelist_all_); + + EXPECT_TRUE(cache->update_time_ > base::TimeTicks()); + } + + void VerifyEmptyManifest(AppCache* cache) { + ASSERT_TRUE(cache!= NULL); + EXPECT_EQ(group_, cache->owning_group()); + EXPECT_TRUE(cache->is_complete()); + + size_t expected = 1; + EXPECT_EQ(expected, cache->entries().size()); + AppCacheEntry* entry = cache->GetEntry( + http_server_->TestServerPage("files/empty-manifest")); + ASSERT_TRUE(entry); + EXPECT_EQ(AppCacheEntry::MANIFEST, entry->types()); + + EXPECT_TRUE(cache->fallback_namespaces_.empty()); + EXPECT_TRUE(cache->online_whitelist_namespaces_.empty()); + EXPECT_FALSE(cache->online_whitelist_all_); + + EXPECT_TRUE(cache->update_time_ > base::TimeTicks()); + } + + private: + // Various manifest files used in this test. + enum TestedManifest { + NONE, + MANIFEST1, + MANIFEST_MERGED_TYPES, + EMPTY_MANIFEST, + }; + + static scoped_ptr<base::Thread> io_thread_; + static scoped_refptr<HTTPTestServer> http_server_; + + ScopedRunnableMethodFactory<AppCacheUpdateJobTest> method_factory_; + scoped_ptr<AppCacheService> service_; + scoped_refptr<TestURLRequestContext> request_context_; + scoped_refptr<AppCacheGroup> group_; + scoped_ptr<base::WaitableEvent> event_; + + // Hosts used by an async test that need to live until update job finishes. + // Otherwise, test can put host on the stack instead of here. + std::vector<AppCacheHost*> hosts_; + + // Flag indicating if test cares to verify the update after update finishes. + bool do_checks_after_update_finished_; + bool expect_group_obsolete_; + bool expect_group_has_cache_; + AppCache* expect_old_cache_; + AppCache* expect_newest_cache_; + std::vector<MockFrontend*> frontends_; // to check expected events + TestedManifest tested_manifest_; + AppCache::EntryMap expect_extra_entries_; +}; + +// static +scoped_ptr<base::Thread> AppCacheUpdateJobTest::io_thread_; +scoped_refptr<HTTPTestServer> AppCacheUpdateJobTest::http_server_; + +TEST_F(AppCacheUpdateJobTest, AlreadyChecking) { + AppCacheService service; + scoped_refptr<AppCacheGroup> group = + new AppCacheGroup(&service, GURL("http://manifesturl.com")); + + AppCacheUpdateJob update(&service, group); + + // Pretend group is in checking state. + group->update_job_ = &update; + group->update_status_ = AppCacheGroup::CHECKING; + + update.StartUpdate(NULL, GURL::EmptyGURL()); + EXPECT_EQ(AppCacheGroup::CHECKING, group->update_status()); + + MockFrontend mock_frontend; + AppCacheHost host(1, &mock_frontend, &service); + update.StartUpdate(&host, GURL::EmptyGURL()); + + MockFrontend::RaisedEvents events = mock_frontend.raised_events_; + size_t expected = 1; + EXPECT_EQ(expected, events.size()); + EXPECT_EQ(expected, events[0].first.size()); + EXPECT_EQ(host.host_id(), events[0].first[0]); + EXPECT_EQ(CHECKING_EVENT, events[0].second); + EXPECT_EQ(AppCacheGroup::CHECKING, group->update_status()); +} + +TEST_F(AppCacheUpdateJobTest, AlreadyDownloading) { + AppCacheService service; + scoped_refptr<AppCacheGroup> group = + new AppCacheGroup(&service, GURL("http://manifesturl.com")); + + AppCacheUpdateJob update(&service, group); + + // Pretend group is in downloading state. + group->update_job_ = &update; + group->update_status_ = AppCacheGroup::DOWNLOADING; + + update.StartUpdate(NULL, GURL::EmptyGURL()); + EXPECT_EQ(AppCacheGroup::DOWNLOADING, group->update_status()); + + MockFrontend mock_frontend; + AppCacheHost host(1, &mock_frontend, &service); + update.StartUpdate(&host, GURL::EmptyGURL()); + + MockFrontend::RaisedEvents events = mock_frontend.raised_events_; + size_t expected = 2; + EXPECT_EQ(expected, events.size()); + expected = 1; + EXPECT_EQ(expected, events[0].first.size()); + EXPECT_EQ(host.host_id(), events[0].first[0]); + EXPECT_EQ(CHECKING_EVENT, events[0].second); + + EXPECT_EQ(expected, events[1].first.size()); + EXPECT_EQ(host.host_id(), events[1].first[0]); + EXPECT_EQ(appcache::DOWNLOADING_EVENT, events[1].second); + + EXPECT_EQ(AppCacheGroup::DOWNLOADING, group->update_status()); +} + +TEST_F(AppCacheUpdateJobTest, StartCacheAttempt) { + RunTestOnIOThread(&AppCacheUpdateJobTest::StartCacheAttemptTest); +} + +TEST_F(AppCacheUpdateJobTest, StartUpgradeAttempt) { + RunTestOnIOThread(&AppCacheUpdateJobTest::StartUpgradeAttemptTest); +} + +TEST_F(AppCacheUpdateJobTest, CacheAttemptFetchManifestFail) { + RunTestOnIOThread(&AppCacheUpdateJobTest::CacheAttemptFetchManifestFailTest); +} + +TEST_F(AppCacheUpdateJobTest, UpgradeFetchManifestFail) { + RunTestOnIOThread(&AppCacheUpdateJobTest::UpgradeFetchManifestFailTest); +} + +TEST_F(AppCacheUpdateJobTest, ManifestRedirect) { + RunTestOnIOThread(&AppCacheUpdateJobTest::ManifestRedirectTest); +} + +TEST_F(AppCacheUpdateJobTest, ManifestWrongMimeType) { + RunTestOnIOThread(&AppCacheUpdateJobTest::ManifestWrongMimeTypeTest); +} + +TEST_F(AppCacheUpdateJobTest, ManifestNotFound) { + RunTestOnIOThread(&AppCacheUpdateJobTest::ManifestNotFoundTest); +} + +TEST_F(AppCacheUpdateJobTest, ManifestGone) { + RunTestOnIOThread(&AppCacheUpdateJobTest::ManifestGoneTest); +} + +TEST_F(AppCacheUpdateJobTest, CacheAttemptNotModified) { + RunTestOnIOThread(&AppCacheUpdateJobTest::CacheAttemptNotModifiedTest); +} + +TEST_F(AppCacheUpdateJobTest, UpgradeNotModified) { + RunTestOnIOThread(&AppCacheUpdateJobTest::UpgradeNotModifiedTest); +} + +TEST_F(AppCacheUpdateJobTest, UpgradeManifestDataUnchanged) { + RunTestOnIOThread(&AppCacheUpdateJobTest::UpgradeManifestDataUnchangedTest); +} + +TEST_F(AppCacheUpdateJobTest, BasicCacheAttemptSuccess) { + RunTestOnIOThread(&AppCacheUpdateJobTest::BasicCacheAttemptSuccessTest); +} + +TEST_F(AppCacheUpdateJobTest, BasicUpgradeSuccess) { + RunTestOnIOThread(&AppCacheUpdateJobTest::BasicUpgradeSuccessTest); +} + +TEST_F(AppCacheUpdateJobTest, UpgradeSuccessMergedTypes) { + RunTestOnIOThread(&AppCacheUpdateJobTest::UpgradeSuccessMergedTypesTest); +} + +TEST_F(AppCacheUpdateJobTest, CacheAttemptFailUrlFetch) { + RunTestOnIOThread(&AppCacheUpdateJobTest::CacheAttemptFailUrlFetchTest); +} + +TEST_F(AppCacheUpdateJobTest, UpgradeFailUrlFetch) { + RunTestOnIOThread(&AppCacheUpdateJobTest::UpgradeFailUrlFetchTest); +} + +TEST_F(AppCacheUpdateJobTest, UpgradeFailMasterUrlFetch) { + RunTestOnIOThread(&AppCacheUpdateJobTest::UpgradeFailMasterUrlFetchTest); +} + +TEST_F(AppCacheUpdateJobTest, EmptyManifest) { + RunTestOnIOThread(&AppCacheUpdateJobTest::EmptyManifestTest); +} + +} // namespace appcache diff --git a/webkit/appcache/data/appcache_unittest/empty-manifest b/webkit/appcache/data/appcache_unittest/empty-manifest new file mode 100644 index 0000000..af16a0e --- /dev/null +++ b/webkit/appcache/data/appcache_unittest/empty-manifest @@ -0,0 +1 @@ +CACHE MANIFEST diff --git a/webkit/appcache/data/appcache_unittest/empty-manifest.mock-http-headers b/webkit/appcache/data/appcache_unittest/empty-manifest.mock-http-headers new file mode 100644 index 0000000..6e904b9 --- /dev/null +++ b/webkit/appcache/data/appcache_unittest/empty-manifest.mock-http-headers @@ -0,0 +1,2 @@ +HTTP/1.1 200 OK +Content-type: text/cache-manifest diff --git a/webkit/appcache/data/appcache_unittest/explicit1 b/webkit/appcache/data/appcache_unittest/explicit1 new file mode 100644 index 0000000..33e739e --- /dev/null +++ b/webkit/appcache/data/appcache_unittest/explicit1 @@ -0,0 +1 @@ +explicit1 diff --git a/webkit/appcache/data/appcache_unittest/explicit2 b/webkit/appcache/data/appcache_unittest/explicit2 new file mode 100644 index 0000000..2128ad6 --- /dev/null +++ b/webkit/appcache/data/appcache_unittest/explicit2 @@ -0,0 +1 @@ +explicit2 diff --git a/webkit/appcache/data/appcache_unittest/fallback1a b/webkit/appcache/data/appcache_unittest/fallback1a new file mode 100644 index 0000000..62cd309 --- /dev/null +++ b/webkit/appcache/data/appcache_unittest/fallback1a @@ -0,0 +1 @@ +fallback1a diff --git a/webkit/appcache/data/appcache_unittest/gone b/webkit/appcache/data/appcache_unittest/gone new file mode 100644 index 0000000..97896a0 --- /dev/null +++ b/webkit/appcache/data/appcache_unittest/gone @@ -0,0 +1 @@ +nothing
\ No newline at end of file diff --git a/webkit/appcache/data/appcache_unittest/gone.mock-http-headers b/webkit/appcache/data/appcache_unittest/gone.mock-http-headers new file mode 100644 index 0000000..2817c70 --- /dev/null +++ b/webkit/appcache/data/appcache_unittest/gone.mock-http-headers @@ -0,0 +1 @@ +HTTP/1.1 410 GONE diff --git a/webkit/appcache/data/appcache_unittest/manifest-fb-404 b/webkit/appcache/data/appcache_unittest/manifest-fb-404 new file mode 100644 index 0000000..aea2364 --- /dev/null +++ b/webkit/appcache/data/appcache_unittest/manifest-fb-404 @@ -0,0 +1,7 @@ +CACHE MANIFEST +explicit1 +FALLBACK: +fallback404 fallback-404 +fallback1 fallback1a +NETWORK: +online1 diff --git a/webkit/appcache/data/appcache_unittest/manifest-fb-404.mock-http-headers b/webkit/appcache/data/appcache_unittest/manifest-fb-404.mock-http-headers new file mode 100644 index 0000000..6e904b9 --- /dev/null +++ b/webkit/appcache/data/appcache_unittest/manifest-fb-404.mock-http-headers @@ -0,0 +1,2 @@ +HTTP/1.1 200 OK +Content-type: text/cache-manifest diff --git a/webkit/appcache/data/appcache_unittest/manifest-merged-types b/webkit/appcache/data/appcache_unittest/manifest-merged-types new file mode 100644 index 0000000..5c6589b --- /dev/null +++ b/webkit/appcache/data/appcache_unittest/manifest-merged-types @@ -0,0 +1,9 @@ +CACHE MANIFEST +explicit1 +# manifest is also an explicit entry +manifest-merged-types +FALLBACK: +# fallback is also explicit entry +fallback1 explicit1 +NETWORK: +online1 diff --git a/webkit/appcache/data/appcache_unittest/manifest-merged-types.mock-http-headers b/webkit/appcache/data/appcache_unittest/manifest-merged-types.mock-http-headers new file mode 100644 index 0000000..6e904b9 --- /dev/null +++ b/webkit/appcache/data/appcache_unittest/manifest-merged-types.mock-http-headers @@ -0,0 +1,2 @@ +HTTP/1.1 200 OK +Content-type: text/cache-manifest diff --git a/webkit/appcache/data/appcache_unittest/manifest-with-404 b/webkit/appcache/data/appcache_unittest/manifest-with-404 new file mode 100644 index 0000000..353fad7 --- /dev/null +++ b/webkit/appcache/data/appcache_unittest/manifest-with-404 @@ -0,0 +1,9 @@ +CACHE MANIFEST +explicit-404 +explicit1 +explicit2 +explicit3 +FALLBACK: +fallback1 fallback1a +NETWORK: +online1 diff --git a/webkit/appcache/data/appcache_unittest/manifest-with-404.mock-http-headers b/webkit/appcache/data/appcache_unittest/manifest-with-404.mock-http-headers new file mode 100644 index 0000000..6e904b9 --- /dev/null +++ b/webkit/appcache/data/appcache_unittest/manifest-with-404.mock-http-headers @@ -0,0 +1,2 @@ +HTTP/1.1 200 OK +Content-type: text/cache-manifest diff --git a/webkit/appcache/data/appcache_unittest/manifest1 b/webkit/appcache/data/appcache_unittest/manifest1 new file mode 100644 index 0000000..b33fad5 --- /dev/null +++ b/webkit/appcache/data/appcache_unittest/manifest1 @@ -0,0 +1,6 @@ +CACHE MANIFEST +explicit1 +FALLBACK: +fallback1 fallback1a +NETWORK: +* diff --git a/webkit/appcache/data/appcache_unittest/manifest1.mock-http-headers b/webkit/appcache/data/appcache_unittest/manifest1.mock-http-headers new file mode 100644 index 0000000..6e904b9 --- /dev/null +++ b/webkit/appcache/data/appcache_unittest/manifest1.mock-http-headers @@ -0,0 +1,2 @@ +HTTP/1.1 200 OK +Content-type: text/cache-manifest diff --git a/webkit/appcache/data/appcache_unittest/notmodified b/webkit/appcache/data/appcache_unittest/notmodified new file mode 100644 index 0000000..b3f1762 --- /dev/null +++ b/webkit/appcache/data/appcache_unittest/notmodified @@ -0,0 +1 @@ +nochange
\ No newline at end of file diff --git a/webkit/appcache/data/appcache_unittest/notmodified.mock-http-headers b/webkit/appcache/data/appcache_unittest/notmodified.mock-http-headers new file mode 100644 index 0000000..b86db0d --- /dev/null +++ b/webkit/appcache/data/appcache_unittest/notmodified.mock-http-headers @@ -0,0 +1 @@ +HTTP/1.1 304 NOT MODIFIED diff --git a/webkit/appcache/data/appcache_unittest/servererror b/webkit/appcache/data/appcache_unittest/servererror new file mode 100644 index 0000000..760589c --- /dev/null +++ b/webkit/appcache/data/appcache_unittest/servererror @@ -0,0 +1 @@ +error
\ No newline at end of file diff --git a/webkit/appcache/data/appcache_unittest/servererror.mock-http-headers b/webkit/appcache/data/appcache_unittest/servererror.mock-http-headers new file mode 100644 index 0000000..472aa4b --- /dev/null +++ b/webkit/appcache/data/appcache_unittest/servererror.mock-http-headers @@ -0,0 +1 @@ +HTTP/1.1 500 INTERNAL SERVER ERROR |