diff options
author | jennb@chromium.org <jennb@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-12-01 01:55:37 +0000 |
---|---|---|
committer | jennb@chromium.org <jennb@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-12-01 01:55:37 +0000 |
commit | 82a25e9eb5eeb5d004e868c22a83ce87eba79890 (patch) | |
tree | d966a88b0b2ad13f8d3160b339650e8320c587af | |
parent | f73662970242609df7629914ce2476fb74035966 (diff) | |
download | chromium_src-82a25e9eb5eeb5d004e868c22a83ce87eba79890.zip chromium_src-82a25e9eb5eeb5d004e868c22a83ce87eba79890.tar.gz chromium_src-82a25e9eb5eeb5d004e868c22a83ce87eba79890.tar.bz2 |
Revert 449036, which rolled back 402098, and fix the valgrind error that caused the original rollback:
Revert 33394 (due to valgrind errors) - Appcache update support for pending
master entries:
Update process issues a URL request to fetch pending master entries.
Pending master entry fetch logic kept separate from regular url fetching as
this will be the case in the longterm solution.
No optimizations to avoid issuing URL request if pending master entry is also
listed in the manifest. (simpler)
Only optimized to prevent refetching something that has already been
successfully fetched.
Longterm optimized solution should be to siphon the responses as the master
resource is downloaded instead of having the update job issue URL requests.
TEST=new tests for update jobs with pending master entries
BUG=none
Review URL: http://codereview.chromium.org/449039
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@33410 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | webkit/appcache/appcache_host.h | 7 | ||||
-rw-r--r-- | webkit/appcache/appcache_update_job.cc | 377 | ||||
-rw-r--r-- | webkit/appcache/appcache_update_job.h | 39 | ||||
-rw-r--r-- | webkit/appcache/appcache_update_job_unittest.cc | 760 | ||||
-rw-r--r-- | webkit/appcache/data/appcache_unittest/bad-manifest | 2 | ||||
-rw-r--r-- | webkit/appcache/data/appcache_unittest/bad-manifest.mock-http-headers | 2 |
6 files changed, 1103 insertions, 84 deletions
diff --git a/webkit/appcache/appcache_host.h b/webkit/appcache/appcache_host.h index 0622e3f..f154bae 100644 --- a/webkit/appcache/appcache_host.h +++ b/webkit/appcache/appcache_host.h @@ -84,6 +84,12 @@ class AppCacheHost : public AppCacheStorage::Delegate, // Used to ensure that a loaded appcache survives a frame navigation. void LoadMainResourceCache(int64 cache_id); + // Used by the update job to keep track of which hosts are associated + // with which pending master entries. + const GURL& pending_master_entry_url() const { + return new_master_entry_url_; + } + int host_id() const { return host_id_; } AppCacheService* service() const { return service_; } AppCacheFrontend* frontend() const { return frontend_; } @@ -163,6 +169,7 @@ class AppCacheHost : public AppCacheStorage::Delegate, ObserverList<Observer> observers_; friend class AppCacheRequestHandlerTest; + friend class AppCacheUpdateJobTest; FRIEND_TEST(AppCacheTest, CleanupUnusedCache); FRIEND_TEST(AppCacheGroupTest, CleanupUnusedGroup); FRIEND_TEST(AppCacheHostTest, Basic); diff --git a/webkit/appcache/appcache_update_job.cc b/webkit/appcache/appcache_update_job.cc index 83190d0..8cd752f 100644 --- a/webkit/appcache/appcache_update_job.cc +++ b/webkit/appcache/appcache_update_job.cc @@ -10,7 +10,6 @@ #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 { @@ -25,6 +24,7 @@ class UpdateJobInfo : public URLRequest::UserData { enum RequestType { MANIFEST_FETCH, URL_FETCH, + MASTER_ENTRY_FETCH, MANIFEST_REFETCH, }; @@ -123,6 +123,8 @@ AppCacheUpdateJob::~AppCacheUpdateJob() { DCHECK(!manifest_url_request_); DCHECK(pending_url_fetches_.empty()); DCHECK(!inprogress_cache_); + DCHECK(pending_master_entries_.empty()); + DCHECK(master_entry_fetches_.empty()); if (group_) group_->SetUpdateStatus(AppCacheGroup::IDLE); @@ -132,23 +134,40 @@ void AppCacheUpdateJob::StartUpdate(AppCacheHost* host, const GURL& new_master_resource) { DCHECK(group_->update_job() == this); + bool is_new_pending_master_entry = false; if (!new_master_resource.is_empty()) { - /* TODO(jennb): uncomment when processing master entries is implemented + DCHECK(new_master_resource == host->pending_master_entry_url()); + DCHECK(!new_master_resource.has_ref()); + DCHECK(new_master_resource.GetOrigin() == manifest_url_.GetOrigin()); + + // Cannot add more to this update if already terminating. + if (IsTerminating()) { + // TODO(jennb): requeue in group + return; + } + std::pair<PendingMasters::iterator, bool> ret = pending_master_entries_.insert( PendingMasters::value_type(new_master_resource, PendingHosts())); + is_new_pending_master_entry = ret.second; ret.first->second.push_back(host); - */ + host->AddObserver(this); } // Notify host (if any) if already checking or downloading. - appcache::AppCacheGroup::UpdateStatus update_status = group_->update_status(); + 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); + + // Add to fetch list or an existing entry if already fetched. + if (!new_master_resource.is_empty()) { + AddMasterEntryToFetchList(host, new_master_resource, + is_new_pending_master_entry); + } } return; } @@ -164,6 +183,11 @@ void AppCacheUpdateJob::StartUpdate(AppCacheHost* host, NotifySingleHost(host, CHECKING_EVENT); } + if (!new_master_resource.is_empty()) { + AddMasterEntryToFetchList(host, new_master_resource, + is_new_pending_master_entry); + } + FetchManifest(true); } @@ -187,7 +211,8 @@ void AppCacheUpdateJob::OnResponseStarted(URLRequest *request) { // completion before reading any response data. UpdateJobInfo* info = static_cast<UpdateJobInfo*>(request->GetUserData(this)); - if (info->type_ == UpdateJobInfo::URL_FETCH) { + if (info->type_ == UpdateJobInfo::URL_FETCH || + info->type_ == UpdateJobInfo::MASTER_ENTRY_FETCH) { info->SetUpResponseWriter( service_->storage()->CreateResponseWriter(manifest_url_), this, request); @@ -250,6 +275,7 @@ bool AppCacheUpdateJob::ConsumeResponseData(URLRequest* request, manifest_data_.append(info->buffer_->data(), bytes_read); break; case UpdateJobInfo::URL_FETCH: + case UpdateJobInfo::MASTER_ENTRY_FETCH: DCHECK(info->response_writer_.get()); info->response_writer_->WriteData(info->buffer_, bytes_read, &info->write_callback_); @@ -266,8 +292,6 @@ bool AppCacheUpdateJob::ConsumeResponseData(URLRequest* request, void AppCacheUpdateJob::OnWriteResponseComplete(int result, URLRequest* request, UpdateJobInfo* info) { - DCHECK(internal_state_ == DOWNLOADING); - if (result < 0) { request->Cancel(); OnResponseCompleted(request); @@ -302,6 +326,9 @@ void AppCacheUpdateJob::OnResponseCompleted(URLRequest* request) { case UpdateJobInfo::URL_FETCH: HandleUrlFetchCompleted(request); break; + case UpdateJobInfo::MASTER_ENTRY_FETCH: + HandleMasterEntryFetchCompleted(request); + break; case UpdateJobInfo::MANIFEST_REFETCH: HandleManifestRefetchCompleted(request); break; @@ -340,6 +367,10 @@ bool AppCacheUpdateJob::RetryRequest(URLRequest* request) { pending_url_fetches_.erase(url); pending_url_fetches_.insert(PendingUrlFetches::value_type(url, retry)); break; + case UpdateJobInfo::MASTER_ENTRY_FETCH: + master_entry_fetches_.erase(url); + master_entry_fetches_.insert(PendingUrlFetches::value_type(url, retry)); + break; default: NOTREACHED(); } @@ -354,21 +385,16 @@ 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(); + int response_code = -1; std::string mime_type; - request->GetMimeType(&mime_type); - manifest_response_info_.reset( - new net::HttpResponseInfo(request->response_info())); + if (request->status().is_success()) { + response_code = request->GetResponseCode(); + request->GetMimeType(&mime_type); + } if ((response_code / 100 == 2) && mime_type == kManifestMimeType) { + manifest_response_info_.reset( + new net::HttpResponseInfo(request->response_info())); if (update_type_ == UPGRADE_ATTEMPT) CheckIfManifestChanged(); // continues asynchronously else @@ -378,15 +404,16 @@ void AppCacheUpdateJob::HandleManifestFetchCompleted(URLRequest* request) { } else if (response_code == 404 || response_code == 410) { service_->storage()->MakeGroupObsolete(group_, this); // async } else { - LOG(INFO) << "Cache failure, response code: " << response_code; internal_state_ = CACHE_FAILURE; + CancelAllMasterEntryFetches(); MaybeCompleteUpdate(); // if not done, run async cache failure steps } } void AppCacheUpdateJob::OnGroupMadeObsolete(AppCacheGroup* group, bool success) { - NotifyAllPendingMasterHosts(ERROR_EVENT); + DCHECK(master_entry_fetches_.empty()); + CancelAllMasterEntryFetches(); if (success) { DCHECK(group->is_obsolete()); NotifyAllAssociatedHosts(OBSOLETE_EVENT); @@ -404,6 +431,9 @@ void AppCacheUpdateJob::ContinueHandleManifestFetchCompleted(bool changed) { if (!changed) { DCHECK(update_type_ == UPGRADE_ATTEMPT); internal_state_ = NO_UPDATE; + + // Wait for pending master entries to download. + FetchMasterEntries(); MaybeCompleteUpdate(); // if not done, run async 6.9.4 step 7 substeps return; } @@ -413,6 +443,7 @@ void AppCacheUpdateJob::ContinueHandleManifestFetchCompleted(bool changed) { manifest_data_.length(), manifest)) { LOG(INFO) << "Failed to parse manifest: " << manifest_url_; internal_state_ = CACHE_FAILURE; + CancelAllMasterEntryFetches(); MaybeCompleteUpdate(); // if not done, run async cache failure steps return; } @@ -437,6 +468,7 @@ void AppCacheUpdateJob::ContinueHandleManifestFetchCompleted(bool changed) { group_->SetUpdateStatus(AppCacheGroup::DOWNLOADING); NotifyAllAssociatedHosts(DOWNLOADING_EVENT); FetchUrls(); + FetchMasterEntries(); MaybeCompleteUpdate(); // if not done, continues when async fetches complete } @@ -451,15 +483,14 @@ void AppCacheUpdateJob::HandleUrlFetchCompleted(URLRequest* request) { ? request->GetResponseCode() : -1; 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)) { // Associate storage with the new entry. + UpdateJobInfo* info = + static_cast<UpdateJobInfo*>(request->GetUserData(this)); DCHECK(info->response_writer_.get()); entry.set_response_id(info->response_writer_->response_id()); - inprogress_cache_->AddEntry(url, entry); + inprogress_cache_->AddOrModifyEntry(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 @@ -476,17 +507,8 @@ void AppCacheUpdateJob::HandleUrlFetchCompleted(URLRequest* request) { 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) { - delete it->second; - } - - url_fetches_completed_ += - pending_url_fetches_.size() + urls_to_fetch_.size(); - pending_url_fetches_.clear(); - urls_to_fetch_.clear(); + CancelAllUrlFetches(); + CancelAllMasterEntryFetches(); } else if (response_code == 404 || response_code == 410) { // Entry is skipped. They are dropped from the cache. } else if (update_type_ == UPGRADE_ATTEMPT) { @@ -505,6 +527,83 @@ void AppCacheUpdateJob::HandleUrlFetchCompleted(URLRequest* request) { MaybeCompleteUpdate(); } +void AppCacheUpdateJob::HandleMasterEntryFetchCompleted(URLRequest* request) { + DCHECK(internal_state_ == NO_UPDATE || internal_state_ == DOWNLOADING); + + // TODO(jennb): Handle downloads completing during cache failure when update + // no longer fetches master entries directly. For now, we cancel all pending + // master entry fetches when entering cache failure state so this will never + // be called in CACHE_FAILURE state. + + const GURL& url = request->original_url(); + master_entry_fetches_.erase(url); + ++master_entries_completed_; + + int response_code = request->status().is_success() + ? request->GetResponseCode() : -1; + + PendingMasters::iterator found = pending_master_entries_.find(url); + DCHECK(found != pending_master_entries_.end()); + PendingHosts& hosts = found->second; + + // Section 6.9.4. No update case: step 7.3, else step 22. + if (response_code / 100 == 2) { + // Add fetched master entry to the appropriate cache. + UpdateJobInfo* info = + static_cast<UpdateJobInfo*>(request->GetUserData(this)); + AppCache* cache = inprogress_cache_ ? inprogress_cache_.get() : + group_->newest_complete_cache(); + DCHECK(info->response_writer_.get()); + AppCacheEntry master_entry(AppCacheEntry::MASTER, + info->response_writer_->response_id()); + cache->AddOrModifyEntry(url, master_entry); + + // In no-update case, associate host with the newest cache. + if (!inprogress_cache_) { + DCHECK(cache == group_->newest_complete_cache()); + for (PendingHosts::iterator host_it = hosts.begin(); + host_it != hosts.end(); ++host_it) { + (*host_it)->AssociateCache(cache); + } + } + } else { + HostNotifier host_notifier; + for (PendingHosts::iterator host_it = hosts.begin(); + host_it != hosts.end(); ++host_it) { + AppCacheHost* host = *host_it; + host_notifier.AddHost(host); + + // In downloading case, disassociate host from inprogress cache. + if (inprogress_cache_) { + host->AssociateCache(NULL); + } + + host->RemoveObserver(this); + } + hosts.clear(); + host_notifier.SendNotifications(ERROR_EVENT); + + // In downloading case, update result is different if all master entries + // failed vs. only some failing. + if (inprogress_cache_) { + // Only count successful downloads to know if all master entries failed. + pending_master_entries_.erase(found); + --master_entries_completed_; + + // Section 6.9.4, step 22.3. + if (update_type_ == CACHE_ATTEMPT && pending_master_entries_.empty()) { + CancelAllUrlFetches(); + internal_state_ = CACHE_FAILURE; + } + } + } + + if (internal_state_ != CACHE_FAILURE) + FetchMasterEntries(); + + MaybeCompleteUpdate(); +} + void AppCacheUpdateJob::HandleManifestRefetchCompleted(URLRequest* request) { DCHECK(internal_state_ == REFETCH_MANIFEST); manifest_url_request_ = NULL; @@ -512,7 +611,7 @@ void AppCacheUpdateJob::HandleManifestRefetchCompleted(URLRequest* request) { int response_code = request->status().is_success() ? request->GetResponseCode() : -1; if (response_code == 304 || manifest_data_ == manifest_refetch_data_) { - // Only need to store response in storage if manifest is not already an + // Only need to store response in storage if manifest is not already // an entry in the cache. AppCacheEntry* entry = inprogress_cache_->GetEntry(manifest_url_); if (entry) { @@ -570,11 +669,6 @@ void AppCacheUpdateJob::OnGroupAndNewestCacheStored(AppCacheGroup* group, bool success) { if (success) { DCHECK_EQ(protect_new_cache_, group->newest_complete_cache()); - if (update_type_ == CACHE_ATTEMPT) - NotifyAllAssociatedHosts(CACHED_EVENT); - else - NotifyAllAssociatedHosts(UPDATE_READY_EVENT); - internal_state_ = COMPLETED; MaybeCompleteUpdate(); // will definitely complete } else { protect_new_cache_ = NULL; @@ -585,9 +679,9 @@ void AppCacheUpdateJob::OnGroupAndNewestCacheStored(AppCacheGroup* group, } void AppCacheUpdateJob::HandleManifestRefetchFailure() { - ScheduleUpdateRetry(kRerunDelayMs); - internal_state_ = CACHE_FAILURE; - MaybeCompleteUpdate(); // will definitely complete + ScheduleUpdateRetry(kRerunDelayMs); + internal_state_ = CACHE_FAILURE; + MaybeCompleteUpdate(); // will definitely complete } void AppCacheUpdateJob::NotifySingleHost(AppCacheHost* host, @@ -638,6 +732,17 @@ void AppCacheUpdateJob::NotifyAllAssociatedHosts(EventID event_id) { host_notifier.SendNotifications(event_id); } +void AppCacheUpdateJob::OnDestructionImminent(AppCacheHost* host) { + // The host is about to be deleted; remove from our collection. + PendingMasters::iterator found = + pending_master_entries_.find(host->pending_master_entry_url()); + DCHECK(found != pending_master_entries_.end()); + PendingHosts& hosts = found->second; + PendingHosts::iterator it = std::find(hosts.begin(), hosts.end(), host); + DCHECK(it != hosts.end()); + hosts.erase(it); +} + void AppCacheUpdateJob::CheckIfManifestChanged() { DCHECK(update_type_ == UPGRADE_ATTEMPT); AppCacheEntry* entry = @@ -724,6 +829,8 @@ void AppCacheUpdateJob::FetchUrls() { AppCacheEntry& entry = it->second; if (ShouldSkipUrlFetch(entry)) { ++url_fetches_completed_; + } else if (AlreadyFetchedEntry(url, entry.types())) { + ++url_fetches_completed_; // saved a URL request } else if (!storage_checked && MaybeLoadFromNewestCache(url, entry)) { // Continues asynchronously after data is loaded from newest cache. } else { @@ -739,6 +846,19 @@ void AppCacheUpdateJob::FetchUrls() { } } +void AppCacheUpdateJob::CancelAllUrlFetches() { + // Cancel any pending URL requests. + for (PendingUrlFetches::iterator it = pending_url_fetches_.begin(); + it != pending_url_fetches_.end(); ++it) { + delete it->second; + } + + url_fetches_completed_ += + pending_url_fetches_.size() + urls_to_fetch_.size(); + pending_url_fetches_.clear(); + urls_to_fetch_.clear(); +} + bool AppCacheUpdateJob::ShouldSkipUrlFetch(const AppCacheEntry& entry) { if (entry.IsExplicit() || entry.IsFallback()) { return false; @@ -748,6 +868,133 @@ bool AppCacheUpdateJob::ShouldSkipUrlFetch(const AppCacheEntry& entry) { return false; } +bool AppCacheUpdateJob::AlreadyFetchedEntry(const GURL& url, + int entry_type) { + DCHECK(internal_state_ == DOWNLOADING || internal_state_ == NO_UPDATE); + AppCacheEntry* existing = inprogress_cache_ ? + inprogress_cache_->GetEntry(url) : + group_->newest_complete_cache()->GetEntry(url); + if (existing) { + existing->add_types(entry_type); + return true; + } + return false; +} + +void AppCacheUpdateJob::AddMasterEntryToFetchList(AppCacheHost* host, + const GURL& url, + bool is_new) { + DCHECK(!IsTerminating()); + + if (internal_state_ == DOWNLOADING || internal_state_ == NO_UPDATE) { + AppCache* cache; + if (inprogress_cache_) { + host->AssociateCache(inprogress_cache_); // always associate + cache = inprogress_cache_.get(); + } else { + cache = group_->newest_complete_cache(); + } + + // Update existing entry if it has already been fetched. + AppCacheEntry* entry = cache->GetEntry(url); + if (entry) { + entry->add_types(AppCacheEntry::MASTER); + if (internal_state_ == NO_UPDATE) + host->AssociateCache(cache); // only associate if have entry + if (is_new) + ++master_entries_completed_; // pretend fetching completed + return; + } + } + + // Add to fetch list if not already fetching. + if (master_entry_fetches_.find(url) == master_entry_fetches_.end()) { + master_entries_to_fetch_.insert(url); + if (internal_state_ == DOWNLOADING || internal_state_ == NO_UPDATE) + FetchMasterEntries(); + } +} + +void AppCacheUpdateJob::FetchMasterEntries() { + DCHECK(internal_state_ == NO_UPDATE || internal_state_ == DOWNLOADING); + + // Fetch each master entry in the list, up to the concurrent limit. + // Additional fetches will be triggered as each fetch completes. + while (master_entry_fetches_.size() < kMaxConcurrentUrlFetches && + !master_entries_to_fetch_.empty()) { + const GURL& url = *master_entries_to_fetch_.begin(); + + if (AlreadyFetchedEntry(url, AppCacheEntry::MASTER)) { + ++master_entries_completed_; // saved a URL request + + // In no update case, associate hosts to newest cache in group + // now that master entry has been "successfully downloaded". + if (internal_state_ == NO_UPDATE) { + DCHECK(!inprogress_cache_.get()); + AppCache* cache = group_->newest_complete_cache(); + PendingMasters::iterator found = pending_master_entries_.find(url); + DCHECK(found != pending_master_entries_.end()); + PendingHosts& hosts = found->second; + for (PendingHosts::iterator host_it = hosts.begin(); + host_it != hosts.end(); ++host_it) { + (*host_it)->AssociateCache(cache); + } + } + } else { + // Send URL request for the master entry. + URLRequest* request = new URLRequest(url, this); + request->SetUserData(this, + new UpdateJobInfo(UpdateJobInfo::MASTER_ENTRY_FETCH)); + request->set_context(service_->request_context()); + request->set_load_flags( + request->load_flags() | net::LOAD_DISABLE_INTERCEPT); + request->Start(); + master_entry_fetches_.insert(PendingUrlFetches::value_type(url, request)); + } + + master_entries_to_fetch_.erase(master_entries_to_fetch_.begin()); + } +} + +void AppCacheUpdateJob::CancelAllMasterEntryFetches() { + // For now, cancel all in-progress fetches for master entries and pretend + // all master entries fetches have completed. + // TODO(jennb): Delete this when update no longer fetches master entries + // directly. + + // Cancel all in-progress fetches. + for (PendingUrlFetches::iterator it = master_entry_fetches_.begin(); + it != master_entry_fetches_.end(); ++it) { + delete it->second; + master_entries_to_fetch_.insert(it->first); // back in unfetched list + } + master_entry_fetches_.clear(); + + master_entries_completed_ += master_entries_to_fetch_.size(); + + // Cache failure steps, step 2. + // Pretend all master entries that have not yet been fetched have completed + // downloading. Unassociate hosts from any appcache and send ERROR event. + HostNotifier host_notifier; + while (!master_entries_to_fetch_.empty()) { + const GURL& url = *master_entries_to_fetch_.begin(); + PendingMasters::iterator found = pending_master_entries_.find(url); + DCHECK(found != pending_master_entries_.end()); + PendingHosts& hosts = found->second; + for (PendingHosts::iterator host_it = hosts.begin(); + host_it != hosts.end(); ++host_it) { + AppCacheHost* host = *host_it; + host->AssociateCache(NULL); + host_notifier.AddHost(host); + host->RemoveObserver(this); + } + hosts.clear(); + + master_entries_to_fetch_.erase(master_entries_to_fetch_.begin()); + } + host_notifier.SendNotifications(ERROR_EVENT); +} + bool AppCacheUpdateJob::MaybeLoadFromNewestCache(const GURL& url, AppCacheEntry& entry) { if (update_type_ != UPGRADE_ATTEMPT) @@ -787,13 +1034,13 @@ void AppCacheUpdateJob::CopyEntryToCache(const GURL& url, AppCacheEntry* dest) { DCHECK(dest); dest->set_response_id(src.response_id()); - inprogress_cache_->AddEntry(url, *dest); + inprogress_cache_->AddOrModifyEntry(url, *dest); } void AppCacheUpdateJob::MaybeCompleteUpdate() { // Must wait for any pending master entries or url fetches to complete. if (master_entries_completed_ != pending_master_entries_.size() || - url_fetches_completed_ != url_file_list_.size() ) { + url_fetches_completed_ != url_file_list_.size()) { DCHECK(internal_state_ != COMPLETED); return; } @@ -802,17 +1049,22 @@ void AppCacheUpdateJob::MaybeCompleteUpdate() { case NO_UPDATE: // 6.9.4 steps 7.3-7.7. NotifyAllAssociatedHosts(NO_UPDATE_EVENT); - pending_master_entries_.clear(); internal_state_ = COMPLETED; break; case DOWNLOADING: internal_state_ = REFETCH_MANIFEST; FetchManifest(false); break; + case REFETCH_MANIFEST: + if (update_type_ == CACHE_ATTEMPT) + NotifyAllAssociatedHosts(CACHED_EVENT); + else + NotifyAllAssociatedHosts(UPDATE_READY_EVENT); + internal_state_ = COMPLETED; + break; case CACHE_FAILURE: // 6.9.4 cache failure steps 2-8. NotifyAllAssociatedHosts(ERROR_EVENT); - pending_master_entries_.clear(); DiscardInprogressCache(); // For a CACHE_ATTEMPT, group will be discarded when the host(s) that // started this update removes its reference to the group. Nothing more @@ -847,8 +1099,15 @@ void AppCacheUpdateJob::Cancel() { it != pending_url_fetches_.end(); ++it) { delete it->second; } + pending_url_fetches_.clear(); - pending_master_entries_.clear(); + for (PendingUrlFetches::iterator it = master_entry_fetches_.begin(); + it != master_entry_fetches_.end(); ++it) { + delete it->second; + } + master_entry_fetches_.clear(); + + ClearPendingMasterEntries(); DiscardInprogressCache(); // Delete response writer to avoid any callbacks. @@ -858,10 +1117,27 @@ void AppCacheUpdateJob::Cancel() { service_->storage()->CancelDelegateCallbacks(this); } +void AppCacheUpdateJob::ClearPendingMasterEntries() { + 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) { + (*host_it)->RemoveObserver(this); + } + } + + pending_master_entries_.clear(); +} + void AppCacheUpdateJob::DiscardInprogressCache() { if (!inprogress_cache_) return; + AppCache::AppCacheHosts& hosts = inprogress_cache_->associated_hosts(); + while (!hosts.empty()) + (*hosts.begin())->AssociateCache(NULL); + // TODO(jennb): Cleanup stored responses for entries in the cache? // May not be necessary if handled automatically by storage layer. @@ -869,6 +1145,7 @@ void AppCacheUpdateJob::DiscardInprogressCache() { } void AppCacheUpdateJob::DeleteSoon() { + ClearPendingMasterEntries(); manifest_response_writer_.reset(); service_->storage()->CancelDelegateCallbacks(this); diff --git a/webkit/appcache/appcache_update_job.h b/webkit/appcache/appcache_update_job.h index b60ca0b..7c60ef1 100644 --- a/webkit/appcache/appcache_update_job.h +++ b/webkit/appcache/appcache_update_job.h @@ -7,12 +7,16 @@ #include <deque> #include <map> +#include <set> +#include <string> +#include <vector> #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_host.h" #include "webkit/appcache/appcache_interfaces.h" #include "webkit/appcache/appcache_storage.h" @@ -22,7 +26,8 @@ class UpdateJobInfo; // Application cache Update algorithm and state. class AppCacheUpdateJob : public URLRequest::Delegate, - public AppCacheStorage::Delegate { + public AppCacheStorage::Delegate, + public AppCacheHost::Observer { public: AppCacheUpdateJob(AppCacheService* service, AppCacheGroup* group); ~AppCacheUpdateJob(); @@ -56,6 +61,8 @@ class AppCacheUpdateJob : public URLRequest::Delegate, FETCH_MANIFEST, NO_UPDATE, DOWNLOADING, + + // Every state after this comment indicates the update is terminating. REFETCH_MANIFEST, CACHE_FAILURE, CANCELLED, @@ -74,6 +81,10 @@ class AppCacheUpdateJob : public URLRequest::Delegate, void OnGroupAndNewestCacheStored(AppCacheGroup* group, bool success); void OnGroupMadeObsolete(AppCacheGroup* group, bool success); + // Methods for AppCacheHost::Observer. + void OnCacheSelectionComplete(AppCacheHost* host) {} // N/A + void OnDestructionImminent(AppCacheHost* host); + void FetchManifest(bool is_first_fetch); void OnResponseCompleted(URLRequest* request); @@ -97,6 +108,7 @@ class AppCacheUpdateJob : public URLRequest::Delegate, void ContinueHandleManifestFetchCompleted(bool changed); void HandleUrlFetchCompleted(URLRequest* request); + void HandleMasterEntryFetchCompleted(URLRequest* request); void HandleManifestRefetchCompleted(URLRequest* request); void OnManifestInfoWriteComplete(int result); @@ -118,8 +130,22 @@ class AppCacheUpdateJob : public URLRequest::Delegate, void BuildUrlFileList(const Manifest& manifest); void AddUrlToFileList(const GURL& url, int type); void FetchUrls(); + void CancelAllUrlFetches(); bool ShouldSkipUrlFetch(const AppCacheEntry& entry); + // If entry already exists in the cache currently being updated, merge + // the entry type information with the existing entry. + // Returns true if entry exists in cache currently being updated. + bool AlreadyFetchedEntry(const GURL& url, int entry_type); + + // TODO(jennb): Delete when update no longer fetches master entries directly. + // Creates the list of master entries that need to be fetched and initiates + // fetches. + void AddMasterEntryToFetchList(AppCacheHost* host, const GURL& url, + bool is_new); + void FetchMasterEntries(); + void CancelAllMasterEntryFetches(); + // 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 @@ -144,11 +170,14 @@ class AppCacheUpdateJob : public URLRequest::Delegate, void ScheduleUpdateRetry(int delay_ms); void Cancel(); + void ClearPendingMasterEntries(); void DiscardInprogressCache(); // Deletes this object after letting the stack unwind. void DeleteSoon(); + bool IsTerminating() { return internal_state_ >= REFETCH_MANIFEST; } + // This factory will be used to schedule invocations of various methods. ScopedRunnableMethodFactory<AppCacheUpdateJob> method_factory_; @@ -171,6 +200,14 @@ class AppCacheUpdateJob : public URLRequest::Delegate, PendingMasters pending_master_entries_; size_t master_entries_completed_; + // TODO(jennb): Delete when update no longer fetches master entries directly. + // Helper containers to track which pending master entries have yet to be + // fetched and which are currently being fetched. Master entries that + // are listed in the manifest may be fetched as a regular URL instead of + // as a separate master entry fetch to optimize against duplicate fetches. + std::set<GURL> master_entries_to_fetch_; + PendingUrlFetches master_entry_fetches_; + // URLs of files to fetch along with their flags. AppCache::EntryMap url_file_list_; size_t url_fetches_completed_; diff --git a/webkit/appcache/appcache_update_job_unittest.cc b/webkit/appcache/appcache_update_job_unittest.cc index 2f4d1b6..734f230 100644 --- a/webkit/appcache/appcache_update_job_unittest.cc +++ b/webkit/appcache/appcache_update_job_unittest.cc @@ -21,6 +21,8 @@ const wchar_t kDocRoot[] = L"webkit/appcache/data/appcache_unittest"; class MockFrontend : public AppCacheFrontend { public: + MockFrontend() : start_update_trigger_(CHECKING_EVENT), update_(NULL) { } + virtual void OnCacheSelected(int host_id, int64 cache_id, Status status) { } @@ -32,12 +34,33 @@ class MockFrontend : public AppCacheFrontend { virtual void OnEventRaised(const std::vector<int>& host_ids, EventID event_id) { raised_events_.push_back(RaisedEvent(host_ids, event_id)); + + // Trigger additional updates if requested. + if (event_id == start_update_trigger_ && update_) { + for (std::vector<AppCacheHost*>::iterator it = update_hosts_.begin(); + it != update_hosts_.end(); ++it) { + AppCacheHost* host = *it; + update_->StartUpdate(host, + (host ? host->pending_master_entry_url() : GURL::EmptyGURL())); + } + update_hosts_.clear(); // only trigger once + } } void AddExpectedEvent(const std::vector<int>& host_ids, EventID event_id) { expected_events_.push_back(RaisedEvent(host_ids, event_id)); } + void TriggerAdditionalUpdates(EventID trigger_event, + AppCacheUpdateJob* update) { + start_update_trigger_ = trigger_event; + update_ = update; + } + + void AdditionalUpdateHost(AppCacheHost* host) { + update_hosts_.push_back(host); + } + typedef std::vector<int> HostIds; typedef std::pair<HostIds, EventID> RaisedEvent; typedef std::vector<RaisedEvent> RaisedEvents; @@ -45,6 +68,11 @@ class MockFrontend : public AppCacheFrontend { // Set the expected events if verification needs to happen asynchronously. RaisedEvents expected_events_; + + // Add ability for frontend to add master entries to an inprogress update. + EventID start_update_trigger_; + AppCacheUpdateJob* update_; + std::vector<AppCacheHost*> update_hosts_; }; // Helper class to let us call methods of AppCacheUpdateJobTest on a @@ -1265,6 +1293,603 @@ class AppCacheUpdateJobTest : public testing::Test, WaitForUpdateToFinish(); } + void MasterEntryFetchManifestFailTest() { + ASSERT_EQ(MessageLoop::TYPE_IO, MessageLoop::current()->type()); + + MakeService(); + group_ = new AppCacheGroup(service_.get(), GURL("http://failme"), 111); + AppCacheUpdateJob* update = new AppCacheUpdateJob(service_.get(), group_); + group_->update_job_ = update; + + MockFrontend* frontend = MakeMockFrontend(); + AppCacheHost* host = MakeHost(1, frontend); + host->new_master_entry_url_ = GURL("http://failme/blah"); + update->StartUpdate(host, host->new_master_entry_url_); + 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; + MockFrontend::HostIds ids1(1, host->host_id()); + frontend->AddExpectedEvent(ids1, CHECKING_EVENT); + frontend->AddExpectedEvent(ids1, ERROR_EVENT); + + WaitForUpdateToFinish(); + } + + void MasterEntryBadManifestTest() { + ASSERT_EQ(MessageLoop::TYPE_IO, MessageLoop::current()->type()); + + MakeService(); + group_ = new AppCacheGroup(service_.get(), + http_server_->TestServerPage("files/bad-manifest"), 111); + AppCacheUpdateJob* update = new AppCacheUpdateJob(service_.get(), group_); + group_->update_job_ = update; + + MockFrontend* frontend = MakeMockFrontend(); + AppCacheHost* host = MakeHost(1, frontend); + host->new_master_entry_url_ = http_server_->TestServerPage("files/blah"); + update->StartUpdate(host, host->new_master_entry_url_); + 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; + MockFrontend::HostIds ids1(1, host->host_id()); + frontend->AddExpectedEvent(ids1, CHECKING_EVENT); + frontend->AddExpectedEvent(ids1, ERROR_EVENT); + + WaitForUpdateToFinish(); + } + + void MasterEntryManifestNotFoundTest() { + ASSERT_EQ(MessageLoop::TYPE_IO, MessageLoop::current()->type()); + + MakeService(); + group_ = new AppCacheGroup( + service_.get(), http_server_->TestServerPage("files/nosuchfile"), 111); + AppCacheUpdateJob* update = new AppCacheUpdateJob(service_.get(), group_); + group_->update_job_ = update; + + MockFrontend* frontend = MakeMockFrontend(); + AppCacheHost* host = MakeHost(1, frontend); + host->new_master_entry_url_ = http_server_->TestServerPage("files/blah"); + + update->StartUpdate(host, host->new_master_entry_url_); + 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; + MockFrontend::HostIds ids1(1, host->host_id()); + frontend->AddExpectedEvent(ids1, CHECKING_EVENT); + frontend->AddExpectedEvent(ids1, ERROR_EVENT); + + WaitForUpdateToFinish(); + } + + void MasterEntryFailUrlFetchTest() { + ASSERT_EQ(MessageLoop::TYPE_IO, MessageLoop::current()->type()); + + MakeService(); + group_ = new AppCacheGroup(service_.get(), + http_server_->TestServerPage("files/manifest-fb-404"), 111); + AppCacheUpdateJob* update = new AppCacheUpdateJob(service_.get(), group_); + group_->update_job_ = update; + + MockFrontend* frontend = MakeMockFrontend(); + AppCacheHost* host = MakeHost(1, frontend); + host->new_master_entry_url_ = + http_server_->TestServerPage("files/explicit1"); + + update->StartUpdate(host, host->new_master_entry_url_); + 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 fallback url is cache failure + MockFrontend::HostIds ids1(1, host->host_id()); + frontend->AddExpectedEvent(ids1, CHECKING_EVENT); + frontend->AddExpectedEvent(ids1, DOWNLOADING_EVENT); + frontend->AddExpectedEvent(ids1, PROGRESS_EVENT); + frontend->AddExpectedEvent(ids1, PROGRESS_EVENT); + frontend->AddExpectedEvent(ids1, PROGRESS_EVENT); + frontend->AddExpectedEvent(ids1, ERROR_EVENT); + + WaitForUpdateToFinish(); + } + + void MasterEntryAllFailTest() { + ASSERT_EQ(MessageLoop::TYPE_IO, MessageLoop::current()->type()); + + MakeService(); + group_ = new AppCacheGroup( + service_.get(), http_server_->TestServerPage("files/manifest1"), 111); + AppCacheUpdateJob* update = new AppCacheUpdateJob(service_.get(), group_); + group_->update_job_ = update; + + MockFrontend* frontend1 = MakeMockFrontend(); + AppCacheHost* host1 = MakeHost(1, frontend1); + host1->new_master_entry_url_ = + http_server_->TestServerPage("files/nosuchfile"); + update->StartUpdate(host1, host1->new_master_entry_url_); + + MockFrontend* frontend2 = MakeMockFrontend(); + AppCacheHost* host2 = MakeHost(2, frontend2); + host2->new_master_entry_url_ = + http_server_->TestServerPage("files/servererror"); + update->StartUpdate(host2, host2->new_master_entry_url_); + + // Set up checks for when update job finishes. + do_checks_after_update_finished_ = true; + expect_group_obsolete_ = false; + expect_group_has_cache_ = false; // all pending masters failed + 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 UpgradeMasterEntryAllFailTest() { + ASSERT_EQ(MessageLoop::TYPE_IO, MessageLoop::current()->type()); + + MakeService(); + group_ = new AppCacheGroup( + service_.get(), http_server_->TestServerPage("files/manifest1"), 111); + AppCacheUpdateJob* update = new AppCacheUpdateJob(service_.get(), group_); + group_->update_job_ = update; + + AppCache* cache = MakeCacheForGroup(service_->storage()->NewCacheId(), 42); + MockFrontend* frontend1 = MakeMockFrontend(); + AppCacheHost* host1 = MakeHost(1, frontend1); + host1->AssociateCache(cache); + + MockFrontend* frontend2 = MakeMockFrontend(); + AppCacheHost* host2 = MakeHost(2, frontend2); + host2->new_master_entry_url_ = + http_server_->TestServerPage("files/nosuchfile"); + update->StartUpdate(host2, host2->new_master_entry_url_); + + MockFrontend* frontend3 = MakeMockFrontend(); + AppCacheHost* host3 = MakeHost(3, frontend3); + host3->new_master_entry_url_ = + http_server_->TestServerPage("files/servererror"); + update->StartUpdate(host3, host3->new_master_entry_url_); + + // 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, DOWNLOADING_EVENT); + frontend2->AddExpectedEvent(ids2, PROGRESS_EVENT); + frontend2->AddExpectedEvent(ids2, PROGRESS_EVENT); + frontend2->AddExpectedEvent(ids2, ERROR_EVENT); + MockFrontend::HostIds ids3(1, host3->host_id()); + frontend3->AddExpectedEvent(ids3, CHECKING_EVENT); + frontend3->AddExpectedEvent(ids3, DOWNLOADING_EVENT); + frontend3->AddExpectedEvent(ids3, PROGRESS_EVENT); + frontend3->AddExpectedEvent(ids3, PROGRESS_EVENT); + frontend3->AddExpectedEvent(ids3, ERROR_EVENT); + + WaitForUpdateToFinish(); + } + + void MasterEntrySomeFailTest() { + ASSERT_EQ(MessageLoop::TYPE_IO, MessageLoop::current()->type()); + + MakeService(); + group_ = new AppCacheGroup( + service_.get(), http_server_->TestServerPage("files/manifest1"), 111); + AppCacheUpdateJob* update = new AppCacheUpdateJob(service_.get(), group_); + group_->update_job_ = update; + + MockFrontend* frontend1 = MakeMockFrontend(); + AppCacheHost* host1 = MakeHost(1, frontend1); + host1->new_master_entry_url_ = + http_server_->TestServerPage("files/nosuchfile"); + update->StartUpdate(host1, host1->new_master_entry_url_); + + MockFrontend* frontend2 = MakeMockFrontend(); + AppCacheHost* host2 = MakeHost(2, frontend2); + host2->new_master_entry_url_ = + http_server_->TestServerPage("files/explicit2"); + update->StartUpdate(host2, host2->new_master_entry_url_); + + // Set up checks for when update job finishes. + do_checks_after_update_finished_ = true; + expect_group_obsolete_ = false; + expect_group_has_cache_ = true; // as long as one pending master succeeds + tested_manifest_ = MANIFEST1; + expect_extra_entries_.insert(AppCache::EntryMap::value_type( + http_server_->TestServerPage("files/explicit2"), + AppCacheEntry(AppCacheEntry::MASTER))); + 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, CACHED_EVENT); + + WaitForUpdateToFinish(); + } + + void UpgradeMasterEntrySomeFailTest() { + ASSERT_EQ(MessageLoop::TYPE_IO, MessageLoop::current()->type()); + + MakeService(); + group_ = new AppCacheGroup( + service_.get(), http_server_->TestServerPage("files/manifest1"), 111); + AppCacheUpdateJob* update = new AppCacheUpdateJob(service_.get(), group_); + group_->update_job_ = update; + + AppCache* cache = MakeCacheForGroup(service_->storage()->NewCacheId(), 42); + MockFrontend* frontend1 = MakeMockFrontend(); + AppCacheHost* host1 = MakeHost(1, frontend1); + host1->AssociateCache(cache); + + MockFrontend* frontend2 = MakeMockFrontend(); + AppCacheHost* host2 = MakeHost(2, frontend2); + host2->new_master_entry_url_ = + http_server_->TestServerPage("files/nosuchfile"); + update->StartUpdate(host2, host2->new_master_entry_url_); + + MockFrontend* frontend3 = MakeMockFrontend(); + AppCacheHost* host3 = MakeHost(3, frontend3); + host3->new_master_entry_url_ = + http_server_->TestServerPage("files/explicit2"); + update->StartUpdate(host3, host3->new_master_entry_url_); + + // 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))); + 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, DOWNLOADING_EVENT); + frontend2->AddExpectedEvent(ids2, PROGRESS_EVENT); + frontend2->AddExpectedEvent(ids2, PROGRESS_EVENT); + frontend2->AddExpectedEvent(ids2, ERROR_EVENT); + MockFrontend::HostIds ids3(1, host3->host_id()); + frontend3->AddExpectedEvent(ids3, CHECKING_EVENT); + frontend3->AddExpectedEvent(ids3, DOWNLOADING_EVENT); + frontend3->AddExpectedEvent(ids3, PROGRESS_EVENT); + frontend3->AddExpectedEvent(ids3, PROGRESS_EVENT); + frontend3->AddExpectedEvent(ids3, UPDATE_READY_EVENT); + + WaitForUpdateToFinish(); + } + + void MasterEntryNoUpdateTest() { + ASSERT_EQ(MessageLoop::TYPE_IO, MessageLoop::current()->type()); + + MakeService(); + group_ = new AppCacheGroup(service_.get(), + http_server_->TestServerPage("files/notmodified"), 111); + AppCacheUpdateJob* update = new AppCacheUpdateJob(service_.get(), group_); + group_->update_job_ = update; + + AppCache* cache = MakeCacheForGroup(1, 111); + MockFrontend* frontend1 = MakeMockFrontend(); + AppCacheHost* host1 = MakeHost(1, frontend1); + host1->AssociateCache(cache); + + // Give cache an existing entry that can also be fetched. + cache->AddEntry(http_server_->TestServerPage("files/explicit2"), + AppCacheEntry(AppCacheEntry::EXPLICIT, 222)); + + MockFrontend* frontend2 = MakeMockFrontend(); + AppCacheHost* host2 = MakeHost(2, frontend2); + host2->new_master_entry_url_ = + http_server_->TestServerPage("files/explicit1"); + update->StartUpdate(host2, host2->new_master_entry_url_); + + AppCacheHost* host3 = MakeHost(3, frontend2); // same frontend as host2 + host3->new_master_entry_url_ = + http_server_->TestServerPage("files/explicit2"); + update->StartUpdate(host3, host3->new_master_entry_url_); + + // 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 still the same cache + tested_manifest_ = PENDING_MASTER_NO_UPDATE; + MockFrontend::HostIds ids1(1, host1->host_id()); + frontend1->AddExpectedEvent(ids1, CHECKING_EVENT); + frontend1->AddExpectedEvent(ids1, NO_UPDATE_EVENT); + MockFrontend::HostIds ids3(1, host3->host_id()); + frontend2->AddExpectedEvent(ids3, CHECKING_EVENT); + MockFrontend::HostIds ids2and3; + ids2and3.push_back(host2->host_id()); + ids2and3.push_back(host3->host_id()); + frontend2->AddExpectedEvent(ids2and3, NO_UPDATE_EVENT); + + WaitForUpdateToFinish(); + } + + void StartUpdateMidCacheAttemptTest() { + ASSERT_EQ(MessageLoop::TYPE_IO, MessageLoop::current()->type()); + + MakeService(); + group_ = new AppCacheGroup( + service_.get(), http_server_->TestServerPage("files/manifest1"), + service_->storage()->NewGroupId()); + AppCacheUpdateJob* update = new AppCacheUpdateJob(service_.get(), group_); + group_->update_job_ = update; + + MockFrontend* frontend1 = MakeMockFrontend(); + AppCacheHost* host1 = MakeHost(1, frontend1); + host1->new_master_entry_url_ = + http_server_->TestServerPage("files/explicit2"); + update->StartUpdate(host1, host1->new_master_entry_url_); + EXPECT_TRUE(update->manifest_url_request_ != NULL); + + // Set up additional updates to be started while update is in progress. + MockFrontend* frontend2 = MakeMockFrontend(); + AppCacheHost* host2 = MakeHost(2, frontend2); + host2->new_master_entry_url_ = + http_server_->TestServerPage("files/nosuchfile"); + + MockFrontend* frontend3 = MakeMockFrontend(); + AppCacheHost* host3 = MakeHost(3, frontend3); + host3->new_master_entry_url_ = + http_server_->TestServerPage("files/explicit1"); + + MockFrontend* frontend4 = MakeMockFrontend(); + AppCacheHost* host4 = MakeHost(4, frontend4); + host4->new_master_entry_url_ = + http_server_->TestServerPage("files/explicit2"); + + MockFrontend* frontend5 = MakeMockFrontend(); + AppCacheHost* host5 = MakeHost(5, frontend5); // no master entry url + + frontend1->TriggerAdditionalUpdates(DOWNLOADING_EVENT, update); + frontend1->AdditionalUpdateHost(host2); // fetch will fail + frontend1->AdditionalUpdateHost(host3); // same as an explicit entry + frontend1->AdditionalUpdateHost(host4); // same as another master entry + frontend1->AdditionalUpdateHost(NULL); // no host + frontend1->AdditionalUpdateHost(host5); // no master entry url + + // 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; + expect_extra_entries_.insert(AppCache::EntryMap::value_type( + http_server_->TestServerPage("files/explicit2"), + AppCacheEntry(AppCacheEntry::MASTER))); + 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, CACHED_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); + MockFrontend::HostIds ids3(1, host3->host_id()); + frontend3->AddExpectedEvent(ids3, CHECKING_EVENT); + frontend3->AddExpectedEvent(ids3, DOWNLOADING_EVENT); + frontend3->AddExpectedEvent(ids3, PROGRESS_EVENT); + frontend3->AddExpectedEvent(ids3, PROGRESS_EVENT); + frontend3->AddExpectedEvent(ids3, CACHED_EVENT); + MockFrontend::HostIds ids4(1, host4->host_id()); + frontend4->AddExpectedEvent(ids4, CHECKING_EVENT); + frontend4->AddExpectedEvent(ids4, DOWNLOADING_EVENT); + frontend4->AddExpectedEvent(ids4, PROGRESS_EVENT); + frontend4->AddExpectedEvent(ids4, PROGRESS_EVENT); + frontend4->AddExpectedEvent(ids4, CACHED_EVENT); + + // Host 5 is not associated with cache so no progress/cached events. + MockFrontend::HostIds ids5(1, host5->host_id()); + frontend5->AddExpectedEvent(ids5, CHECKING_EVENT); + frontend5->AddExpectedEvent(ids5, DOWNLOADING_EVENT); + + WaitForUpdateToFinish(); + } + + void StartUpdateMidNoUpdateTest() { + ASSERT_EQ(MessageLoop::TYPE_IO, MessageLoop::current()->type()); + + MakeService(); + group_ = new AppCacheGroup( + service_.get(), http_server_->TestServerPage("files/notmodified"), + service_->storage()->NewGroupId()); + AppCacheUpdateJob* update = new AppCacheUpdateJob(service_.get(), group_); + group_->update_job_ = update; + + AppCache* cache = MakeCacheForGroup(1, 111); + MockFrontend* frontend1 = MakeMockFrontend(); + AppCacheHost* host1 = MakeHost(1, frontend1); + host1->AssociateCache(cache); + + // Give cache an existing entry. + cache->AddEntry(http_server_->TestServerPage("files/explicit2"), + AppCacheEntry(AppCacheEntry::EXPLICIT, 222)); + + // Start update with a pending master entry that will fail to give us an + // event to trigger other updates. + MockFrontend* frontend2 = MakeMockFrontend(); + AppCacheHost* host2 = MakeHost(2, frontend2); + host2->new_master_entry_url_ = + http_server_->TestServerPage("files/nosuchfile"); + update->StartUpdate(host2, host2->new_master_entry_url_); + EXPECT_TRUE(update->manifest_url_request_ != NULL); + + // Set up additional updates to be started while update is in progress. + MockFrontend* frontend3 = MakeMockFrontend(); + AppCacheHost* host3 = MakeHost(3, frontend3); + host3->new_master_entry_url_ = + http_server_->TestServerPage("files/explicit1"); + + MockFrontend* frontend4 = MakeMockFrontend(); + AppCacheHost* host4 = MakeHost(4, frontend4); // no master entry url + + MockFrontend* frontend5 = MakeMockFrontend(); + AppCacheHost* host5 = MakeHost(5, frontend5); + host5->new_master_entry_url_ = + http_server_->TestServerPage("files/explicit2"); // existing entry + + MockFrontend* frontend6 = MakeMockFrontend(); + AppCacheHost* host6 = MakeHost(6, frontend6); + host6->new_master_entry_url_ = + http_server_->TestServerPage("files/explicit1"); + + frontend2->TriggerAdditionalUpdates(ERROR_EVENT, update); + frontend2->AdditionalUpdateHost(host3); + frontend2->AdditionalUpdateHost(NULL); // no host + frontend2->AdditionalUpdateHost(host4); // no master entry url + frontend2->AdditionalUpdateHost(host5); // same as existing cache entry + frontend2->AdditionalUpdateHost(host6); // same as another master entry + + // 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 + tested_manifest_ = PENDING_MASTER_NO_UPDATE; + MockFrontend::HostIds ids1(1, host1->host_id()); // prior associated host + frontend1->AddExpectedEvent(ids1, CHECKING_EVENT); + frontend1->AddExpectedEvent(ids1, NO_UPDATE_EVENT); + MockFrontend::HostIds ids2(1, host2->host_id()); + frontend2->AddExpectedEvent(ids2, ERROR_EVENT); + MockFrontend::HostIds ids3(1, host3->host_id()); + frontend3->AddExpectedEvent(ids3, CHECKING_EVENT); + frontend3->AddExpectedEvent(ids3, NO_UPDATE_EVENT); + MockFrontend::HostIds ids4(1, host4->host_id()); // unassociated w/cache + frontend4->AddExpectedEvent(ids4, CHECKING_EVENT); + MockFrontend::HostIds ids5(1, host5->host_id()); + frontend5->AddExpectedEvent(ids5, CHECKING_EVENT); + frontend5->AddExpectedEvent(ids5, NO_UPDATE_EVENT); + MockFrontend::HostIds ids6(1, host6->host_id()); + frontend6->AddExpectedEvent(ids6, CHECKING_EVENT); + frontend6->AddExpectedEvent(ids6, NO_UPDATE_EVENT); + + WaitForUpdateToFinish(); + } + + void StartUpdateMidDownloadTest() { + ASSERT_EQ(MessageLoop::TYPE_IO, MessageLoop::current()->type()); + + MakeService(); + group_ = new AppCacheGroup( + service_.get(), http_server_->TestServerPage("files/manifest1"), 111); + AppCacheUpdateJob* update = new AppCacheUpdateJob(service_.get(), group_); + group_->update_job_ = update; + + AppCache* cache = MakeCacheForGroup(service_->storage()->NewCacheId(), 42); + MockFrontend* frontend1 = MakeMockFrontend(); + AppCacheHost* host1 = MakeHost(1, frontend1); + host1->AssociateCache(cache); + + update->StartUpdate(NULL, GURL::EmptyGURL()); + + // Set up additional updates to be started while update is in progress. + MockFrontend* frontend2 = MakeMockFrontend(); + AppCacheHost* host2 = MakeHost(2, frontend2); + host2->new_master_entry_url_ = + http_server_->TestServerPage("files/explicit1"); + + MockFrontend* frontend3 = MakeMockFrontend(); + AppCacheHost* host3 = MakeHost(3, frontend3); + host3->new_master_entry_url_ = + http_server_->TestServerPage("files/explicit2"); + + MockFrontend* frontend4 = MakeMockFrontend(); + AppCacheHost* host4 = MakeHost(4, frontend4); // no master entry url + + MockFrontend* frontend5 = MakeMockFrontend(); + AppCacheHost* host5 = MakeHost(5, frontend5); + host5->new_master_entry_url_ = + http_server_->TestServerPage("files/explicit2"); + + frontend1->TriggerAdditionalUpdates(PROGRESS_EVENT, update); + frontend1->AdditionalUpdateHost(host2); // same as entry in manifest + frontend1->AdditionalUpdateHost(NULL); // no host + frontend1->AdditionalUpdateHost(host3); // new master entry + frontend1->AdditionalUpdateHost(host4); // no master entry url + frontend1->AdditionalUpdateHost(host5); // same as another master entry + + // 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; + expect_extra_entries_.insert(AppCache::EntryMap::value_type( + http_server_->TestServerPage("files/explicit2"), + AppCacheEntry(AppCacheEntry::MASTER))); + MockFrontend::HostIds ids1(1, host1->host_id()); // prior associated host + 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, UPDATE_READY_EVENT); + MockFrontend::HostIds ids3(1, host3->host_id()); + frontend3->AddExpectedEvent(ids3, CHECKING_EVENT); + frontend3->AddExpectedEvent(ids3, DOWNLOADING_EVENT); + frontend3->AddExpectedEvent(ids3, PROGRESS_EVENT); + frontend3->AddExpectedEvent(ids3, UPDATE_READY_EVENT); + MockFrontend::HostIds ids4(1, host4->host_id()); // unassociated w/cache + frontend4->AddExpectedEvent(ids4, CHECKING_EVENT); + frontend4->AddExpectedEvent(ids4, DOWNLOADING_EVENT); + MockFrontend::HostIds ids5(1, host5->host_id()); + frontend5->AddExpectedEvent(ids5, CHECKING_EVENT); + frontend5->AddExpectedEvent(ids5, DOWNLOADING_EVENT); + frontend5->AddExpectedEvent(ids5, PROGRESS_EVENT); + frontend5->AddExpectedEvent(ids5, UPDATE_READY_EVENT); + + WaitForUpdateToFinish(); + } + void WaitForUpdateToFinish() { if (group_->update_status() == AppCacheGroup::IDLE) UpdateFinished(); @@ -1404,30 +2029,36 @@ class AppCacheUpdateJobTest : public testing::Test, // Verify expected cache contents last as some checks are asserts // and will abort the test if they fail. - 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 EMPTY_FILE_MANIFEST: - VerifyEmptyFileManifest(group_->newest_complete_cache()); - break; - case NONE: - default: - break; + if (tested_manifest_) { + AppCache* cache = group_->newest_complete_cache(); + ASSERT_TRUE(cache != NULL); + EXPECT_EQ(group_, cache->owning_group()); + EXPECT_TRUE(cache->is_complete()); + + switch (tested_manifest_) { + case MANIFEST1: + VerifyManifest1(cache); + break; + case MANIFEST_MERGED_TYPES: + VerifyManifestMergedTypes(cache); + break; + case EMPTY_MANIFEST: + VerifyEmptyManifest(cache); + break; + case EMPTY_FILE_MANIFEST: + VerifyEmptyFileManifest(cache); + break; + case PENDING_MASTER_NO_UPDATE: + VerifyMasterEntryNoUpdate(cache); + break; + case NONE: + default: + break; + } } } 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 = @@ -1436,7 +2067,7 @@ class AppCacheUpdateJobTest : public testing::Test, EXPECT_EQ(AppCacheEntry::MANIFEST, entry->types()); entry = cache->GetEntry(http_server_->TestServerPage("files/explicit1")); ASSERT_TRUE(entry); - EXPECT_EQ(AppCacheEntry::EXPLICIT, entry->types()); + EXPECT_TRUE(entry->IsExplicit()); entry = cache->GetEntry( http_server_->TestServerPage("files/fallback1a")); ASSERT_TRUE(entry); @@ -1466,10 +2097,6 @@ class AppCacheUpdateJobTest : public testing::Test, } 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( @@ -1502,10 +2129,6 @@ class AppCacheUpdateJobTest : public testing::Test, } 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( @@ -1521,10 +2144,6 @@ class AppCacheUpdateJobTest : public testing::Test, } void VerifyEmptyFileManifest(AppCache* cache) { - ASSERT_TRUE(cache != NULL); - EXPECT_EQ(group_, cache->owning_group()); - EXPECT_TRUE(cache->is_complete()); - EXPECT_EQ(size_t(2), cache->entries().size()); AppCacheEntry* entry = cache->GetEntry( http_server_->TestServerPage("files/empty-file-manifest")); @@ -1544,6 +2163,32 @@ class AppCacheUpdateJobTest : public testing::Test, EXPECT_TRUE(cache->update_time_ > base::TimeTicks()); } + void VerifyMasterEntryNoUpdate(AppCache* cache) { + EXPECT_EQ(size_t(3), cache->entries().size()); + AppCacheEntry* entry = cache->GetEntry( + http_server_->TestServerPage("files/notmodified")); + ASSERT_TRUE(entry); + EXPECT_EQ(AppCacheEntry::MANIFEST, entry->types()); + + entry = cache->GetEntry( + http_server_->TestServerPage("files/explicit1")); + ASSERT_TRUE(entry); + EXPECT_EQ(AppCacheEntry::MASTER, entry->types()); + EXPECT_TRUE(entry->has_response_id()); + + entry = cache->GetEntry( + http_server_->TestServerPage("files/explicit2")); + ASSERT_TRUE(entry); + EXPECT_EQ(AppCacheEntry::EXPLICIT | AppCacheEntry::MASTER, entry->types()); + EXPECT_TRUE(entry->has_response_id()); + + 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 { @@ -1552,6 +2197,7 @@ class AppCacheUpdateJobTest : public testing::Test, MANIFEST_MERGED_TYPES, EMPTY_MANIFEST, EMPTY_FILE_MANIFEST, + PENDING_MASTER_NO_UPDATE, }; static scoped_ptr<base::Thread> io_thread_; @@ -1764,4 +2410,52 @@ TEST_F(AppCacheUpdateJobTest, UpgradeFailMakeGroupObsolete) { RunTestOnIOThread(&AppCacheUpdateJobTest::UpgradeFailMakeGroupObsoleteTest); } +TEST_F(AppCacheUpdateJobTest, MasterEntryFetchManifestFail) { + RunTestOnIOThread(&AppCacheUpdateJobTest::MasterEntryFetchManifestFailTest); +} + +TEST_F(AppCacheUpdateJobTest, MasterEntryBadManifest) { + RunTestOnIOThread(&AppCacheUpdateJobTest::MasterEntryBadManifestTest); +} + +TEST_F(AppCacheUpdateJobTest, MasterEntryManifestNotFound) { + RunTestOnIOThread(&AppCacheUpdateJobTest::MasterEntryManifestNotFoundTest); +} + +TEST_F(AppCacheUpdateJobTest, MasterEntryFailUrlFetch) { + RunTestOnIOThread(&AppCacheUpdateJobTest::MasterEntryFailUrlFetchTest); +} + +TEST_F(AppCacheUpdateJobTest, MasterEntryAllFail) { + RunTestOnIOThread(&AppCacheUpdateJobTest::MasterEntryAllFailTest); +} + +TEST_F(AppCacheUpdateJobTest, UpgradeMasterEntryAllFail) { + RunTestOnIOThread(&AppCacheUpdateJobTest::UpgradeMasterEntryAllFailTest); +} + +TEST_F(AppCacheUpdateJobTest, MasterEntrySomeFail) { + RunTestOnIOThread(&AppCacheUpdateJobTest::MasterEntrySomeFailTest); +} + +TEST_F(AppCacheUpdateJobTest, UpgradeMasterEntrySomeFail) { + RunTestOnIOThread(&AppCacheUpdateJobTest::UpgradeMasterEntrySomeFailTest); +} + +TEST_F(AppCacheUpdateJobTest, MasterEntryNoUpdate) { + RunTestOnIOThread(&AppCacheUpdateJobTest::MasterEntryNoUpdateTest); +} + +TEST_F(AppCacheUpdateJobTest, StartUpdateMidCacheAttempt) { + RunTestOnIOThread(&AppCacheUpdateJobTest::StartUpdateMidCacheAttemptTest); +} + +TEST_F(AppCacheUpdateJobTest, StartUpdateMidNoUpdate) { + RunTestOnIOThread(&AppCacheUpdateJobTest::StartUpdateMidNoUpdateTest); +} + +TEST_F(AppCacheUpdateJobTest, StartUpdateMidDownload) { + RunTestOnIOThread(&AppCacheUpdateJobTest::StartUpdateMidDownloadTest); +} + } // namespace appcache diff --git a/webkit/appcache/data/appcache_unittest/bad-manifest b/webkit/appcache/data/appcache_unittest/bad-manifest new file mode 100644 index 0000000..6a7d6e2 --- /dev/null +++ b/webkit/appcache/data/appcache_unittest/bad-manifest @@ -0,0 +1,2 @@ +BAD CACHE MANIFEST + diff --git a/webkit/appcache/data/appcache_unittest/bad-manifest.mock-http-headers b/webkit/appcache/data/appcache_unittest/bad-manifest.mock-http-headers new file mode 100644 index 0000000..6e904b9 --- /dev/null +++ b/webkit/appcache/data/appcache_unittest/bad-manifest.mock-http-headers @@ -0,0 +1,2 @@ +HTTP/1.1 200 OK +Content-type: text/cache-manifest |