summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjennb@chromium.org <jennb@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-10-06 17:19:37 +0000
committerjennb@chromium.org <jennb@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-10-06 17:19:37 +0000
commitae3c0b266d4a3f7f2f121589e24e76528d44f4f7 (patch)
tree85af04a19120db4a75b1ad07ca2a6e1473f1767e
parentff9cfce7ec2ae06df4ac413cfde3bfd7ae503677 (diff)
downloadchromium_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
-rw-r--r--webkit/appcache/appcache.cc21
-rw-r--r--webkit/appcache/appcache.h19
-rw-r--r--webkit/appcache/appcache_group.cc45
-rw-r--r--webkit/appcache/appcache_group.h50
-rw-r--r--webkit/appcache/appcache_group_unittest.cc24
-rw-r--r--webkit/appcache/appcache_interfaces.cc2
-rw-r--r--webkit/appcache/appcache_interfaces.h1
-rw-r--r--webkit/appcache/appcache_service.cc2
-rw-r--r--webkit/appcache/appcache_service.h2
-rw-r--r--webkit/appcache/appcache_unittest.cc39
-rw-r--r--webkit/appcache/appcache_update_job.cc671
-rw-r--r--webkit/appcache/appcache_update_job.h184
-rw-r--r--webkit/appcache/appcache_update_job_unittest.cc1172
-rw-r--r--webkit/appcache/data/appcache_unittest/empty-manifest1
-rw-r--r--webkit/appcache/data/appcache_unittest/empty-manifest.mock-http-headers2
-rw-r--r--webkit/appcache/data/appcache_unittest/explicit11
-rw-r--r--webkit/appcache/data/appcache_unittest/explicit21
-rw-r--r--webkit/appcache/data/appcache_unittest/fallback1a1
-rw-r--r--webkit/appcache/data/appcache_unittest/gone1
-rw-r--r--webkit/appcache/data/appcache_unittest/gone.mock-http-headers1
-rw-r--r--webkit/appcache/data/appcache_unittest/manifest-fb-4047
-rw-r--r--webkit/appcache/data/appcache_unittest/manifest-fb-404.mock-http-headers2
-rw-r--r--webkit/appcache/data/appcache_unittest/manifest-merged-types9
-rw-r--r--webkit/appcache/data/appcache_unittest/manifest-merged-types.mock-http-headers2
-rw-r--r--webkit/appcache/data/appcache_unittest/manifest-with-4049
-rw-r--r--webkit/appcache/data/appcache_unittest/manifest-with-404.mock-http-headers2
-rw-r--r--webkit/appcache/data/appcache_unittest/manifest16
-rw-r--r--webkit/appcache/data/appcache_unittest/manifest1.mock-http-headers2
-rw-r--r--webkit/appcache/data/appcache_unittest/notmodified1
-rw-r--r--webkit/appcache/data/appcache_unittest/notmodified.mock-http-headers1
-rw-r--r--webkit/appcache/data/appcache_unittest/servererror1
-rw-r--r--webkit/appcache/data/appcache_unittest/servererror.mock-http-headers1
-rw-r--r--webkit/tools/test_shell/test_shell.gyp1
-rw-r--r--webkit/webkit.gyp2
34 files changed, 2258 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
diff --git a/webkit/tools/test_shell/test_shell.gyp b/webkit/tools/test_shell/test_shell.gyp
index 5124764..5a3d5a8 100644
--- a/webkit/tools/test_shell/test_shell.gyp
+++ b/webkit/tools/test_shell/test_shell.gyp
@@ -361,6 +361,7 @@
'../../appcache/appcache_group_unittest.cc',
'../../appcache/appcache_host_unittest.cc',
'../../appcache/appcache_service_unittest.cc',
+ '../../appcache/appcache_update_job_unittest.cc',
'../../glue/bookmarklet_unittest.cc',
'../../glue/context_menu_unittest.cc',
'../../glue/cpp_bound_class_unittest.cc',
diff --git a/webkit/webkit.gyp b/webkit/webkit.gyp
index 2dd6bba..7bb9705 100644
--- a/webkit/webkit.gyp
+++ b/webkit/webkit.gyp
@@ -406,6 +406,8 @@
'appcache/appcache_request_handler.h',
'appcache/appcache_service.cc',
'appcache/appcache_service.h',
+ 'appcache/appcache_update_job.cc',
+ 'appcache/appcache_update_job.h',
'appcache/manifest_parser.cc',
'appcache/manifest_parser.h',
'appcache/web_application_cache_host_impl.cc',