summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--webkit/appcache/appcache_group.cc20
-rw-r--r--webkit/appcache/appcache_group.h4
-rw-r--r--webkit/appcache/appcache_update_job.cc263
-rw-r--r--webkit/appcache/appcache_update_job.h36
-rw-r--r--webkit/appcache/appcache_update_job_unittest.cc196
-rw-r--r--webkit/appcache/mock_appcache_storage.cc16
-rw-r--r--webkit/appcache/mock_appcache_storage.h16
7 files changed, 467 insertions, 84 deletions
diff --git a/webkit/appcache/appcache_group.cc b/webkit/appcache/appcache_group.cc
index 8d7571c..9efd8ca2 100644
--- a/webkit/appcache/appcache_group.cc
+++ b/webkit/appcache/appcache_group.cc
@@ -75,20 +75,32 @@ void AppCacheGroup::AddCache(AppCache* complete_cache) {
void AppCacheGroup::RemoveCache(AppCache* cache) {
DCHECK(cache->associated_hosts().empty());
if (cache == newest_complete_cache_) {
- AppCache* cache = newest_complete_cache_;
+ AppCache* tmp_cache = newest_complete_cache_;
newest_complete_cache_ = NULL;
- cache->set_owning_group(NULL); // may cause this group to be deleted
+ tmp_cache->set_owning_group(NULL); // may cause this group to be deleted
} else {
Caches::iterator it =
std::find(old_caches_.begin(), old_caches_.end(), cache);
if (it != old_caches_.end()) {
- AppCache* cache = *it;
+ AppCache* tmp_cache = *it;
old_caches_.erase(it);
- cache->set_owning_group(NULL); // may cause group to be deleted
+ tmp_cache->set_owning_group(NULL); // may cause group to be deleted
}
}
}
+void AppCacheGroup::RestoreCacheAsNewest(AppCache* former_newest_cache) {
+ newest_complete_cache_->set_owning_group(NULL);
+ newest_complete_cache_ = former_newest_cache;
+ if (former_newest_cache) {
+ DCHECK(former_newest_cache->owning_group() == this);
+ Caches::iterator it =
+ std::find(old_caches_.begin(), old_caches_.end(), former_newest_cache);
+ DCHECK(it != old_caches_.end());
+ old_caches_.erase(it);
+ }
+}
+
void AppCacheGroup::StartUpdateWithNewMasterEntry(
AppCacheHost* host, const GURL& new_master_resource) {
if (!update_job_)
diff --git a/webkit/appcache/appcache_group.h b/webkit/appcache/appcache_group.h
index 6f3c210..dece474 100644
--- a/webkit/appcache/appcache_group.h
+++ b/webkit/appcache/appcache_group.h
@@ -86,6 +86,10 @@ class AppCacheGroup : public base::RefCounted<AppCacheGroup> {
const Caches& old_caches() const { return old_caches_; }
+ // Used by update process to restore the group's newest cache if storage
+ // fails to store the newly created cache.
+ void RestoreCacheAsNewest(AppCache* cache);
+
GURL manifest_url_;
UpdateStatus update_status_;
bool is_obsolete_;
diff --git a/webkit/appcache/appcache_update_job.cc b/webkit/appcache/appcache_update_job.cc
index a65731bf..b1cf778 100644
--- a/webkit/appcache/appcache_update_job.cc
+++ b/webkit/appcache/appcache_update_job.cc
@@ -20,7 +20,8 @@ static const int kMax503Retries = 3;
// Extra info associated with requests for use during response processing.
// This info is deleted when the URLRequest is deleted.
-struct UpdateJobInfo : public URLRequest::UserData {
+class UpdateJobInfo : public URLRequest::UserData {
+ public:
enum RequestType {
MANIFEST_FETCH,
URL_FETCH,
@@ -28,16 +29,40 @@ struct UpdateJobInfo : public URLRequest::UserData {
};
explicit UpdateJobInfo(RequestType request_type)
- : type(request_type),
- buffer(new net::IOBuffer(kBufferSize)),
- retry_503_attempts(0) {
- }
-
- RequestType type;
- scoped_refptr<net::IOBuffer> buffer;
- // TODO(jennb): need storage info to stream response data to storage
-
- int retry_503_attempts;
+ : type_(request_type),
+ buffer_(new net::IOBuffer(kBufferSize)),
+ retry_503_attempts_(0),
+ update_job_(NULL),
+ request_(NULL),
+ wrote_response_info_(false),
+ ALLOW_THIS_IN_INITIALIZER_LIST(write_callback_(
+ this, &UpdateJobInfo::OnWriteComplete)) {
+ }
+
+ void SetUpResponseWriter(AppCacheResponseWriter* writer,
+ AppCacheUpdateJob* update,
+ URLRequest* request) {
+ DCHECK(!response_writer_.get());
+ response_writer_.reset(writer);
+ update_job_ = update;
+ request_ = request;
+ }
+
+ void OnWriteComplete(int result) {
+ // A completed write may delete the URL request and this object.
+ update_job_->OnWriteResponseComplete(result, request_, this);
+ }
+
+ RequestType type_;
+ scoped_refptr<net::IOBuffer> buffer_;
+ int retry_503_attempts_;
+
+ // Info needed to write responses to storage and process callbacks.
+ scoped_ptr<AppCacheResponseWriter> response_writer_;
+ AppCacheUpdateJob* update_job_;
+ URLRequest* request_;
+ bool wrote_response_info_;
+ net::CompletionCallbackImpl<UpdateJobInfo> write_callback_;
};
// Helper class for collecting hosts per frontend when sending notifications
@@ -82,7 +107,11 @@ AppCacheUpdateJob::AppCacheUpdateJob(AppCacheService* service,
internal_state_(FETCH_MANIFEST),
master_entries_completed_(0),
url_fetches_completed_(0),
- manifest_url_request_(NULL) {
+ manifest_url_request_(NULL),
+ ALLOW_THIS_IN_INITIALIZER_LIST(manifest_info_write_callback_(
+ this, &AppCacheUpdateJob::OnManifestInfoWriteComplete)),
+ ALLOW_THIS_IN_INITIALIZER_LIST(manifest_data_write_callback_(
+ this, &AppCacheUpdateJob::OnManifestDataWriteComplete)) {
DCHECK(group_);
manifest_url_ = group_->manifest_url();
}
@@ -160,13 +189,14 @@ void AppCacheUpdateJob::OnResponseStarted(URLRequest *request) {
void AppCacheUpdateJob::ReadResponseData(URLRequest* request) {
if (internal_state_ == CACHE_FAILURE || internal_state_ == CANCELLED ||
- internal_state_ == COMPLETED)
+ internal_state_ == COMPLETED) {
return;
+ }
int bytes_read = 0;
UpdateJobInfo* info =
static_cast<UpdateJobInfo*>(request->GetUserData(this));
- request->Read(info->buffer, kBufferSize, &bytes_read);
+ request->Read(info->buffer_, kBufferSize, &bytes_read);
OnReadCompleted(request, bytes_read);
}
@@ -179,7 +209,7 @@ void AppCacheUpdateJob::OnReadCompleted(URLRequest* request, int bytes_read) {
data_consumed = ConsumeResponseData(request, info, bytes_read);
if (data_consumed) {
bytes_read = 0;
- while (request->Read(info->buffer, kBufferSize, &bytes_read)) {
+ while (request->Read(info->buffer_, kBufferSize, &bytes_read)) {
if (bytes_read > 0) {
data_consumed = ConsumeResponseData(request, info, bytes_read);
if (!data_consumed)
@@ -198,20 +228,22 @@ void AppCacheUpdateJob::OnReadCompleted(URLRequest* request, int bytes_read) {
bool AppCacheUpdateJob::ConsumeResponseData(URLRequest* request,
UpdateJobInfo* info,
int bytes_read) {
- switch (info->type) {
+ DCHECK_GT(bytes_read, 0);
+ switch (info->type_) {
case UpdateJobInfo::MANIFEST_FETCH:
- manifest_data_.append(info->buffer->data(), bytes_read);
+ 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;
+ if (!info->response_writer_.get()) {
+ info->SetUpResponseWriter(
+ service_->storage()->CreateResponseWriter(manifest_url_),
+ this, request);
+ }
+ info->response_writer_->WriteData(info->buffer_, bytes_read,
+ &info->write_callback_);
+ return false; // wait for async write completion to continue reading
case UpdateJobInfo::MANIFEST_REFETCH:
- manifest_refetch_data_.append(info->buffer->data(), bytes_read);
+ manifest_refetch_data_.append(info->buffer_->data(), bytes_read);
break;
default:
NOTREACHED();
@@ -219,6 +251,29 @@ bool AppCacheUpdateJob::ConsumeResponseData(URLRequest* request,
return true;
}
+void AppCacheUpdateJob::OnWriteResponseComplete(int result,
+ URLRequest* request,
+ UpdateJobInfo* info) {
+ DCHECK(internal_state_ == DOWNLOADING);
+
+ if (result < 0) {
+ request->Cancel();
+ OnResponseCompleted(request);
+ return;
+ }
+
+ if (!info->wrote_response_info_) {
+ info->wrote_response_info_ = true;
+ scoped_refptr<HttpResponseInfoIOBuffer> io_buffer =
+ new HttpResponseInfoIOBuffer(
+ new net::HttpResponseInfo(request->response_info()));
+ info->response_writer_->WriteInfo(io_buffer, &info->write_callback_);
+ return;
+ }
+
+ ReadResponseData(request);
+}
+
void AppCacheUpdateJob::OnReceivedRedirect(URLRequest* request,
const GURL& new_url,
bool* defer_redirect) {
@@ -237,7 +292,7 @@ void AppCacheUpdateJob::OnResponseCompleted(URLRequest* request) {
UpdateJobInfo* info =
static_cast<UpdateJobInfo*>(request->GetUserData(this));
- switch (info->type) {
+ switch (info->type_) {
case UpdateJobInfo::MANIFEST_FETCH:
HandleManifestFetchCompleted(request);
break;
@@ -257,7 +312,7 @@ void AppCacheUpdateJob::OnResponseCompleted(URLRequest* request) {
bool AppCacheUpdateJob::RetryRequest(URLRequest* request) {
UpdateJobInfo* info =
static_cast<UpdateJobInfo*>(request->GetUserData(this));
- if (info->retry_503_attempts >= kMax503Retries) {
+ if (info->retry_503_attempts_ >= kMax503Retries) {
return false;
}
@@ -266,13 +321,13 @@ bool AppCacheUpdateJob::RetryRequest(URLRequest* request) {
const GURL& url = request->original_url();
URLRequest* retry = new URLRequest(url, this);
- UpdateJobInfo* retry_info = new UpdateJobInfo(info->type);
- retry_info->retry_503_attempts = info->retry_503_attempts + 1;
+ UpdateJobInfo* retry_info = new UpdateJobInfo(info->type_);
+ retry_info->retry_503_attempts_ = info->retry_503_attempts_ + 1;
retry->SetUserData(this, retry_info);
retry->set_context(request->context());
retry->set_load_flags(request->load_flags());
- switch (info->type) {
+ switch (info->type_) {
case UpdateJobInfo::MANIFEST_FETCH:
case UpdateJobInfo::MANIFEST_REFETCH:
manifest_url_request_ = retry;
@@ -307,6 +362,8 @@ void AppCacheUpdateJob::HandleManifestFetchCompleted(URLRequest* request) {
int response_code = request->GetResponseCode();
std::string mime_type;
request->GetMimeType(&mime_type);
+ manifest_response_info_.reset(
+ new net::HttpResponseInfo(request->response_info()));
if ((response_code / 100 == 2) && mime_type == kManifestMimeType) {
if (update_type_ == UPGRADE_ATTEMPT)
@@ -315,20 +372,29 @@ void AppCacheUpdateJob::HandleManifestFetchCompleted(URLRequest* request) {
ContinueHandleManifestFetchCompleted(true);
} else if (response_code == 304 && update_type_ == UPGRADE_ATTEMPT) {
ContinueHandleManifestFetchCompleted(false);
+ } else if (response_code == 404 || response_code == 410) {
+ service_->storage()->MakeGroupObsolete(group_, this); // async
} else {
- if (response_code == 404 || response_code == 410) {
- group_->set_obsolete(true);
- NotifyAllAssociatedHosts(OBSOLETE_EVENT);
- NotifyAllPendingMasterHosts(ERROR_EVENT);
- internal_state_ = COMPLETED;
- } else {
- LOG(INFO) << "Cache failure, response code: " << response_code;
- internal_state_ = CACHE_FAILURE;
- }
+ LOG(INFO) << "Cache failure, response code: " << response_code;
+ internal_state_ = CACHE_FAILURE;
MaybeCompleteUpdate(); // if not done, run async cache failure steps
}
}
+void AppCacheUpdateJob::OnGroupMadeObsolete(AppCacheGroup* group,
+ bool success) {
+ NotifyAllPendingMasterHosts(ERROR_EVENT);
+ if (success) {
+ DCHECK(group->is_obsolete());
+ NotifyAllAssociatedHosts(OBSOLETE_EVENT);
+ internal_state_ = COMPLETED;
+ } else {
+ // Treat failure to mark group obsolete as a cache failure.
+ internal_state_ = CACHE_FAILURE;
+ }
+ MaybeCompleteUpdate();
+}
+
void AppCacheUpdateJob::ContinueHandleManifestFetchCompleted(bool changed) {
DCHECK(internal_state_ == FETCH_MANIFEST);
@@ -382,8 +448,14 @@ void AppCacheUpdateJob::HandleUrlFetchCompleted(URLRequest* request) {
int response_code = request->GetResponseCode();
AppCacheEntry& entry = url_file_list_.find(url)->second;
+ UpdateJobInfo* info =
+ static_cast<UpdateJobInfo*>(request->GetUserData(this));
+
if (request->status().is_success() && (response_code / 100 == 2)) {
- // TODO(jennb): associate storage with the new entry
+ // Associate storage with the new entry.
+ DCHECK(info->response_writer_.get());
+ entry.set_response_id(info->response_writer_->response_id());
+
inprogress_cache_->AddEntry(url, entry);
// Foreign entries will be detected during cache selection.
@@ -396,7 +468,9 @@ void AppCacheUpdateJob::HandleUrlFetchCompleted(URLRequest* request) {
<< " os_error: " << request->status().os_error()
<< " response code: " << response_code;
- // TODO(jennb): discard any stored data for this entry
+ // TODO(jennb): Discard any stored data for this entry? May be unnecessary
+ // if handled automatically by storage layer.
+
if (entry.IsExplicit() || entry.IsFallback()) {
internal_state_ = CACHE_FAILURE;
@@ -434,35 +508,88 @@ void AppCacheUpdateJob::HandleManifestRefetchCompleted(URLRequest* request) {
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.
+ // Only need to store response in storage if manifest is not already an
+ // an entry in the cache.
+ AppCacheEntry* entry = inprogress_cache_->GetEntry(manifest_url_);
+ if (entry) {
+ entry->add_types(AppCacheEntry::MANIFEST);
+ CompleteInprogressCache();
+ } else {
+ manifest_response_writer_.reset(
+ service_->storage()->CreateResponseWriter(manifest_url_));
+ scoped_refptr<HttpResponseInfoIOBuffer> io_buffer =
+ new HttpResponseInfoIOBuffer(manifest_response_info_.release());
+ manifest_response_writer_->WriteInfo(io_buffer,
+ &manifest_info_write_callback_);
+ }
+ } else {
+ LOG(INFO) << "Request status: " << request->status().status()
+ << " os_error: " << request->status().os_error()
+ << " response code: " << response_code;
+ HandleManifestRefetchFailure();
+ }
+}
+
+void AppCacheUpdateJob::OnManifestInfoWriteComplete(int result) {
+ if (result > 0) {
+ scoped_refptr<net::StringIOBuffer> io_buffer =
+ new net::StringIOBuffer(manifest_data_);
+ manifest_response_writer_->WriteData(io_buffer, manifest_data_.length(),
+ &manifest_data_write_callback_);
+ } else {
+ // Treat storage failure as if refetch of manifest failed.
+ HandleManifestRefetchFailure();
+ }
+}
+
+void AppCacheUpdateJob::OnManifestDataWriteComplete(int result) {
+ if (result > 0) {
+ AppCacheEntry entry(AppCacheEntry::MANIFEST,
+ manifest_response_writer_->response_id());
inprogress_cache_->AddOrModifyEntry(manifest_url_, entry);
- inprogress_cache_->set_update_time(base::TimeTicks::Now());
+ CompleteInprogressCache();
+ } else {
+ // Treat storage failure as if refetch of manifest failed.
+ HandleManifestRefetchFailure();
+ }
+}
+
+void AppCacheUpdateJob::CompleteInprogressCache() {
+ inprogress_cache_->set_update_time(base::TimeTicks::Now());
+ inprogress_cache_->set_complete(true);
- // TODO(jennb): start of part to make async (cache/group storage may fail)
- inprogress_cache_->set_complete(true);
- group_->AddCache(inprogress_cache_);
- protect_new_cache_.swap(inprogress_cache_);
+ protect_former_newest_cache_ = group_->newest_complete_cache();
+ group_->AddCache(inprogress_cache_);
+ protect_new_cache_.swap(inprogress_cache_);
- // TODO(jennb): write new group and cache to storage here
+ service_->storage()->StoreGroupAndNewestCache(group_, this); // async
+}
- if (update_type_ == CACHE_ATTEMPT) {
+void AppCacheUpdateJob::OnGroupAndNewestCacheStored(AppCacheGroup* group,
+ bool success) {
+ if (success) {
+ if (update_type_ == CACHE_ATTEMPT)
NotifyAllAssociatedHosts(CACHED_EVENT);
- } else {
+ else
NotifyAllAssociatedHosts(UPDATE_READY_EVENT);
- }
internal_state_ = COMPLETED;
- // TODO(jennb): end of part that needs to be made async.
+ MaybeCompleteUpdate(); // will definitely complete
} else {
- LOG(INFO) << "Request status: " << request->status().status()
- << " os_error: " << request->status().os_error()
- << " response code: " << response_code;
- ScheduleUpdateRetry(kRerunDelayMs);
- internal_state_ = CACHE_FAILURE;
+ // TODO(jennb): Change storage so clients won't need to revert group state?
+ // Change group back to reflect former newest group.
+ group_->RestoreCacheAsNewest(protect_former_newest_cache_);
+ protect_new_cache_ = NULL;
+
+ // Treat storage failure as if manifest refetch failed.
+ HandleManifestRefetchFailure();
}
+ protect_former_newest_cache_ = NULL;
+}
- MaybeCompleteUpdate(); // will definitely complete
+void AppCacheUpdateJob::HandleManifestRefetchFailure() {
+ ScheduleUpdateRetry(kRerunDelayMs);
+ internal_state_ = CACHE_FAILURE;
+ MaybeCompleteUpdate(); // will definitely complete
}
void AppCacheUpdateJob::NotifySingleHost(AppCacheHost* host,
@@ -654,7 +781,7 @@ void AppCacheUpdateJob::CopyEntryToCache(const GURL& url,
const AppCacheEntry& src,
AppCacheEntry* dest) {
DCHECK(dest);
- // TODO(jennb): copy storage key from src to dest
+ dest->set_response_id(src.response_id());
inprogress_cache_->AddEntry(url, *dest);
}
@@ -718,18 +845,28 @@ void AppCacheUpdateJob::Cancel() {
pending_master_entries_.clear();
DiscardInprogressCache();
- // TODO(jennb): cancel any storage callbacks
+
+ // Delete response writer to avoid any callbacks.
+ if (manifest_response_writer_.get())
+ manifest_response_writer_.reset();
+
+ service_->storage()->CancelDelegateCallbacks(this);
}
void AppCacheUpdateJob::DiscardInprogressCache() {
if (!inprogress_cache_)
return;
- // TODO(jennb): cleanup stored responses for entries in the cache
+ // TODO(jennb): Cleanup stored responses for entries in the cache?
+ // May not be necessary if handled automatically by storage layer.
+
inprogress_cache_ = NULL;
}
void AppCacheUpdateJob::DeleteSoon() {
+ manifest_response_writer_.reset();
+ service_->storage()->CancelDelegateCallbacks(this);
+
// Break the connection with the group so the group cannot call delete
// on this object after we've posted a task to delete ourselves.
group_->SetUpdateStatus(AppCacheGroup::IDLE);
diff --git a/webkit/appcache/appcache_update_job.h b/webkit/appcache/appcache_update_job.h
index 518404e..7b9bd51 100644
--- a/webkit/appcache/appcache_update_job.h
+++ b/webkit/appcache/appcache_update_job.h
@@ -14,13 +14,15 @@
#include "net/url_request/url_request.h"
#include "webkit/appcache/appcache.h"
#include "webkit/appcache/appcache_interfaces.h"
+#include "webkit/appcache/appcache_storage.h"
namespace appcache {
-struct UpdateJobInfo;
+class UpdateJobInfo;
// Application cache Update algorithm and state.
-class AppCacheUpdateJob : public URLRequest::Delegate {
+class AppCacheUpdateJob : public URLRequest::Delegate,
+ public AppCacheStorage::Delegate {
public:
AppCacheUpdateJob(AppCacheService* service, AppCacheGroup* group);
~AppCacheUpdateJob();
@@ -29,13 +31,10 @@ class AppCacheUpdateJob : public URLRequest::Delegate {
// 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;
+ friend class UpdateJobInfo;
// Master entries have multiple hosts, for example, the same page is opened
// in different tabs.
@@ -71,6 +70,10 @@ class AppCacheUpdateJob : public URLRequest::Delegate {
bool* defer_redirect);
// TODO(jennb): any other delegate callbacks to handle? certificate?
+ // Methods for AppCacheStorage::Delegate.
+ void OnGroupAndNewestCacheStored(AppCacheGroup* group, bool success);
+ void OnGroupMadeObsolete(AppCacheGroup* group, bool success);
+
void FetchManifest(bool is_first_fetch);
void OnResponseCompleted(URLRequest* request);
@@ -82,16 +85,24 @@ class AppCacheUpdateJob : public URLRequest::Delegate {
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.
+ // case ReadResponseData 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 OnWriteResponseComplete(int result, URLRequest* request,
+ UpdateJobInfo* info);
void HandleManifestFetchCompleted(URLRequest* request);
void ContinueHandleManifestFetchCompleted(bool changed);
+
void HandleUrlFetchCompleted(URLRequest* request);
+
void HandleManifestRefetchCompleted(URLRequest* request);
+ void OnManifestInfoWriteComplete(int result);
+ void OnManifestDataWriteComplete(int result);
+ void CompleteInprogressCache();
+ void HandleManifestRefetchFailure();
void NotifySingleHost(AppCacheHost* host, EventID event_id);
void NotifyAllPendingMasterHosts(EventID event_id);
@@ -157,6 +168,10 @@ class AppCacheUpdateJob : public URLRequest::Delegate {
// the notification when its status is set to IDLE in ~AppCacheUpdateJob.
scoped_refptr<AppCache> protect_new_cache_;
+ // Hold a reference to the group's newest cache (prior to update) in order
+ // to restore the group's newest cache if storage fails.
+ scoped_refptr<AppCache> protect_former_newest_cache_;
+
AppCacheGroup* group_;
UpdateType update_type_;
@@ -181,6 +196,11 @@ class AppCacheUpdateJob : public URLRequest::Delegate {
// Temporary storage of manifest response data for parsing and comparison.
std::string manifest_data_;
std::string manifest_refetch_data_;
+ scoped_ptr<net::HttpResponseInfo> manifest_response_info_;
+ scoped_ptr<AppCacheResponseWriter> manifest_response_writer_;
+
+ net::CompletionCallbackImpl<AppCacheUpdateJob> manifest_info_write_callback_;
+ net::CompletionCallbackImpl<AppCacheUpdateJob> manifest_data_write_callback_;
// TODO(jennb): delete when able to mock storage behavior
bool simulate_manifest_changed_;
diff --git a/webkit/appcache/appcache_update_job_unittest.cc b/webkit/appcache/appcache_update_job_unittest.cc
index be1d716..c4cb361 100644
--- a/webkit/appcache/appcache_update_job_unittest.cc
+++ b/webkit/appcache/appcache_update_job_unittest.cc
@@ -673,7 +673,7 @@ class AppCacheUpdateJobTest : public testing::Test,
// 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));
+ AppCacheEntry(AppCacheEntry::MASTER, 111));
// TODO(jennb): simulate this by mocking storage behavior instead
update->SimulateManifestChanged(true); // changed
@@ -795,13 +795,13 @@ class AppCacheUpdateJobTest : public testing::Test,
// Give the newest cache some master entries; one will fail with a 404.
cache->AddEntry(
http_server_->TestServerPage("files/notfound"),
- AppCacheEntry(AppCacheEntry::MASTER));
+ AppCacheEntry(AppCacheEntry::MASTER, 222));
cache->AddEntry(
http_server_->TestServerPage("files/explicit2"),
- AppCacheEntry(AppCacheEntry::MASTER | AppCacheEntry::FOREIGN));
+ AppCacheEntry(AppCacheEntry::MASTER | AppCacheEntry::FOREIGN, 333));
cache->AddEntry(
http_server_->TestServerPage("files/servererror"),
- AppCacheEntry(AppCacheEntry::MASTER));
+ AppCacheEntry(AppCacheEntry::MASTER, 444));
// TODO(jennb): simulate this by mocking storage behavior instead
update->SimulateManifestChanged(true); // changed
@@ -820,7 +820,7 @@ class AppCacheUpdateJobTest : public testing::Test,
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
+ AppCacheEntry(AppCacheEntry::MASTER)));
MockFrontend::HostIds ids1(1, host1->host_id());
frontend1->AddExpectedEvent(ids1, CHECKING_EVENT);
frontend1->AddExpectedEvent(ids1, DOWNLOADING_EVENT);
@@ -1012,6 +1012,8 @@ class AppCacheUpdateJobTest : public testing::Test,
}
void RetryUrlTest() {
+ ASSERT_EQ(MessageLoop::TYPE_IO, MessageLoop::current()->type());
+
// Set 1 as the retry limit (does not exceed the max).
// Expect 1 manifest fetch, 1 url fetch, 1 url retry, 1 manifest refetch.
RetryRequestTestJob::Initialize(1, RetryRequestTestJob::RETRY_AFTER_0, 4);
@@ -1039,6 +1041,147 @@ class AppCacheUpdateJobTest : public testing::Test,
WaitForUpdateToFinish();
}
+ void FailStoreNewestCacheTest() {
+ ASSERT_EQ(MessageLoop::TYPE_IO, MessageLoop::current()->type());
+
+ MakeService();
+ MockAppCacheStorage* storage =
+ reinterpret_cast<MockAppCacheStorage*>(service_->storage());
+ storage->SimulateStoreGroupAndNewestCacheFailure();
+
+ 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());
+
+ // Set up checks for when update job finishes.
+ do_checks_after_update_finished_ = true;
+ expect_group_obsolete_ = false;
+ expect_group_has_cache_ = false; // storage failed
+ frontend->AddExpectedEvent(MockFrontend::HostIds(1, host->host_id()),
+ CHECKING_EVENT);
+
+ WaitForUpdateToFinish();
+ }
+
+ void UpgradeFailStoreNewestCacheTest() {
+ ASSERT_EQ(MessageLoop::TYPE_IO, MessageLoop::current()->type());
+
+ MakeService();
+ MockAppCacheStorage* storage =
+ reinterpret_cast<MockAppCacheStorage*>(service_->storage());
+ storage->SimulateStoreGroupAndNewestCacheFailure();
+
+ 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_->storage()->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());
+
+ // 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; // unchanged
+ 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, 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, ERROR_EVENT);
+
+ WaitForUpdateToFinish();
+ }
+
+ void FailMakeGroupObsoleteTest() {
+ ASSERT_EQ(MessageLoop::TYPE_IO, MessageLoop::current()->type());
+
+ MakeService();
+ MockAppCacheStorage* storage =
+ reinterpret_cast<MockAppCacheStorage*>(service_->storage());
+ storage->SimulateMakeGroupObsoleteFailure();
+
+ 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_ = false;
+ expect_group_has_cache_ = false;
+ frontend->AddExpectedEvent(MockFrontend::HostIds(1, host->host_id()),
+ CHECKING_EVENT);
+
+ WaitForUpdateToFinish();
+ }
+
+ void UpgradeFailMakeGroupObsoleteTest() {
+ ASSERT_EQ(MessageLoop::TYPE_IO, MessageLoop::current()->type());
+
+ MakeService();
+ MockAppCacheStorage* storage =
+ reinterpret_cast<MockAppCacheStorage*>(service_->storage());
+ storage->SimulateMakeGroupObsoleteFailure();
+
+ 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_ = 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 WaitForUpdateToFinish() {
if (group_->update_status() == AppCacheGroup::IDLE)
UpdateFinished();
@@ -1053,8 +1196,8 @@ class AppCacheUpdateJobTest : public testing::Test,
}
void UpdateFinished() {
- // We unwind the stack prior to finishing up to let stack
- // based objects get deleted.
+ // We unwind the stack prior to finishing up to let stack-based objects
+ // get deleted.
MessageLoop::current()->PostTask(FROM_HERE,
method_factory_.NewRunnableMethod(
&AppCacheUpdateJobTest::UpdateFinishedUnwound));
@@ -1119,8 +1262,29 @@ class AppCacheUpdateJobTest : public testing::Test,
std::find(group_->old_caches().begin(),
group_->old_caches().end(), expect_old_cache_));
}
- if (expect_newest_cache_)
+ if (expect_newest_cache_) {
EXPECT_EQ(expect_newest_cache_, group_->newest_complete_cache());
+ EXPECT_TRUE(group_->old_caches().end() ==
+ std::find(group_->old_caches().begin(),
+ group_->old_caches().end(), expect_newest_cache_));
+ } else {
+ // Tests that don't know which newest cache to expect contain updates
+ // that succeed (because the update creates a new cache whose pointer
+ // is unknown to the test). Check group and newest cache were stored
+ // when update succeeds.
+ MockAppCacheStorage* storage =
+ reinterpret_cast<MockAppCacheStorage*>(service_->storage());
+ EXPECT_TRUE(storage->IsGroupStored(group_));
+ EXPECT_TRUE(storage->IsCacheStored(group_->newest_complete_cache()));
+
+ // Check that all entries in the newest cache were stored.
+ const AppCache::EntryMap& entries =
+ group_->newest_complete_cache()->entries();
+ for (AppCache::EntryMap::const_iterator it = entries.begin();
+ it != entries.end(); ++it) {
+ EXPECT_NE(kNoResponseId, it->second.response_id());
+ }
+ }
} else {
EXPECT_TRUE(group_->newest_complete_cache() == NULL);
}
@@ -1458,4 +1622,20 @@ TEST_F(AppCacheUpdateJobTest, RetryUrl) {
RunTestOnIOThread(&AppCacheUpdateJobTest::RetryUrlTest);
}
+TEST_F(AppCacheUpdateJobTest, FailStoreNewestCache) {
+ RunTestOnIOThread(&AppCacheUpdateJobTest::FailStoreNewestCacheTest);
+}
+
+TEST_F(AppCacheUpdateJobTest, UpgradeFailStoreNewestCache) {
+ RunTestOnIOThread(&AppCacheUpdateJobTest::UpgradeFailStoreNewestCacheTest);
+}
+
+TEST_F(AppCacheUpdateJobTest, FailMakeGroupObsolete) {
+ RunTestOnIOThread(&AppCacheUpdateJobTest::FailMakeGroupObsoleteTest);
+}
+
+TEST_F(AppCacheUpdateJobTest, UpgradeFailMakeGroupObsolete) {
+ RunTestOnIOThread(&AppCacheUpdateJobTest::UpgradeFailMakeGroupObsoleteTest);
+}
+
} // namespace appcache
diff --git a/webkit/appcache/mock_appcache_storage.cc b/webkit/appcache/mock_appcache_storage.cc
index 3c626ec..34bfbb7 100644
--- a/webkit/appcache/mock_appcache_storage.cc
+++ b/webkit/appcache/mock_appcache_storage.cc
@@ -27,7 +27,9 @@ namespace appcache {
MockAppCacheStorage::MockAppCacheStorage(AppCacheService* service)
: AppCacheStorage(service),
- ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) {
+ ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)),
+ simulate_make_group_obsolete_failure_(false),
+ simulate_store_group_and_newest_cache_failure_(false) {
last_cache_id_ = 0;
last_entry_id_ = 0;
last_group_id_ = 0;
@@ -156,6 +158,12 @@ void MockAppCacheStorage::ProcessStoreGroupAndNewestCache(
scoped_refptr<DelegateReference> delegate_ref) {
DCHECK(group->newest_complete_cache() == newest_cache.get());
+ if (simulate_store_group_and_newest_cache_failure_) {
+ if (delegate_ref->delegate)
+ delegate_ref->delegate->OnGroupAndNewestCacheStored(group, false);
+ return;
+ }
+
AddStoredGroup(group);
AddStoredCache(group->newest_complete_cache());
@@ -195,6 +203,12 @@ void MockAppCacheStorage::ProcessFindResponseForMainRequest(
void MockAppCacheStorage::ProcessMakeGroupObsolete(
scoped_refptr<AppCacheGroup> group,
scoped_refptr<DelegateReference> delegate_ref) {
+ if (simulate_make_group_obsolete_failure_) {
+ if (delegate_ref->delegate)
+ delegate_ref->delegate->OnGroupMadeObsolete(group, false);
+ return;
+ }
+
RemoveStoredGroup(group);
if (group->newest_complete_cache())
RemoveStoredCache(group->newest_complete_cache());
diff --git a/webkit/appcache/mock_appcache_storage.h b/webkit/appcache/mock_appcache_storage.h
index ca6bd70..eaf742b 100644
--- a/webkit/appcache/mock_appcache_storage.h
+++ b/webkit/appcache/mock_appcache_storage.h
@@ -41,6 +41,8 @@ class MockAppCacheStorage : public AppCacheStorage {
const GURL& manifest_url, const std::vector<int64>& response_ids);
private:
+ friend class AppCacheUpdateJobTest;
+
typedef base::hash_map<int64, scoped_refptr<AppCache> > StoredCacheMap;
typedef std::map<GURL, scoped_refptr<AppCacheGroup> > StoredGroupMap;
typedef std::set<int64> DoomedResponseIds;
@@ -70,6 +72,9 @@ class MockAppCacheStorage : public AppCacheStorage {
void AddStoredGroup(AppCacheGroup* group);
void RemoveStoredGroup(AppCacheGroup* group);
+ bool IsGroupStored(const AppCacheGroup* group) {
+ return stored_groups_.find(group->manifest_url()) != stored_groups_.end();
+ }
// These helpers determine when certain operations should complete
// asynchronously vs synchronously to faithfully mimic, or mock,
@@ -87,6 +92,14 @@ class MockAppCacheStorage : public AppCacheStorage {
return disk_cache_.get();
}
+ // Simulate failures for testing.
+ void SimulateMakeGroupObsoleteFailure() {
+ simulate_make_group_obsolete_failure_ = true;
+ }
+ void SimulateStoreGroupAndNewestCacheFailure() {
+ simulate_store_group_and_newest_cache_failure_ = true;
+ }
+
StoredCacheMap stored_caches_;
StoredGroupMap stored_groups_;
DoomedResponseIds doomed_response_ids_;
@@ -94,6 +107,9 @@ class MockAppCacheStorage : public AppCacheStorage {
std::deque<Task*> pending_tasks_;
ScopedRunnableMethodFactory<MockAppCacheStorage> method_factory_;
+ bool simulate_make_group_obsolete_failure_;
+ bool simulate_store_group_and_newest_cache_failure_;
+
FRIEND_TEST(MockAppCacheStorageTest, CreateGroup);
FRIEND_TEST(MockAppCacheStorageTest, LoadCache_FarHit);
FRIEND_TEST(MockAppCacheStorageTest, LoadGroupAndCache_FarHit);