diff options
-rw-r--r-- | webkit/appcache/appcache_group.cc | 20 | ||||
-rw-r--r-- | webkit/appcache/appcache_group.h | 4 | ||||
-rw-r--r-- | webkit/appcache/appcache_update_job.cc | 263 | ||||
-rw-r--r-- | webkit/appcache/appcache_update_job.h | 36 | ||||
-rw-r--r-- | webkit/appcache/appcache_update_job_unittest.cc | 196 | ||||
-rw-r--r-- | webkit/appcache/mock_appcache_storage.cc | 16 | ||||
-rw-r--r-- | webkit/appcache/mock_appcache_storage.h | 16 |
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); |