diff options
author | jennb@chromium.org <jennb@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-12-01 00:54:57 +0000 |
---|---|---|
committer | jennb@chromium.org <jennb@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-12-01 00:54:57 +0000 |
commit | a17b674580be4397cf56e2c955fac9f2c4a3bfaa (patch) | |
tree | 09e01865679b16cbaa27d55de7ee02930d304ec2 /webkit/appcache | |
parent | b2fb9ff65d58beffec6a59853e1c3cfa5ecdb173 (diff) | |
download | chromium_src-a17b674580be4397cf56e2c955fac9f2c4a3bfaa.zip chromium_src-a17b674580be4397cf56e2c955fac9f2c4a3bfaa.tar.gz chromium_src-a17b674580be4397cf56e2c955fac9f2c4a3bfaa.tar.bz2 |
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 long-term 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.
Long-term 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/402098
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@33394 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'webkit/appcache')
-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..74b071d 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() : 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 |