diff options
Diffstat (limited to 'webkit/browser/appcache/appcache_service_impl.cc')
-rw-r--r-- | webkit/browser/appcache/appcache_service_impl.cc | 578 |
1 files changed, 578 insertions, 0 deletions
diff --git a/webkit/browser/appcache/appcache_service_impl.cc b/webkit/browser/appcache/appcache_service_impl.cc new file mode 100644 index 0000000..3091a5c --- /dev/null +++ b/webkit/browser/appcache/appcache_service_impl.cc @@ -0,0 +1,578 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/appcache/appcache_service_impl.h" + +#include <functional> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "base/stl_util.h" +#include "net/base/completion_callback.h" +#include "net/base/io_buffer.h" +#include "webkit/browser/appcache/appcache.h" +#include "webkit/browser/appcache/appcache_backend_impl.h" +#include "webkit/browser/appcache/appcache_entry.h" +#include "webkit/browser/appcache/appcache_executable_handler.h" +#include "webkit/browser/appcache/appcache_histograms.h" +#include "webkit/browser/appcache/appcache_policy.h" +#include "webkit/browser/appcache/appcache_quota_client.h" +#include "webkit/browser/appcache/appcache_response.h" +#include "webkit/browser/appcache/appcache_service_impl.h" +#include "webkit/browser/appcache/appcache_storage_impl.h" +#include "webkit/browser/quota/special_storage_policy.h" + +namespace appcache { + +namespace { + +void DeferredCallback(const net::CompletionCallback& callback, int rv) { + callback.Run(rv); +} + +} // namespace + +AppCacheInfoCollection::AppCacheInfoCollection() {} + +AppCacheInfoCollection::~AppCacheInfoCollection() {} + +// AsyncHelper ------- + +class AppCacheServiceImpl::AsyncHelper + : public AppCacheStorage::Delegate { + public: + AsyncHelper(AppCacheServiceImpl* service, + const net::CompletionCallback& callback) + : service_(service), callback_(callback) { + service_->pending_helpers_.insert(this); + } + + virtual ~AsyncHelper() { + if (service_) + service_->pending_helpers_.erase(this); + } + + virtual void Start() = 0; + virtual void Cancel(); + + protected: + void CallCallback(int rv) { + if (!callback_.is_null()) { + // Defer to guarantee async completion. + base::MessageLoop::current()->PostTask( + FROM_HERE, base::Bind(&DeferredCallback, callback_, rv)); + } + callback_.Reset(); + } + + AppCacheServiceImpl* service_; + net::CompletionCallback callback_; +}; + +void AppCacheServiceImpl::AsyncHelper::Cancel() { + if (!callback_.is_null()) { + callback_.Run(net::ERR_ABORTED); + callback_.Reset(); + } + service_->storage()->CancelDelegateCallbacks(this); + service_ = NULL; +} + +// CanHandleOfflineHelper ------- + +class AppCacheServiceImpl::CanHandleOfflineHelper : AsyncHelper { + public: + CanHandleOfflineHelper( + AppCacheServiceImpl* service, const GURL& url, + const GURL& first_party, const net::CompletionCallback& callback) + : AsyncHelper(service, callback), + url_(url), + first_party_(first_party) { + } + + virtual void Start() OVERRIDE { + AppCachePolicy* policy = service_->appcache_policy(); + if (policy && !policy->CanLoadAppCache(url_, first_party_)) { + CallCallback(net::ERR_FAILED); + delete this; + return; + } + + service_->storage()->FindResponseForMainRequest(url_, GURL(), this); + } + + private: + // AppCacheStorage::Delegate implementation. + virtual void OnMainResponseFound( + const GURL& url, const AppCacheEntry& entry, + const GURL& fallback_url, const AppCacheEntry& fallback_entry, + int64 cache_id, int64 group_id, const GURL& mainfest_url) OVERRIDE; + + GURL url_; + GURL first_party_; + + DISALLOW_COPY_AND_ASSIGN(CanHandleOfflineHelper); +}; + +void AppCacheServiceImpl::CanHandleOfflineHelper::OnMainResponseFound( + const GURL& url, const AppCacheEntry& entry, + const GURL& fallback_url, const AppCacheEntry& fallback_entry, + int64 cache_id, int64 group_id, const GURL& manifest_url) { + bool can = (entry.has_response_id() || fallback_entry.has_response_id()); + CallCallback(can ? net::OK : net::ERR_FAILED); + delete this; +} + +// DeleteHelper ------- + +class AppCacheServiceImpl::DeleteHelper : public AsyncHelper { + public: + DeleteHelper( + AppCacheServiceImpl* service, const GURL& manifest_url, + const net::CompletionCallback& callback) + : AsyncHelper(service, callback), manifest_url_(manifest_url) { + } + + virtual void Start() OVERRIDE { + service_->storage()->LoadOrCreateGroup(manifest_url_, this); + } + + private: + // AppCacheStorage::Delegate implementation. + virtual void OnGroupLoaded( + appcache::AppCacheGroup* group, const GURL& manifest_url) OVERRIDE; + virtual void OnGroupMadeObsolete(appcache::AppCacheGroup* group, + bool success, + int response_code) OVERRIDE; + + GURL manifest_url_; + DISALLOW_COPY_AND_ASSIGN(DeleteHelper); +}; + +void AppCacheServiceImpl::DeleteHelper::OnGroupLoaded( + appcache::AppCacheGroup* group, const GURL& manifest_url) { + if (group) { + group->set_being_deleted(true); + group->CancelUpdate(); + service_->storage()->MakeGroupObsolete(group, this, 0); + } else { + CallCallback(net::ERR_FAILED); + delete this; + } +} + +void AppCacheServiceImpl::DeleteHelper::OnGroupMadeObsolete( + appcache::AppCacheGroup* group, + bool success, + int response_code) { + CallCallback(success ? net::OK : net::ERR_FAILED); + delete this; +} + +// DeleteOriginHelper ------- + +class AppCacheServiceImpl::DeleteOriginHelper : public AsyncHelper { + public: + DeleteOriginHelper( + AppCacheServiceImpl* service, const GURL& origin, + const net::CompletionCallback& callback) + : AsyncHelper(service, callback), origin_(origin), + num_caches_to_delete_(0), successes_(0), failures_(0) { + } + + virtual void Start() OVERRIDE { + // We start by listing all caches, continues in OnAllInfo(). + service_->storage()->GetAllInfo(this); + } + + private: + // AppCacheStorage::Delegate implementation. + virtual void OnAllInfo(AppCacheInfoCollection* collection) OVERRIDE; + virtual void OnGroupLoaded( + appcache::AppCacheGroup* group, const GURL& manifest_url) OVERRIDE; + virtual void OnGroupMadeObsolete(appcache::AppCacheGroup* group, + bool success, + int response_code) OVERRIDE; + + void CacheCompleted(bool success); + + GURL origin_; + int num_caches_to_delete_; + int successes_; + int failures_; + + DISALLOW_COPY_AND_ASSIGN(DeleteOriginHelper); +}; + +void AppCacheServiceImpl::DeleteOriginHelper::OnAllInfo( + AppCacheInfoCollection* collection) { + if (!collection) { + // Failed to get a listing. + CallCallback(net::ERR_FAILED); + delete this; + return; + } + + std::map<GURL, AppCacheInfoVector>::iterator found = + collection->infos_by_origin.find(origin_); + if (found == collection->infos_by_origin.end() || found->second.empty()) { + // No caches for this origin. + CallCallback(net::OK); + delete this; + return; + } + + // We have some caches to delete. + const AppCacheInfoVector& caches_to_delete = found->second; + successes_ = 0; + failures_ = 0; + num_caches_to_delete_ = static_cast<int>(caches_to_delete.size()); + for (AppCacheInfoVector::const_iterator iter = caches_to_delete.begin(); + iter != caches_to_delete.end(); ++iter) { + service_->storage()->LoadOrCreateGroup(iter->manifest_url, this); + } +} + +void AppCacheServiceImpl::DeleteOriginHelper::OnGroupLoaded( + appcache::AppCacheGroup* group, const GURL& manifest_url) { + if (group) { + group->set_being_deleted(true); + group->CancelUpdate(); + service_->storage()->MakeGroupObsolete(group, this, 0); + } else { + CacheCompleted(false); + } +} + +void AppCacheServiceImpl::DeleteOriginHelper::OnGroupMadeObsolete( + appcache::AppCacheGroup* group, + bool success, + int response_code) { + CacheCompleted(success); +} + +void AppCacheServiceImpl::DeleteOriginHelper::CacheCompleted(bool success) { + if (success) + ++successes_; + else + ++failures_; + if ((successes_ + failures_) < num_caches_to_delete_) + return; + + CallCallback(!failures_ ? net::OK : net::ERR_FAILED); + delete this; +} + + +// GetInfoHelper ------- + +class AppCacheServiceImpl::GetInfoHelper : AsyncHelper { + public: + GetInfoHelper( + AppCacheServiceImpl* service, AppCacheInfoCollection* collection, + const net::CompletionCallback& callback) + : AsyncHelper(service, callback), collection_(collection) { + } + + virtual void Start() OVERRIDE { + service_->storage()->GetAllInfo(this); + } + + private: + // AppCacheStorage::Delegate implementation. + virtual void OnAllInfo(AppCacheInfoCollection* collection) OVERRIDE; + + scoped_refptr<AppCacheInfoCollection> collection_; + + DISALLOW_COPY_AND_ASSIGN(GetInfoHelper); +}; + +void AppCacheServiceImpl::GetInfoHelper::OnAllInfo( + AppCacheInfoCollection* collection) { + if (collection) + collection->infos_by_origin.swap(collection_->infos_by_origin); + CallCallback(collection ? net::OK : net::ERR_FAILED); + delete this; +} + +// CheckResponseHelper ------- + +class AppCacheServiceImpl::CheckResponseHelper : AsyncHelper { + public: + CheckResponseHelper( + AppCacheServiceImpl* service, const GURL& manifest_url, int64 cache_id, + int64 response_id) + : AsyncHelper(service, net::CompletionCallback()), + manifest_url_(manifest_url), + cache_id_(cache_id), + response_id_(response_id), + kIOBufferSize(32 * 1024), + expected_total_size_(0), + amount_headers_read_(0), + amount_data_read_(0) { + } + + virtual void Start() OVERRIDE { + service_->storage()->LoadOrCreateGroup(manifest_url_, this); + } + + virtual void Cancel() OVERRIDE { + AppCacheHistograms::CountCheckResponseResult( + AppCacheHistograms::CHECK_CANCELED); + response_reader_.reset(); + AsyncHelper::Cancel(); + } + + private: + virtual void OnGroupLoaded(AppCacheGroup* group, + const GURL& manifest_url) OVERRIDE; + void OnReadInfoComplete(int result); + void OnReadDataComplete(int result); + + // Inputs describing what to check. + GURL manifest_url_; + int64 cache_id_; + int64 response_id_; + + // Internals used to perform the checks. + const int kIOBufferSize; + scoped_refptr<AppCache> cache_; + scoped_ptr<AppCacheResponseReader> response_reader_; + scoped_refptr<HttpResponseInfoIOBuffer> info_buffer_; + scoped_refptr<net::IOBuffer> data_buffer_; + int64 expected_total_size_; + int amount_headers_read_; + int amount_data_read_; + DISALLOW_COPY_AND_ASSIGN(CheckResponseHelper); +}; + +void AppCacheServiceImpl::CheckResponseHelper::OnGroupLoaded( + AppCacheGroup* group, const GURL& manifest_url) { + DCHECK_EQ(manifest_url_, manifest_url); + if (!group || !group->newest_complete_cache() || group->is_being_deleted() || + group->is_obsolete()) { + AppCacheHistograms::CountCheckResponseResult( + AppCacheHistograms::MANIFEST_OUT_OF_DATE); + delete this; + return; + } + + cache_ = group->newest_complete_cache(); + const AppCacheEntry* entry = cache_->GetEntryWithResponseId(response_id_); + if (!entry) { + if (cache_->cache_id() == cache_id_) { + AppCacheHistograms::CountCheckResponseResult( + AppCacheHistograms::ENTRY_NOT_FOUND); + service_->DeleteAppCacheGroup(manifest_url_, net::CompletionCallback()); + } else { + AppCacheHistograms::CountCheckResponseResult( + AppCacheHistograms::RESPONSE_OUT_OF_DATE); + } + delete this; + return; + } + + // Verify that we can read the response info and data. + expected_total_size_ = entry->response_size(); + response_reader_.reset(service_->storage()->CreateResponseReader( + manifest_url_, group->group_id(), response_id_)); + info_buffer_ = new HttpResponseInfoIOBuffer(); + response_reader_->ReadInfo( + info_buffer_.get(), + base::Bind(&CheckResponseHelper::OnReadInfoComplete, + base::Unretained(this))); +} + +void AppCacheServiceImpl::CheckResponseHelper::OnReadInfoComplete(int result) { + if (result < 0) { + AppCacheHistograms::CountCheckResponseResult( + AppCacheHistograms::READ_HEADERS_ERROR); + service_->DeleteAppCacheGroup(manifest_url_, net::CompletionCallback()); + delete this; + return; + } + amount_headers_read_ = result; + + // Start reading the data. + data_buffer_ = new net::IOBuffer(kIOBufferSize); + response_reader_->ReadData( + data_buffer_.get(), + kIOBufferSize, + base::Bind(&CheckResponseHelper::OnReadDataComplete, + base::Unretained(this))); +} + +void AppCacheServiceImpl::CheckResponseHelper::OnReadDataComplete(int result) { + if (result > 0) { + // Keep reading until we've read thru everything or failed to read. + amount_data_read_ += result; + response_reader_->ReadData( + data_buffer_.get(), + kIOBufferSize, + base::Bind(&CheckResponseHelper::OnReadDataComplete, + base::Unretained(this))); + return; + } + + AppCacheHistograms::CheckResponseResultType check_result; + if (result < 0) + check_result = AppCacheHistograms::READ_DATA_ERROR; + else if (info_buffer_->response_data_size != amount_data_read_ || + expected_total_size_ != amount_data_read_ + amount_headers_read_) + check_result = AppCacheHistograms::UNEXPECTED_DATA_SIZE; + else + check_result = AppCacheHistograms::RESPONSE_OK; + AppCacheHistograms::CountCheckResponseResult(check_result); + + if (check_result != AppCacheHistograms::RESPONSE_OK) + service_->DeleteAppCacheGroup(manifest_url_, net::CompletionCallback()); + delete this; +} + +// AppCacheStorageReference ------ + +AppCacheStorageReference::AppCacheStorageReference( + scoped_ptr<AppCacheStorage> storage) + : storage_(storage.Pass()) {} +AppCacheStorageReference::~AppCacheStorageReference() {} + +// AppCacheServiceImpl ------- + +AppCacheServiceImpl::AppCacheServiceImpl(quota::QuotaManagerProxy* + quota_manager_proxy) + : appcache_policy_(NULL), quota_client_(NULL), handler_factory_(NULL), + quota_manager_proxy_(quota_manager_proxy), + request_context_(NULL), + force_keep_session_state_(false) { + if (quota_manager_proxy_.get()) { + quota_client_ = new AppCacheQuotaClient(this); + quota_manager_proxy_->RegisterClient(quota_client_); + } +} + +AppCacheServiceImpl::~AppCacheServiceImpl() { + DCHECK(backends_.empty()); + std::for_each(pending_helpers_.begin(), + pending_helpers_.end(), + std::mem_fun(&AsyncHelper::Cancel)); + STLDeleteElements(&pending_helpers_); + if (quota_client_) + quota_client_->NotifyAppCacheDestroyed(); + + // Destroy storage_ first; ~AppCacheStorageImpl accesses other data members + // (special_storage_policy_). + storage_.reset(); +} + +void AppCacheServiceImpl::Initialize(const base::FilePath& cache_directory, + base::MessageLoopProxy* db_thread, + base::MessageLoopProxy* cache_thread) { + DCHECK(!storage_.get()); + cache_directory_ = cache_directory; + db_thread_ = db_thread; + cache_thread_ = cache_thread; + AppCacheStorageImpl* storage = new AppCacheStorageImpl(this); + storage->Initialize(cache_directory, db_thread, cache_thread); + storage_.reset(storage); +} + +void AppCacheServiceImpl::ScheduleReinitialize() { + if (reinit_timer_.IsRunning()) + return; + + // Reinitialization only happens when corruption has been noticed. + // We don't want to thrash the disk but we also don't want to + // leave the appcache disabled for an indefinite period of time. Some + // users never shutdown the browser. + + const base::TimeDelta kZeroDelta; + const base::TimeDelta kOneHour(base::TimeDelta::FromHours(1)); + const base::TimeDelta k30Seconds(base::TimeDelta::FromSeconds(30)); + + // If the system managed to stay up for long enough, reset the + // delay so a new failure won't incur a long wait to get going again. + base::TimeDelta up_time = base::Time::Now() - last_reinit_time_; + if (next_reinit_delay_ != kZeroDelta && up_time > kOneHour) + next_reinit_delay_ = kZeroDelta; + + reinit_timer_.Start(FROM_HERE, next_reinit_delay_, + this, &AppCacheServiceImpl::Reinitialize); + + // Adjust the delay for next time. + base::TimeDelta increment = std::max(k30Seconds, next_reinit_delay_); + next_reinit_delay_ = std::min(next_reinit_delay_ + increment, kOneHour); +} + +void AppCacheServiceImpl::Reinitialize() { + AppCacheHistograms::CountReinitAttempt(!last_reinit_time_.is_null()); + last_reinit_time_ = base::Time::Now(); + + // Inform observers of about this and give them a chance to + // defer deletion of the old storage object. + scoped_refptr<AppCacheStorageReference> + old_storage_ref(new AppCacheStorageReference(storage_.Pass())); + FOR_EACH_OBSERVER(Observer, observers_, + OnServiceReinitialized(old_storage_ref.get())); + + Initialize(cache_directory_, db_thread_, cache_thread_); +} + +void AppCacheServiceImpl::CanHandleMainResourceOffline( + const GURL& url, + const GURL& first_party, + const net::CompletionCallback& callback) { + CanHandleOfflineHelper* helper = + new CanHandleOfflineHelper(this, url, first_party, callback); + helper->Start(); +} + +void AppCacheServiceImpl::GetAllAppCacheInfo( + AppCacheInfoCollection* collection, + const net::CompletionCallback& callback) { + DCHECK(collection); + GetInfoHelper* helper = new GetInfoHelper(this, collection, callback); + helper->Start(); +} + +void AppCacheServiceImpl::DeleteAppCacheGroup( + const GURL& manifest_url, + const net::CompletionCallback& callback) { + DeleteHelper* helper = new DeleteHelper(this, manifest_url, callback); + helper->Start(); +} + +void AppCacheServiceImpl::DeleteAppCachesForOrigin( + const GURL& origin, const net::CompletionCallback& callback) { + DeleteOriginHelper* helper = new DeleteOriginHelper(this, origin, callback); + helper->Start(); +} + +void AppCacheServiceImpl::CheckAppCacheResponse(const GURL& manifest_url, + int64 cache_id, + int64 response_id) { + CheckResponseHelper* helper = new CheckResponseHelper( + this, manifest_url, cache_id, response_id); + helper->Start(); +} + +void AppCacheServiceImpl::set_special_storage_policy( + quota::SpecialStoragePolicy* policy) { + special_storage_policy_ = policy; +} + +void AppCacheServiceImpl::RegisterBackend( + AppCacheBackendImpl* backend_impl) { + DCHECK(backends_.find(backend_impl->process_id()) == backends_.end()); + backends_.insert( + BackendMap::value_type(backend_impl->process_id(), backend_impl)); +} + +void AppCacheServiceImpl::UnregisterBackend( + AppCacheBackendImpl* backend_impl) { + backends_.erase(backend_impl->process_id()); +} + +} // namespace appcache |