diff options
21 files changed, 2231 insertions, 82 deletions
diff --git a/webkit/appcache/appcache.h b/webkit/appcache/appcache.h index 57ca42a..b601fcc 100644 --- a/webkit/appcache/appcache.h +++ b/webkit/appcache/appcache.h @@ -75,6 +75,11 @@ class AppCache : public base::RefCounted<AppCache> { // Do not use the manifest after this call. void InitializeWithManifest(Manifest* manifest); + void FindResponseForRequest(const GURL& url, + AppCacheEntry* found_entry, AppCacheEntry* found_fallback_entry, + bool* found_network_namespace) { + return; // TODO(michaeln): write me + } private: friend class AppCacheGroup; friend class AppCacheHost; diff --git a/webkit/appcache/appcache_entry.h b/webkit/appcache/appcache_entry.h index c57ab6b..ac67ac3 100644 --- a/webkit/appcache/appcache_entry.h +++ b/webkit/appcache/appcache_entry.h @@ -42,6 +42,7 @@ class AppCacheEntry { int64 response_id() const { return response_id_; } void set_response_id(int64 id) { response_id_ = id; } + bool has_response_id() const { return response_id_ != kNoResponseId; } private: int types_; diff --git a/webkit/appcache/appcache_host.cc b/webkit/appcache/appcache_host.cc index 9266fd5..c91f221 100644 --- a/webkit/appcache/appcache_host.cc +++ b/webkit/appcache/appcache_host.cc @@ -68,7 +68,7 @@ void AppCacheHost::SelectCache(const GURL& document_url, // step is skipped here. See WebApplicationCacheHostImpl.cc if (cache_document_was_loaded_from != kNoCacheId) { - LoadCache(cache_document_was_loaded_from); + LoadSelectedCache(cache_document_was_loaded_from); return; } @@ -176,9 +176,9 @@ AppCacheRequestHandler* AppCacheHost::CreateRequestHandler( return new AppCacheRequestHandler(this, true); if ((associated_cache() && associated_cache()->is_complete()) || - is_selection_pending()) + is_selection_pending()) { return new AppCacheRequestHandler(this, false); - + } return NULL; } @@ -201,19 +201,20 @@ void AppCacheHost::OnGroupLoaded(AppCacheGroup* group, FinishCacheSelection(NULL, group); } -void AppCacheHost::LoadCache(int64 cache_id) { +void AppCacheHost::LoadSelectedCache(int64 cache_id) { DCHECK(cache_id != kNoCacheId); pending_selected_cache_id_ = cache_id; service_->storage()->LoadCache(cache_id, this); } void AppCacheHost::OnCacheLoaded(AppCache* cache, int64 cache_id) { - DCHECK(cache_id == pending_selected_cache_id_); - pending_selected_cache_id_ = kNoCacheId; - if (cache) + if (cache_id == pending_main_resource_cache_id_) { + pending_main_resource_cache_id_ = kNoCacheId; + main_resource_cache_ = cache; + } else if (cache_id == pending_selected_cache_id_) { + pending_selected_cache_id_ = kNoCacheId; FinishCacheSelection(cache, NULL); - else - FinishCacheSelection(NULL, NULL); + } } void AppCacheHost::FinishCacheSelection( @@ -281,6 +282,16 @@ void AppCacheHost::SetSwappableCache(AppCacheGroup* group) { swappable_cache_ = NULL; } +void AppCacheHost::LoadMainResourceCache(int64 cache_id) { + DCHECK(cache_id != kNoCacheId); + if (pending_main_resource_cache_id_ == cache_id || + (main_resource_cache_ && main_resource_cache_->cache_id() == cache_id)) { + return; + } + pending_main_resource_cache_id_ = cache_id; + service_->storage()->LoadCache(cache_id, this); +} + void AppCacheHost::AssociateCache(AppCache* cache) { if (associated_cache_.get()) { associated_cache_->UnassociateHost(this); diff --git a/webkit/appcache/appcache_host.h b/webkit/appcache/appcache_host.h index 1ebb68b..0622e3f 100644 --- a/webkit/appcache/appcache_host.h +++ b/webkit/appcache/appcache_host.h @@ -81,18 +81,22 @@ class AppCacheHost : public AppCacheStorage::Delegate, // same as the cache that is currently associated with the host. void SetSwappableCache(AppCacheGroup* group); + // Used to ensure that a loaded appcache survives a frame navigation. + void LoadMainResourceCache(int64 cache_id); + int host_id() const { return host_id_; } AppCacheService* service() const { return service_; } AppCacheFrontend* frontend() const { return frontend_; } AppCache* associated_cache() const { return associated_cache_.get(); } - private: bool is_selection_pending() const { return pending_selected_cache_id_ != kNoCacheId || !pending_selected_manifest_url_.is_empty(); } + + private: Status GetStatus(); - void LoadCache(int64 cache_id); + void LoadSelectedCache(int64 cache_id); void LoadOrCreateGroup(const GURL& manifest_url); // AppCacheStorage::Delegate impl @@ -125,6 +129,11 @@ class AppCacheHost : public AppCacheStorage::Delegate, // Keep a reference to the group being updated until the update completes. scoped_refptr<AppCacheGroup> group_being_updated_; + // Keep a reference to the cache of the main resource so it survives frame + // navigations. + scoped_refptr<AppCache> main_resource_cache_; + int64 pending_main_resource_cache_id_; + // Cache loading is async, if we're loading a specific cache or group // for the purposes of cache selection, one or the other of these will // indicate which cache or group is being loaded. @@ -153,6 +162,7 @@ class AppCacheHost : public AppCacheStorage::Delegate, // List of objects observing us. ObserverList<Observer> observers_; + friend class AppCacheRequestHandlerTest; FRIEND_TEST(AppCacheTest, CleanupUnusedCache); FRIEND_TEST(AppCacheGroupTest, CleanupUnusedGroup); FRIEND_TEST(AppCacheHostTest, Basic); diff --git a/webkit/appcache/appcache_interceptor.cc b/webkit/appcache/appcache_interceptor.cc index 88bcd56..fc4be1d 100644 --- a/webkit/appcache/appcache_interceptor.cc +++ b/webkit/appcache/appcache_interceptor.cc @@ -9,6 +9,7 @@ #include "webkit/appcache/appcache_interfaces.h" #include "webkit/appcache/appcache_request_handler.h" #include "webkit/appcache/appcache_service.h" +#include "webkit/appcache/appcache_url_request_job.h" namespace appcache { diff --git a/webkit/appcache/appcache_request_handler.cc b/webkit/appcache/appcache_request_handler.cc index 614c12e..068535e 100644 --- a/webkit/appcache/appcache_request_handler.cc +++ b/webkit/appcache/appcache_request_handler.cc @@ -7,68 +7,303 @@ #include "net/url_request/url_request.h" #include "net/url_request/url_request_job.h" #include "webkit/appcache/appcache.h" +#include "webkit/appcache/appcache_url_request_job.h" namespace appcache { -// AppCacheRequestHandler ----------------------------------------------------- - AppCacheRequestHandler::AppCacheRequestHandler(AppCacheHost* host, bool is_main_resource) - : is_main_request_(is_main_resource), host_(host) { + : host_(host), is_main_request_(is_main_resource), + is_waiting_for_cache_selection_(false), found_cache_id_(0), + found_network_namespace_(false) { DCHECK(host_); host_->AddObserver(this); } AppCacheRequestHandler::~AppCacheRequestHandler() { - if (host_) + if (host_) { + storage()->CancelDelegateCallbacks(this); host_->RemoveObserver(this); + } +} + +AppCacheStorage* AppCacheRequestHandler::storage() { + DCHECK(host_); + return host_->service()->storage(); } void AppCacheRequestHandler::GetExtraResponseInfo( int64* cache_id, GURL* manifest_url) { - // TODO(michaeln): If this is a main request and it was retrieved from - // an appcache, ensure that appcache survives the frame navigation. The - // AppCacheHost should hold reference to that cache to prevent it from - // being dropped from the in-memory collection of AppCaches. When cache - // selection occurs, that extra reference should be dropped. Perhaps - // maybe: if (is_main) host->LoadCacheOfMainResource(cache_id); + if (job_ && job_->is_delivering_appcache_response()) { + *cache_id = job_->cache_id(); + *manifest_url = job_->manifest_url(); + } } -URLRequestJob* AppCacheRequestHandler::MaybeLoadResource(URLRequest* request) { - // 6.9.7 Changes to the networking model - // If the resource is not to be fetched using the HTTP GET mechanism or - // equivalent ... then fetch the resource normally +AppCacheURLRequestJob* AppCacheRequestHandler::MaybeLoadResource( + URLRequest* request) { if (!host_ || !IsSchemeAndMethodSupported(request)) return NULL; - // TODO(michaeln): write me - return NULL; + + // This method can get called multiple times over the life + // of a request. The case we detect here is having scheduled + // delivery of a "network response" using a job setup on an + // earlier call thru this method. To send the request thru + // to the network involves restarting the request altogether, + // which will call thru to our interception layer again. + // This time thru, we return NULL so the request hits the wire. + if (job_) { + DCHECK(job_->is_delivering_network_response()); + job_ = NULL; + return NULL; + } + + // Clear out our 'found' fields since we're starting a request for a + // new resource, any values in those fields are no longer valid. + found_entry_ = AppCacheEntry(); + found_fallback_entry_ = AppCacheEntry(); + found_cache_id_ = kNoCacheId; + found_manifest_url_ = GURL::EmptyGURL(); + found_network_namespace_ = false; + + if (is_main_request_) + MaybeLoadMainResource(request); + else + MaybeLoadSubResource(request); + + // If its been setup to deliver a network response, we can just delete + // it now and return NULL instead to achieve that since it couldn't + // have been started yet. + if (job_ && job_->is_delivering_network_response()) { + DCHECK(!job_->has_been_started()); + job_ = NULL; + } + + return job_; } -URLRequestJob* AppCacheRequestHandler::MaybeLoadFallbackForRedirect( +AppCacheURLRequestJob* AppCacheRequestHandler::MaybeLoadFallbackForRedirect( URLRequest* request, const GURL& location) { if (!host_ || !IsSchemeAndMethodSupported(request)) return NULL; - // TODO(michaeln): write me - return NULL; + if (is_main_request_) + return NULL; + if (request->url().GetOrigin() == location.GetOrigin()) + return NULL; + + DCHECK(!job_); // our jobs never generate redirects + + if (found_fallback_entry_.has_response_id()) { + // 6.9.6, step 4: If this results in a redirect to another origin, + // get the resource of the fallback entry. + job_ = new AppCacheURLRequestJob(request, storage()); + DeliverAppCachedResponse(found_fallback_entry_, found_cache_id_, + found_manifest_url_); + } else if (!found_network_namespace_) { + // 6.9.6, step 6: Fail the resource load. + job_ = new AppCacheURLRequestJob(request, storage()); + DeliverErrorResponse(); + } else { + // 6.9.6 step 3 and 5: Fetch the resource normally. + } + + return job_; } -URLRequestJob* AppCacheRequestHandler::MaybeLoadFallbackForResponse( +AppCacheURLRequestJob* AppCacheRequestHandler::MaybeLoadFallbackForResponse( URLRequest* request) { if (!host_ || !IsSchemeAndMethodSupported(request)) return NULL; - // TODO(michaeln): write me - return NULL; + if (!found_fallback_entry_.has_response_id()) + return NULL; + + if (request->status().status() == URLRequestStatus::CANCELED || + request->status().status() == URLRequestStatus::HANDLED_EXTERNALLY) { + // 6.9.6, step 4: But not if the user canceled the download. + return NULL; + } + + // We don't fallback for responses that we delivered. + if (job_) { + DCHECK(!job_->is_delivering_network_response()); + return NULL; + } + + if (request->status().is_success()) { + int code_major = request->GetResponseCode() / 100; + if (code_major !=4 && code_major != 5) + return NULL; + } + + // 6.9.6, step 4: If this results in a 4xx or 5xx status code + // or there were network errors, get the resource of the fallback entry. + job_ = new AppCacheURLRequestJob(request, storage()); + DeliverAppCachedResponse(found_fallback_entry_, found_cache_id_, + found_manifest_url_); + return job_; +} + +void AppCacheRequestHandler::OnDestructionImminent(AppCacheHost* host) { + storage()->CancelDelegateCallbacks(this); + host_ = NULL; // no need to RemoveObserver, the host is being deleted + + // Since the host is being deleted, we don't have to complete any job + // that is current running. It's destined for the bit bucket anyway. + if (job_) { + job_->Kill(); + job_ = NULL; + } +} + +void AppCacheRequestHandler::DeliverAppCachedResponse( + const AppCacheEntry& entry, int64 cache_id, const GURL& manifest_url) { + DCHECK(job_ && job_->is_waiting()); + DCHECK(entry.has_response_id()); + job_->DeliverAppCachedResponse(manifest_url, cache_id, entry); +} + +void AppCacheRequestHandler::DeliverErrorResponse() { + DCHECK(job_ && job_->is_waiting()); + job_->DeliverErrorResponse(); +} + +void AppCacheRequestHandler::DeliverNetworkResponse() { + DCHECK(job_ && job_->is_waiting()); + job_->DeliverNetworkResponse(); +} + +// Main-resource handling ---------------------------------------------- + +void AppCacheRequestHandler::MaybeLoadMainResource(URLRequest* request) { + DCHECK(!job_); + + // We may have to wait for our storage query to complete, but + // this query can also complete syncrhonously. + job_ = new AppCacheURLRequestJob(request, storage()); + storage()->FindResponseForMainRequest(request->url(), this); +} + +void AppCacheRequestHandler::OnMainResponseFound( + const GURL& url, const AppCacheEntry& entry, + const AppCacheEntry& fallback_entry, + int64 cache_id, const GURL& manifest_url) { + DCHECK(host_); + DCHECK(is_main_request_); + DCHECK(!entry.IsForeign()); + DCHECK(!fallback_entry.IsForeign()); + DCHECK(!(entry.has_response_id() && fallback_entry.has_response_id())); + + if (cache_id != kNoCacheId) { + // AppCacheHost loads and holds a reference to the main resource cache + // for two reasons, firstly to preload the cache into the working set + // in advance of subresource loads happening, secondly to prevent the + // AppCache from falling out of the working set on frame navigations. + host_->LoadMainResourceCache(cache_id); + } + + // 6.11.1 Navigating across documents, steps 10 and 14. + + found_entry_ = entry; + found_fallback_entry_ = fallback_entry; + found_cache_id_ = cache_id; + found_manifest_url_ = manifest_url; + found_network_namespace_ = false; // not applicable to main requests + + if (found_entry_.has_response_id()) { + DeliverAppCachedResponse(found_entry_, found_cache_id_, + found_manifest_url_); + } else { + DeliverNetworkResponse(); + } +} + +// Sub-resource handling ---------------------------------------------- + +void AppCacheRequestHandler::MaybeLoadSubResource( + URLRequest* request) { + DCHECK(!job_); + + if (host_->is_selection_pending()) { + // We have to wait until cache selection is complete and the + // selected cache is loaded. + is_waiting_for_cache_selection_ = true; + job_ = new AppCacheURLRequestJob(request, storage()); + return; + } + + if (!host_->associated_cache() || + !host_->associated_cache()->is_complete()) { + return; + } + + job_ = new AppCacheURLRequestJob(request, storage()); + ContinueMaybeLoadSubResource(); +} + +void AppCacheRequestHandler::ContinueMaybeLoadSubResource() { + // 6.9.6 Changes to the networking model + // If the resource is not to be fetched using the HTTP GET mechanism or + // equivalent ... then fetch the resource normally. + DCHECK(job_); + DCHECK(host_->associated_cache() && + host_->associated_cache()->is_complete()); + + const GURL& url = job_->request()->url(); + AppCache* cache = host_->associated_cache(); + storage()->FindResponseForSubRequest( + host_->associated_cache(), url, + &found_entry_, &found_fallback_entry_, &found_network_namespace_); + + if (found_entry_.has_response_id()) { + // Step 2: If there's an entry, get it instead. + DCHECK(!found_network_namespace_ && + !found_fallback_entry_.has_response_id()); + found_cache_id_ = cache->cache_id(); + found_manifest_url_ = cache->owning_group()->manifest_url(); + DeliverAppCachedResponse( + found_entry_, found_cache_id_, found_manifest_url_); + return; + } + + if (found_fallback_entry_.has_response_id()) { + // Step 4: Fetch the resource normally, if this results + // in certain conditions, then use the fallback. + DCHECK(!found_network_namespace_ && + !found_entry_.has_response_id()); + found_cache_id_ = cache->cache_id(); + found_manifest_url_ = cache->owning_group()->manifest_url(); + DeliverNetworkResponse(); + return; + } + + if (found_network_namespace_) { + // Step 3 and 5: Fetch the resource normally. + DCHECK(!found_entry_.has_response_id() && + !found_fallback_entry_.has_response_id()); + DeliverNetworkResponse(); + return; + } + + // Step 6: Fail the resource load. + DeliverErrorResponse(); } void AppCacheRequestHandler::OnCacheSelectionComplete(AppCacheHost* host) { DCHECK(host == host_); - // TODO(michaeln): write me -} + if (is_main_request_) + return; + if (!is_waiting_for_cache_selection_) + return; -void AppCacheRequestHandler::OnDestructionImminent(AppCacheHost* host) { - host_ = NULL; - // TODO(michaeln): write me + is_waiting_for_cache_selection_ = false; + + if (!host_->associated_cache() || + !host_->associated_cache()->is_complete()) { + DeliverNetworkResponse(); + return; + } + + ContinueMaybeLoadSubResource(); } } // namespace appcache - diff --git a/webkit/appcache/appcache_request_handler.h b/webkit/appcache/appcache_request_handler.h index d848f08..55b4ef7 100644 --- a/webkit/appcache/appcache_request_handler.h +++ b/webkit/appcache/appcache_request_handler.h @@ -13,21 +13,24 @@ class URLRequestJob; namespace appcache { +class AppCacheURLRequestJob; + // An instance is created for each URLRequest. The instance survives all // http transactions involved in the processing of its URLRequest, and is // given the opportunity to hijack the request along the way. Callers // should use AppCacheHost::CreateRequestHandler to manufacture instances // that can retrieve resources for a particular host. class AppCacheRequestHandler : public URLRequest::UserData, - public AppCacheHost::Observer { + public AppCacheHost::Observer, + public AppCacheStorage::Delegate { public: virtual ~AppCacheRequestHandler(); - // Should be called on each request intercept opportunity. - URLRequestJob* MaybeLoadResource(URLRequest* request); - URLRequestJob* MaybeLoadFallbackForRedirect(URLRequest* request, - const GURL& location); - URLRequestJob* MaybeLoadFallbackForResponse(URLRequest* request); + // These are called on each request intercept opportunity. + AppCacheURLRequestJob* MaybeLoadResource(URLRequest* request); + AppCacheURLRequestJob* MaybeLoadFallbackForRedirect(URLRequest* request, + const GURL& location); + AppCacheURLRequestJob* MaybeLoadFallbackForResponse(URLRequest* request); void GetExtraResponseInfo(int64* cache_id, GURL* manifest_url); @@ -37,16 +40,61 @@ class AppCacheRequestHandler : public URLRequest::UserData, // Callers should use AppCacheHost::CreateRequestHandler. AppCacheRequestHandler(AppCacheHost* host, bool is_main_resource); - // AppCacheHost::Observer methods - virtual void OnCacheSelectionComplete(AppCacheHost* host); + // AppCacheHost::Observer override virtual void OnDestructionImminent(AppCacheHost* host); - // Main vs subresource loads are very different. - // TODO(michaeln): maybe have two derived classes? - bool is_main_request_; + // Helpers to instruct a waiting job with what response to + // deliver for the request we're handling. + void DeliverAppCachedResponse(const AppCacheEntry& entry, int64 cache_id, + const GURL& manifest_url); + void DeliverNetworkResponse(); + void DeliverErrorResponse(); + + // Helper to retrieve a pointer to the storage object. + AppCacheStorage* storage(); + + // Main-resource loading ------------------------------------- + + void MaybeLoadMainResource(URLRequest* request); + + // AppCacheStorage::Delegate methods + virtual void OnMainResponseFound( + const GURL& url, const AppCacheEntry& entry, + const AppCacheEntry& fallback_entry, + int64 cache_id, const GURL& mainfest_url); + + // Sub-resource loading ------------------------------------- + + void MaybeLoadSubResource(URLRequest* request); + void ContinueMaybeLoadSubResource(); + + // AppCacheHost::Observer override + virtual void OnCacheSelectionComplete(AppCacheHost* host); + + // Data members ----------------------------------------------- + + // What host we're servicing a request for. AppCacheHost* host_; - scoped_refptr<AppCache> cache_; - scoped_refptr<URLRequestJob> job_; + + // Main vs subresource loads are somewhat different. + bool is_main_request_; + + // Subresource requests wait until after cache selection completes. + bool is_waiting_for_cache_selection_; + + // Info about the type of response we found for delivery. + // These are relevant for both main and subresource requests. + AppCacheEntry found_entry_; + AppCacheEntry found_fallback_entry_; + int64 found_cache_id_; + GURL found_manifest_url_; + bool found_network_namespace_; + + // The job we use to deliver a response. + scoped_refptr<AppCacheURLRequestJob> job_; + + friend class AppCacheRequestHandlerTest; + DISALLOW_COPY_AND_ASSIGN(AppCacheRequestHandler); }; } // namespace appcache diff --git a/webkit/appcache/appcache_request_handler_unittest.cc b/webkit/appcache/appcache_request_handler_unittest.cc new file mode 100644 index 0000000..6b31651 --- /dev/null +++ b/webkit/appcache/appcache_request_handler_unittest.cc @@ -0,0 +1,653 @@ +// Copyright (c) 2009 The Chromium Authos. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/message_loop.h" +#include "base/thread.h" +#include "base/waitable_event.h" +#include "net/url_request/url_request.h" +#include "net/url_request/url_request_error_job.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/appcache/appcache.h" +#include "webkit/appcache/appcache_request_handler.h" +#include "webkit/appcache/appcache_url_request_job.h" +#include "webkit/appcache/mock_appcache_service.h" + +namespace appcache { + +class AppCacheRequestHandlerTest : public testing::Test { + public: + class MockFrontend : public AppCacheFrontend { + public: + virtual void OnCacheSelected(int host_id, int64 cache_id , + appcache::Status status) {} + + virtual void OnStatusChanged(const std::vector<int>& host_ids, + appcache::Status status) {} + + virtual void OnEventRaised(const std::vector<int>& host_ids, + appcache::EventID event_id) {} + }; + + // Helper class run a test on our io_thread. The io_thread + // is spun up once and reused for all tests. + template <class Method> + class WrapperTask : public Task { + public: + WrapperTask(AppCacheRequestHandlerTest* test, Method method) + : test_(test), method_(method) { + } + + virtual void Run() { + test_->SetUpTest(); + (test_->*method_)(); + } + + private: + AppCacheRequestHandlerTest* test_; + Method method_; + }; + + // Subclasses to simulate particular response codes so test cases can + // exercise fallback code paths. + + class MockURLRequestJob : public URLRequestJob { + public: + MockURLRequestJob(URLRequest* request, int response_code) + : URLRequestJob(request), response_code_(response_code) {} + virtual void Start() {} + virtual int GetResponseCode() const { return response_code_; } + int response_code_; + }; + + class MockURLRequest : public URLRequest { + public: + explicit MockURLRequest(const GURL& url) : URLRequest(url, NULL) {} + + void SimulateResponseCode(int http_response_code) { + mock_factory_job_ = new MockURLRequestJob(this, http_response_code); + Start(); + DCHECK(!mock_factory_job_); + // All our simulation need to do satisfy are the following two DCHECKs + DCHECK(status().is_success()); + DCHECK_EQ(http_response_code, GetResponseCode()); + } + }; + + static URLRequestJob* MockHttpJobFactory(URLRequest* request, + const std::string& scheme) { + if (mock_factory_job_) { + URLRequestJob* temp = mock_factory_job_; + mock_factory_job_ = NULL; + return temp; + } else { + // Some of these tests trigger UpdateJobs which start URLRequests. + // We short circuit those be returning error jobs. + return new URLRequestErrorJob(request, net::ERR_INTERNET_DISCONNECTED); + } + } + + static void SetUpTestCase() { + io_thread_.reset(new base::Thread("AppCacheRequestHandlerTest Thread")); + base::Thread::Options options(MessageLoop::TYPE_IO, 0); + io_thread_->StartWithOptions(options); + } + + static void TearDownTestCase() { + io_thread_.reset(NULL); + } + + // Test harness -------------------------------------------------- + + AppCacheRequestHandlerTest() + : ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)), + orig_http_factory_(NULL) { + } + + template <class Method> + void RunTestOnIOThread(Method method) { + test_finished_event_ .reset(new base::WaitableEvent(false, false)); + io_thread_->message_loop()->PostTask( + FROM_HERE, new WrapperTask<Method>(this, method)); + test_finished_event_->Wait(); + } + + void SetUpTest() { + DCHECK(MessageLoop::current() == io_thread_->message_loop()); + orig_http_factory_ = URLRequest::RegisterProtocolFactory( + "http", MockHttpJobFactory); + mock_service_.reset(new MockAppCacheService); + mock_frontend_.reset(new MockFrontend); + host_.reset( + new AppCacheHost(1, mock_frontend_.get(), mock_service_.get())); + } + + void TearDownTest() { + DCHECK(MessageLoop::current() == io_thread_->message_loop()); + DCHECK(!mock_factory_job_); + URLRequest::RegisterProtocolFactory("http", orig_http_factory_); + orig_http_factory_ = NULL; + job_ = NULL; + handler_.reset(); + request_.reset(); + host_.reset(); + mock_frontend_.reset(); + mock_service_.reset(); + } + + void TestFinished() { + // We unwind the stack prior to finishing up to let stack + // based objects get deleted. + DCHECK(MessageLoop::current() == io_thread_->message_loop()); + MessageLoop::current()->PostTask(FROM_HERE, + method_factory_.NewRunnableMethod( + &AppCacheRequestHandlerTest::TestFinishedUnwound)); + } + + void TestFinishedUnwound() { + TearDownTest(); + test_finished_event_->Signal(); + } + + void PushNextTask(Task* task) { + task_stack_.push(task); + } + + void ScheduleNextTask() { + DCHECK(MessageLoop::current() == io_thread_->message_loop()); + if (task_stack_.empty()) { + TestFinished(); + return; + } + MessageLoop::current()->PostTask(FROM_HERE, task_stack_.top()); + task_stack_.pop(); + } + + // MainResource_Miss -------------------------------------------------- + + void MainResource_Miss() { + PushNextTask(method_factory_.NewRunnableMethod( + &AppCacheRequestHandlerTest::Verify_MainResource_Miss)); + + request_.reset(new MockURLRequest(GURL("http://blah/"))); + handler_.reset(host_->CreateRequestHandler(request_.get(), true)); + EXPECT_TRUE(handler_.get()); + + job_ = handler_->MaybeLoadResource(request_.get()); + EXPECT_TRUE(job_.get()); + EXPECT_TRUE(job_->is_waiting()); + + // We have to wait for completion of storage->FindResponseForMainRequest. + ScheduleNextTask(); + } + + void Verify_MainResource_Miss() { + EXPECT_FALSE(job_->is_waiting()); + EXPECT_TRUE(job_->is_delivering_network_response()); + + int64 cache_id = kNoCacheId; + GURL manifest_url; + handler_->GetExtraResponseInfo(&cache_id, &manifest_url); + EXPECT_EQ(kNoCacheId, cache_id); + EXPECT_EQ(GURL(), manifest_url); + + AppCacheURLRequestJob* fallback_job; + fallback_job = handler_->MaybeLoadFallbackForRedirect( + request_.get(), GURL("http://blah/redirect")); + EXPECT_FALSE(fallback_job); + fallback_job = handler_->MaybeLoadFallbackForResponse(request_.get()); + EXPECT_FALSE(fallback_job); + + TestFinished(); + } + + // MainResource_Hit -------------------------------------------------- + + void MainResource_Hit() { + PushNextTask(method_factory_.NewRunnableMethod( + &AppCacheRequestHandlerTest::Verify_MainResource_Hit)); + + request_.reset(new MockURLRequest(GURL("http://blah/"))); + handler_.reset(host_->CreateRequestHandler(request_.get(), true)); + EXPECT_TRUE(handler_.get()); + + mock_storage()->SimulateFindMainResource( + AppCacheEntry(AppCacheEntry::EXPLICIT, 1), AppCacheEntry(), + 1, GURL("http://blah/manifest/")); + + job_ = handler_->MaybeLoadResource(request_.get()); + EXPECT_TRUE(job_.get()); + EXPECT_TRUE(job_->is_waiting()); + + // We have to wait for completion of storage->FindResponseForMainRequest. + ScheduleNextTask(); + } + + void Verify_MainResource_Hit() { + EXPECT_FALSE(job_->is_waiting()); + EXPECT_TRUE(job_->is_delivering_appcache_response()); + + int64 cache_id = kNoCacheId; + GURL manifest_url; + handler_->GetExtraResponseInfo(&cache_id, &manifest_url); + EXPECT_EQ(1, cache_id); + EXPECT_EQ(GURL("http://blah/manifest/"), manifest_url); + + AppCacheURLRequestJob* fallback_job; + fallback_job = handler_->MaybeLoadFallbackForResponse(request_.get()); + EXPECT_FALSE(fallback_job); + + TestFinished(); + } + + // MainResource_Fallback -------------------------------------------------- + + void MainResource_Fallback() { + PushNextTask(method_factory_.NewRunnableMethod( + &AppCacheRequestHandlerTest::Verify_MainResource_Fallback)); + + request_.reset(new MockURLRequest(GURL("http://blah/"))); + handler_.reset(host_->CreateRequestHandler(request_.get(), true)); + EXPECT_TRUE(handler_.get()); + + mock_storage()->SimulateFindMainResource( + AppCacheEntry(), AppCacheEntry(AppCacheEntry::EXPLICIT, 1), + 1, GURL("http://blah/manifest/")); + + job_ = handler_->MaybeLoadResource(request_.get()); + EXPECT_TRUE(job_.get()); + EXPECT_TRUE(job_->is_waiting()); + + // We have to wait for completion of storage->FindResponseForMainRequest. + ScheduleNextTask(); + } + + void Verify_MainResource_Fallback() { + EXPECT_FALSE(job_->is_waiting()); + EXPECT_TRUE(job_->is_delivering_network_response()); + + // When the request is restarted, the existing job is dropped so a + // real network job gets created. We expect NULL here which will cause + // the net library to create a real job. + job_ = handler_->MaybeLoadResource(request_.get()); + EXPECT_FALSE(job_); + + // Simulate an http error of the real network job. + request_->SimulateResponseCode(500); + + job_ = handler_->MaybeLoadFallbackForResponse(request_.get()); + EXPECT_TRUE(job_); + EXPECT_TRUE(job_->is_delivering_appcache_response()); + + int64 cache_id = kNoCacheId; + GURL manifest_url; + handler_->GetExtraResponseInfo(&cache_id, &manifest_url); + EXPECT_EQ(1, cache_id); + EXPECT_EQ(GURL("http://blah/manifest/"), manifest_url); + + TestFinished(); + } + + // SubResource_Miss_WithNoCacheSelected ---------------------------------- + + void SubResource_Miss_WithNoCacheSelected() { + request_.reset(new MockURLRequest(GURL("http://blah/"))); + handler_.reset(host_->CreateRequestHandler(request_.get(), false)); + + // We avoid creating handler when possible, sub-resource requests are not + // subject to retrieval from an appcache when there's no associated cache. + EXPECT_FALSE(handler_.get()); + + TestFinished(); + } + + // SubResource_Miss_WithCacheSelected ---------------------------------- + + void SubResource_Miss_WithCacheSelected() { + // A sub-resource load where the resource is not in an appcache, or + // in a network or fallback namespace, should result in a failed request. + host_->AssociateCache(MakeNewCache()); + + request_.reset(new MockURLRequest(GURL("http://blah/"))); + handler_.reset(host_->CreateRequestHandler(request_.get(), false)); + EXPECT_TRUE(handler_.get()); + + job_ = handler_->MaybeLoadResource(request_.get()); + EXPECT_TRUE(job_.get()); + EXPECT_TRUE(job_->is_delivering_error_response()); + + AppCacheURLRequestJob* fallback_job; + fallback_job = handler_->MaybeLoadFallbackForRedirect( + request_.get(), GURL("http://blah/redirect")); + EXPECT_FALSE(fallback_job); + fallback_job = handler_->MaybeLoadFallbackForResponse(request_.get()); + EXPECT_FALSE(fallback_job); + + TestFinished(); + } + + // SubResource_Miss_WithWaitForCacheSelection ----------------------------- + + void SubResource_Miss_WithWaitForCacheSelection() { + // Precondition, the host is waiting on cache selection. + scoped_refptr<AppCache> cache(MakeNewCache()); + host_->pending_selected_cache_id_ = cache->cache_id(); + + request_.reset(new MockURLRequest(GURL("http://blah/"))); + handler_.reset(host_->CreateRequestHandler(request_.get(), false)); + EXPECT_TRUE(handler_.get()); + job_ = handler_->MaybeLoadResource(request_.get()); + EXPECT_TRUE(job_.get()); + EXPECT_TRUE(job_->is_waiting()); + + host_->FinishCacheSelection(cache, NULL); + EXPECT_FALSE(job_->is_waiting()); + EXPECT_TRUE(job_->is_delivering_error_response()); + + AppCacheURLRequestJob* fallback_job; + fallback_job = handler_->MaybeLoadFallbackForRedirect( + request_.get(), GURL("http://blah/redirect")); + EXPECT_FALSE(fallback_job); + fallback_job = handler_->MaybeLoadFallbackForResponse(request_.get()); + EXPECT_FALSE(fallback_job); + + TestFinished(); + } + + // SubResource_Hit ----------------------------- + + void SubResource_Hit() { + host_->AssociateCache(MakeNewCache()); + + mock_storage()->SimulateFindSubResource( + AppCacheEntry(AppCacheEntry::EXPLICIT, 1), AppCacheEntry(), false); + + request_.reset(new MockURLRequest(GURL("http://blah/"))); + handler_.reset(host_->CreateRequestHandler(request_.get(), false)); + EXPECT_TRUE(handler_.get()); + job_ = handler_->MaybeLoadResource(request_.get()); + EXPECT_TRUE(job_.get()); + EXPECT_TRUE(job_->is_delivering_appcache_response()); + + AppCacheURLRequestJob* fallback_job; + fallback_job = handler_->MaybeLoadFallbackForRedirect( + request_.get(), GURL("http://blah/redirect")); + EXPECT_FALSE(fallback_job); + fallback_job = handler_->MaybeLoadFallbackForResponse(request_.get()); + EXPECT_FALSE(fallback_job); + + TestFinished(); + } + + // SubResource_RedirectFallback ----------------------------- + + void SubResource_RedirectFallback() { + // Redirects to resources in the a different origin are subject to + // fallback namespaces. + host_->AssociateCache(MakeNewCache()); + + mock_storage()->SimulateFindSubResource( + AppCacheEntry(), AppCacheEntry(AppCacheEntry::EXPLICIT, 1), false); + + request_.reset(new MockURLRequest(GURL("http://blah/"))); + handler_.reset(host_->CreateRequestHandler(request_.get(), false)); + EXPECT_TRUE(handler_.get()); + job_ = handler_->MaybeLoadResource(request_.get()); + EXPECT_FALSE(job_.get()); + + job_ = handler_->MaybeLoadFallbackForRedirect( + request_.get(), GURL("http://not_blah/redirect")); + EXPECT_TRUE(job_.get()); + EXPECT_TRUE(job_->is_delivering_appcache_response()); + + AppCacheURLRequestJob* fallback_job; + fallback_job = handler_->MaybeLoadFallbackForResponse(request_.get()); + EXPECT_FALSE(fallback_job); + + TestFinished(); + } + + // SubResource_NoRedirectFallback ----------------------------- + + void SubResource_NoRedirectFallback() { + // Redirects to resources in the same-origin are not subject to + // fallback namespaces. + host_->AssociateCache(MakeNewCache()); + + mock_storage()->SimulateFindSubResource( + AppCacheEntry(), AppCacheEntry(AppCacheEntry::EXPLICIT, 1), false); + + request_.reset(new MockURLRequest(GURL("http://blah/"))); + handler_.reset(host_->CreateRequestHandler(request_.get(), false)); + EXPECT_TRUE(handler_.get()); + job_ = handler_->MaybeLoadResource(request_.get()); + EXPECT_FALSE(job_.get()); + + AppCacheURLRequestJob* fallback_job; + fallback_job = handler_->MaybeLoadFallbackForRedirect( + request_.get(), GURL("http://blah/redirect")); + EXPECT_FALSE(fallback_job); + + request_->SimulateResponseCode(200); + fallback_job = handler_->MaybeLoadFallbackForResponse(request_.get()); + EXPECT_FALSE(fallback_job); + + TestFinished(); + } + + // SubResource_Network ----------------------------- + + void SubResource_Network() { + // A sub-resource load where the resource is in a network namespace, + // should result in the system using a 'real' job to do the network + // retrieval. + host_->AssociateCache(MakeNewCache()); + + mock_storage()->SimulateFindSubResource( + AppCacheEntry(), AppCacheEntry(), true); + + request_.reset(new MockURLRequest(GURL("http://blah/"))); + handler_.reset(host_->CreateRequestHandler(request_.get(), false)); + EXPECT_TRUE(handler_.get()); + job_ = handler_->MaybeLoadResource(request_.get()); + EXPECT_FALSE(job_.get()); + + AppCacheURLRequestJob* fallback_job; + fallback_job = handler_->MaybeLoadFallbackForRedirect( + request_.get(), GURL("http://blah/redirect")); + EXPECT_FALSE(fallback_job); + fallback_job = handler_->MaybeLoadFallbackForResponse(request_.get()); + EXPECT_FALSE(fallback_job); + + TestFinished(); + } + + // DestroyedHost ----------------------------- + + void DestroyedHost() { + host_->AssociateCache(MakeNewCache()); + + mock_storage()->SimulateFindSubResource( + AppCacheEntry(AppCacheEntry::EXPLICIT, 1), AppCacheEntry(), false); + + request_.reset(new MockURLRequest(GURL("http://blah/"))); + handler_.reset(host_->CreateRequestHandler(request_.get(), false)); + EXPECT_TRUE(handler_.get()); + + host_.reset(); + + EXPECT_FALSE(handler_->MaybeLoadResource(request_.get())); + EXPECT_FALSE(handler_->MaybeLoadFallbackForRedirect( + request_.get(), GURL("http://blah/redirect"))); + EXPECT_FALSE(handler_->MaybeLoadFallbackForResponse(request_.get())); + + TestFinished(); + } + + // DestroyedHostWithWaitingJob ----------------------------- + + void DestroyedHostWithWaitingJob() { + // Precondition, the host is waiting on cache selection. + host_->pending_selected_cache_id_ = 1; + + request_.reset(new MockURLRequest(GURL("http://blah/"))); + handler_.reset(host_->CreateRequestHandler(request_.get(), false)); + EXPECT_TRUE(handler_.get()); + + job_ = handler_->MaybeLoadResource(request_.get()); + EXPECT_TRUE(job_.get()); + EXPECT_TRUE(job_->is_waiting()); + + host_.reset(); + EXPECT_TRUE(job_->has_been_killed()); + + EXPECT_FALSE(handler_->MaybeLoadResource(request_.get())); + EXPECT_FALSE(handler_->MaybeLoadFallbackForRedirect( + request_.get(), GURL("http://blah/redirect"))); + EXPECT_FALSE(handler_->MaybeLoadFallbackForResponse(request_.get())); + + TestFinished(); + } + + // UnsupportedScheme ----------------------------- + + void UnsupportedScheme() { + // Precondition, the host is waiting on cache selection. + host_->pending_selected_cache_id_ = 1; + + request_.reset(new MockURLRequest(GURL("ftp://blah/"))); + handler_.reset(host_->CreateRequestHandler(request_.get(), false)); + EXPECT_TRUE(handler_.get()); // we could redirect to http (conceivably) + + EXPECT_FALSE(handler_->MaybeLoadResource(request_.get())); + EXPECT_FALSE(handler_->MaybeLoadFallbackForRedirect( + request_.get(), GURL("ftp://blah/redirect"))); + EXPECT_FALSE(handler_->MaybeLoadFallbackForResponse(request_.get())); + + TestFinished(); + } + + // CanceledRequest ----------------------------- + + void CanceledRequest() { + request_.reset(new MockURLRequest(GURL("http://blah/"))); + handler_.reset(host_->CreateRequestHandler(request_.get(), true)); + EXPECT_TRUE(handler_.get()); + + job_ = handler_->MaybeLoadResource(request_.get()); + EXPECT_TRUE(job_.get()); + EXPECT_TRUE(job_->is_waiting()); + EXPECT_FALSE(job_->has_been_started()); + + mock_factory_job_ = job_.get(); + request_->Start(); + EXPECT_TRUE(job_->has_been_started()); + + request_->Cancel(); + EXPECT_TRUE(job_->has_been_killed()); + + EXPECT_FALSE(handler_->MaybeLoadFallbackForResponse(request_.get())); + + TestFinished(); + } + + // Test case helpers -------------------------------------------------- + + AppCache* MakeNewCache() { + AppCache* cache = new AppCache( + mock_service_.get(), mock_storage()->NewCacheId()); + cache->set_complete(true); + AppCacheGroup* group = new AppCacheGroup( + mock_service_.get(), GURL("http://blah/manifest")); + group->AddCache(cache); + return cache; + } + + MockAppCacheStorage* mock_storage() { + return reinterpret_cast<MockAppCacheStorage*>(mock_service_->storage()); + } + + // Data members -------------------------------------------------- + + ScopedRunnableMethodFactory<AppCacheRequestHandlerTest> method_factory_; + scoped_ptr<base::WaitableEvent> test_finished_event_; + std::stack<Task*> task_stack_; + scoped_ptr<MockAppCacheService> mock_service_; + scoped_ptr<MockFrontend> mock_frontend_; + scoped_ptr<AppCacheHost> host_; + scoped_ptr<MockURLRequest> request_; + scoped_ptr<AppCacheRequestHandler> handler_; + scoped_refptr<AppCacheURLRequestJob> job_; + URLRequest::ProtocolFactory* orig_http_factory_; + + static scoped_ptr<base::Thread> io_thread_; + static URLRequestJob* mock_factory_job_; +}; + +// static +scoped_ptr<base::Thread> AppCacheRequestHandlerTest::io_thread_; +URLRequestJob* AppCacheRequestHandlerTest::mock_factory_job_ = NULL; + +TEST_F(AppCacheRequestHandlerTest, MainResource_Miss) { + RunTestOnIOThread(&AppCacheRequestHandlerTest::MainResource_Miss); +} + +TEST_F(AppCacheRequestHandlerTest, MainResource_Hit) { + RunTestOnIOThread(&AppCacheRequestHandlerTest::MainResource_Hit); +} + +TEST_F(AppCacheRequestHandlerTest, MainResource_Fallback) { + RunTestOnIOThread(&AppCacheRequestHandlerTest::MainResource_Fallback); +} + +TEST_F(AppCacheRequestHandlerTest, SubResource_Miss_WithNoCacheSelected) { + RunTestOnIOThread( + &AppCacheRequestHandlerTest::SubResource_Miss_WithNoCacheSelected); +} + +TEST_F(AppCacheRequestHandlerTest, SubResource_Miss_WithCacheSelected) { + RunTestOnIOThread( + &AppCacheRequestHandlerTest::SubResource_Miss_WithCacheSelected); +} + +TEST_F(AppCacheRequestHandlerTest, + SubResource_Miss_WithWaitForCacheSelection) { + RunTestOnIOThread( + &AppCacheRequestHandlerTest::SubResource_Miss_WithWaitForCacheSelection); +} + +TEST_F(AppCacheRequestHandlerTest, SubResource_Hit) { + RunTestOnIOThread(&AppCacheRequestHandlerTest::SubResource_Hit); +} + +TEST_F(AppCacheRequestHandlerTest, SubResource_RedirectFallback) { + RunTestOnIOThread(&AppCacheRequestHandlerTest::SubResource_RedirectFallback); +} + +TEST_F(AppCacheRequestHandlerTest, SubResource_NoRedirectFallback) { + RunTestOnIOThread( + &AppCacheRequestHandlerTest::SubResource_NoRedirectFallback); +} + +TEST_F(AppCacheRequestHandlerTest, SubResource_Network) { + RunTestOnIOThread(&AppCacheRequestHandlerTest::SubResource_Network); +} + +TEST_F(AppCacheRequestHandlerTest, DestroyedHost) { + RunTestOnIOThread(&AppCacheRequestHandlerTest::DestroyedHost); +} + +TEST_F(AppCacheRequestHandlerTest, DestroyedHostWithWaitingJob) { + RunTestOnIOThread(&AppCacheRequestHandlerTest::DestroyedHostWithWaitingJob); +} + +TEST_F(AppCacheRequestHandlerTest, UnsupportedScheme) { + RunTestOnIOThread(&AppCacheRequestHandlerTest::UnsupportedScheme); +} + +TEST_F(AppCacheRequestHandlerTest, CanceledRequest) { + RunTestOnIOThread(&AppCacheRequestHandlerTest::CanceledRequest); +} + +} // namespace appcache + diff --git a/webkit/appcache/appcache_response.cc b/webkit/appcache/appcache_response.cc index e827a14..9ae5b29 100644 --- a/webkit/appcache/appcache_response.cc +++ b/webkit/appcache/appcache_response.cc @@ -52,10 +52,10 @@ class WrappedPickleIOBuffer : public net::WrappedIOBuffer { // AppCacheResponseInfo ---------------------------------------------- AppCacheResponseInfo::AppCacheResponseInfo( - AppCacheService* service, int64 response_id, - net::HttpResponseInfo* http_info) - : response_id_(response_id), http_response_info_(http_info), - service_(service) { + AppCacheService* service, const GURL& manifest_url, + int64 response_id, net::HttpResponseInfo* http_info) + : manifest_url_(manifest_url), response_id_(response_id), + http_response_info_(http_info), service_(service) { DCHECK(http_info); DCHECK(response_id != kNoResponseId); service_->storage()->working_set()->AddResponseInfo(this); @@ -222,6 +222,7 @@ void AppCacheResponseWriter::WriteInfo(HttpResponseInfoIOBuffer* info_buf, const bool kTruncated = false; Pickle* pickle = new Pickle; info_buf->http_info->Persist(pickle, kSkipTransientHeaders, kTruncated); + info_buffer_ = info_buf; write_amount_ = static_cast<int>(pickle->size()); buffer_ = new WrappedPickleIOBuffer(pickle); // takes ownership of pickle WriteRaw(kResponseInfoIndex, 0, buffer_, write_amount_); diff --git a/webkit/appcache/appcache_response.h b/webkit/appcache/appcache_response.h index 6b8e09a..419e739 100644 --- a/webkit/appcache/appcache_response.h +++ b/webkit/appcache/appcache_response.h @@ -8,6 +8,7 @@ #include "base/compiler_specific.h" #include "base/ref_counted.h" #include "base/scoped_ptr.h" +#include "googleurl/src/gurl.h" #include "net/base/completion_callback.h" #include "net/http/http_response_info.h" #include "webkit/appcache/appcache_interfaces.h" @@ -30,12 +31,12 @@ class AppCacheResponseInfo : public base::RefCounted<AppCacheResponseInfo> { public: // AppCacheResponseInfo takes ownership of the http_info. - AppCacheResponseInfo(AppCacheService* service, int64 response_id, - net::HttpResponseInfo* http_info); - // TODO(michaeln): should the ctor/dtor be hidden from public view? + AppCacheResponseInfo(AppCacheService* service, const GURL& manifest_url, + int64 response_id, net::HttpResponseInfo* http_info); + // TODO(michaeln): should the ctor be hidden from public view? + const GURL& manifest_url() const { return manifest_url_; } int64 response_id() const { return response_id_; } - const net::HttpResponseInfo* http_response_info() const { return http_response_info_.get(); } @@ -44,6 +45,7 @@ class AppCacheResponseInfo friend class base::RefCounted<AppCacheResponseInfo>; ~AppCacheResponseInfo(); + const GURL manifest_url_; const int64 response_id_; const scoped_ptr<net::HttpResponseInfo> http_response_info_; const AppCacheService* service_; diff --git a/webkit/appcache/appcache_response_unittest.cc b/webkit/appcache/appcache_response_unittest.cc index 939086e..1077b9b 100644 --- a/webkit/appcache/appcache_response_unittest.cc +++ b/webkit/appcache/appcache_response_unittest.cc @@ -12,6 +12,7 @@ #include "webkit/appcache/mock_appcache_service.h" using net::IOBuffer; +using net::WrappedIOBuffer; namespace appcache { @@ -164,16 +165,21 @@ class AppCacheResponseTest : public testing::Test { // Wrappers to call AppCacheResponseReader/Writer Read and Write methods void WriteBasicResponse() { - static const char* kRawHttpHeaders = - "HTTP/1.0 200 OK\r\nContent-Length: 5\r\n\r\n"; - static const char* kRawHttpBody = "Hello"; - WriteResponse(MakeHttpResponseInfo(kRawHttpHeaders), kRawHttpBody); - } - - void WriteResponse(net::HttpResponseInfo* head, const char* body) { + static const char kHttpHeaders[] = + "HTTP/1.0 200 OK\0Content-Length: 5\0\0"; + static const char* kHttpBody = "Hello"; + scoped_refptr<IOBuffer> body = new WrappedIOBuffer(kHttpBody); + std::string raw_headers(kHttpHeaders, arraysize(kHttpHeaders)); + WriteResponse(MakeHttpResponseInfo(raw_headers), body, strlen(kHttpBody)); + } + + void WriteResponse(net::HttpResponseInfo* head, + IOBuffer* body, int body_len) { + DCHECK(body); + scoped_refptr<IOBuffer> body_ref(body); PushNextTask(method_factory_.NewRunnableMethod( &AppCacheResponseTest::WriteResponseBody, - new net::WrappedIOBuffer(body), strlen(body))); + body_ref, body_len)); WriteResponseHead(head); } @@ -238,7 +244,7 @@ class AppCacheResponseTest : public testing::Test { // Helpers to work with HttpResponseInfo objects - net::HttpResponseInfo* MakeHttpResponseInfo(const char* raw_headers) { + net::HttpResponseInfo* MakeHttpResponseInfo(const std::string& raw_headers) { net::HttpResponseInfo* info = new net::HttpResponseInfo; info->request_time = base::Time::Now(); info->response_time = base::Time::Now(); diff --git a/webkit/appcache/appcache_storage.h b/webkit/appcache/appcache_storage.h index 608ea34..67717a5 100644 --- a/webkit/appcache/appcache_storage.h +++ b/webkit/appcache/appcache_storage.h @@ -55,6 +55,7 @@ class AppCacheStorage { // containing cache and group are also returned. virtual void OnMainResponseFound( const GURL& url, const AppCacheEntry& entry, + const AppCacheEntry& fallback_entry, int64 cache_id, const GURL& mainfest_url) {} }; @@ -99,6 +100,13 @@ class AppCacheStorage { virtual void FindResponseForMainRequest( const GURL& url, Delegate* delegate) = 0; + // Performs an immediate lookup of the in-memory cache to + // identify a response for a sub resource request. + virtual void FindResponseForSubRequest( + AppCache* cache, const GURL& url, + AppCacheEntry* found_entry, AppCacheEntry* found_fallback_entry, + bool* found_network_namespace) = 0; + // Immediately updates in-memory storage, if the cache is in memory, // and schedules a task to update persistent storage. If the cache is // already scheduled to be loaded, upon loading completion the entry @@ -201,15 +209,15 @@ class AppCacheStorage { class ResponseInfoLoadTask { public: ResponseInfoLoadTask(const GURL& manifest_url, int64 response_id, - AppCacheStorage* storage) : - storage_(storage), - manifest_url_(manifest_url), - response_id_(response_id), - info_buffer_(new HttpResponseInfoIOBuffer), - ALLOW_THIS_IN_INITIALIZER_LIST(read_callback_( - this, &ResponseInfoLoadTask::OnReadComplete)) { - ALLOW_THIS_IN_INITIALIZER_LIST(storage_->pending_info_loads_.insert( - PendingResponseInfoLoads::value_type(response_id, this))); + AppCacheStorage* storage) + : storage_(storage), + manifest_url_(manifest_url), + response_id_(response_id), + info_buffer_(new HttpResponseInfoIOBuffer), + ALLOW_THIS_IN_INITIALIZER_LIST(read_callback_( + this, &ResponseInfoLoadTask::OnReadComplete)) { + storage_->pending_info_loads_.insert( + PendingResponseInfoLoads::value_type(response_id, this)); } int64 response_id() const { return response_id_; } @@ -233,7 +241,7 @@ class AppCacheStorage { scoped_refptr<AppCacheResponseInfo> info; if (result >= 0) { info = new AppCacheResponseInfo( - storage_->service(), reader_->response_id(), + storage_->service(), manifest_url_, response_id_, info_buffer_->http_info.release()); } FOR_EACH_DELEGATE( diff --git a/webkit/appcache/appcache_storage_unittest.cc b/webkit/appcache/appcache_storage_unittest.cc index 7025e75..506dc04 100644 --- a/webkit/appcache/appcache_storage_unittest.cc +++ b/webkit/appcache/appcache_storage_unittest.cc @@ -55,7 +55,8 @@ TEST_F(AppCacheStorageTest, AddRemoveGroup) { TEST_F(AppCacheStorageTest, AddRemoveResponseInfo) { MockAppCacheService service; scoped_refptr<AppCacheResponseInfo> info = - new AppCacheResponseInfo(&service, 111, new net::HttpResponseInfo); + new AppCacheResponseInfo(&service, GURL(), + 111, new net::HttpResponseInfo); EXPECT_EQ(info.get(), service.storage()->working_set()->GetResponseInfo(111)); diff --git a/webkit/appcache/appcache_url_request_job.cc b/webkit/appcache/appcache_url_request_job.cc new file mode 100644 index 0000000..43ac20d --- /dev/null +++ b/webkit/appcache/appcache_url_request_job.cc @@ -0,0 +1,197 @@ +// Copyright (c) 2009 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/appcache/appcache_url_request_job.h" + +#include "base/message_loop.h" +#include "net/url_request/url_request_status.h" + +namespace appcache { + +AppCacheURLRequestJob::AppCacheURLRequestJob( + URLRequest* request, AppCacheStorage* storage) + : URLRequestJob(request), storage_(storage), + has_been_started_(false), has_been_killed_(false), + delivery_type_(AWAITING_DELIVERY_ORDERS), + cache_id_(kNoCacheId), + ALLOW_THIS_IN_INITIALIZER_LIST(read_callback_( + this, &AppCacheURLRequestJob::OnReadComplete)) { + DCHECK(storage_); +} + +AppCacheURLRequestJob::~AppCacheURLRequestJob() { + if (storage_) + storage_->CancelDelegateCallbacks(this); +} + +void AppCacheURLRequestJob::DeliverAppCachedResponse( + const GURL& manifest_url, int64 cache_id, const AppCacheEntry& entry) { + DCHECK(!has_delivery_orders()); + DCHECK(entry.has_response_id()); + delivery_type_ = APPCACHED_DELIVERY; + manifest_url_ = manifest_url; + cache_id_ = cache_id; + entry_ = entry; + MaybeBeginDelivery(); +} + +void AppCacheURLRequestJob::DeliverNetworkResponse() { + DCHECK(!has_delivery_orders()); + delivery_type_ = NETWORK_DELIVERY; + storage_ = NULL; // not needed + MaybeBeginDelivery(); +} + +void AppCacheURLRequestJob::DeliverErrorResponse() { + DCHECK(!has_delivery_orders()); + delivery_type_ = ERROR_DELIVERY; + storage_ = NULL; // not needed + MaybeBeginDelivery(); +} + +void AppCacheURLRequestJob::MaybeBeginDelivery() { + if (has_been_started() && has_delivery_orders()) { + // Start asynchronously so that all error reporting and data + // callbacks happen as they would for network requests. + MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( + this, &AppCacheURLRequestJob::BeginDelivery)); + } +} + +void AppCacheURLRequestJob::BeginDelivery() { + DCHECK(has_delivery_orders() && has_been_started()); + + if (has_been_killed()) + return; + + switch (delivery_type_) { + case NETWORK_DELIVERY: + // To fallthru to the network, we restart the request which will + // cause a new job to be created to retrieve the resource from the + // network. Our caller is responsible for arranging to not re-intercept + // the same request. + NotifyRestartRequired(); + break; + + case ERROR_DELIVERY: + NotifyStartError( + URLRequestStatus(URLRequestStatus::FAILED, net::ERR_FAILED)); + break; + + case APPCACHED_DELIVERY: + storage_->LoadResponseInfo(manifest_url_, entry_.response_id(), this); + break; + + default: + NOTREACHED(); + break; + } +} + +void AppCacheURLRequestJob::OnResponseInfoLoaded( + AppCacheResponseInfo* response_info, int64 response_id) { + DCHECK(is_delivering_appcache_response()); + scoped_refptr<AppCacheURLRequestJob> protect(this); + if (response_info) { + info_ = response_info; + reader_.reset( + storage_->CreateResponseReader(manifest_url_, entry_.response_id())); + NotifyHeadersComplete(); + } else { + NotifyStartError( + URLRequestStatus(URLRequestStatus::FAILED, net::ERR_FAILED)); + } + storage_ = NULL; // no longer needed +} + +void AppCacheURLRequestJob::OnReadComplete(int result) { + DCHECK(is_delivering_appcache_response()); + if (result == 0) + NotifyDone(URLRequestStatus()); + else if (result < 0) + NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, result)); + else + SetStatus(URLRequestStatus()); // Clear the IO_PENDING status + + NotifyReadComplete(result); +} + +// URLRequestJob overrides ------------------------------------------------ + +void AppCacheURLRequestJob::Start() { + DCHECK(!has_been_started()); + has_been_started_ = true; + MaybeBeginDelivery(); +} + +void AppCacheURLRequestJob::Kill() { + if (!has_been_killed_) { + has_been_killed_ = true; + reader_.reset(); + if (storage_) { + storage_->CancelDelegateCallbacks(this); + storage_ = NULL; + } + URLRequestJob::Kill(); + } +} + +net::LoadState AppCacheURLRequestJob::GetLoadState() const { + if (!has_been_started()) + return net::LOAD_STATE_IDLE; + if (!has_delivery_orders()) + return net::LOAD_STATE_WAITING_FOR_CACHE; + if (delivery_type_ != APPCACHED_DELIVERY) + return net::LOAD_STATE_IDLE; + if (!info_.get()) + return net::LOAD_STATE_WAITING_FOR_CACHE; + if (reader_.get() && reader_->IsReadPending()) + return net::LOAD_STATE_READING_RESPONSE; + return net::LOAD_STATE_IDLE; +} + +bool AppCacheURLRequestJob::GetMimeType(std::string* mime_type) const { + if (!http_info()) + return false; + return http_info()->headers->GetMimeType(mime_type); +} + +bool AppCacheURLRequestJob::GetCharset(std::string* charset) { + if (!http_info()) + return false; + return http_info()->headers->GetCharset(charset); +} + +void AppCacheURLRequestJob::GetResponseInfo(net::HttpResponseInfo* info) { + if (!http_info()) + return; + *info = *http_info(); +} + +int AppCacheURLRequestJob::GetResponseCode() const { + if (!http_info()) + return -1; + return http_info()->headers->response_code(); +} + +bool AppCacheURLRequestJob::GetMoreData() { + // TODO(michaeln): This method is in the URLRequestJob interface, + // but its never called by anything, it can be removed from the + // base class. + return false; +} + +bool AppCacheURLRequestJob::ReadRawData(net::IOBuffer* buf, int buf_size, + int *bytes_read) { + DCHECK(is_delivering_appcache_response()); + DCHECK_NE(buf_size, 0); + DCHECK(bytes_read); + DCHECK(!reader_->IsReadPending()); + reader_->ReadData(buf, buf_size, &read_callback_); + SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0)); + return false; +} + +} // namespace appcache + diff --git a/webkit/appcache/appcache_url_request_job.h b/webkit/appcache/appcache_url_request_job.h new file mode 100644 index 0000000..b987327 --- /dev/null +++ b/webkit/appcache/appcache_url_request_job.h @@ -0,0 +1,142 @@ +// Copyright (c) 2009 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. + +#ifndef WEBKIT_APPCACHE_APPCACHE_URL_REQUEST_JOB_H_ +#define WEBKIT_APPCACHE_APPCACHE_URL_REQUEST_JOB_H_ + +#include <string> + +#include "net/url_request/url_request_job.h" +#include "webkit/appcache/appcache_entry.h" +#include "webkit/appcache/appcache_response.h" +#include "webkit/appcache/appcache_storage.h" + +namespace appcache { + +// A URLRequestJob derivative that knows how to return a response stored +// in the appcache. +class AppCacheURLRequestJob : public URLRequestJob, + public AppCacheStorage::Delegate { + public: + explicit AppCacheURLRequestJob(URLRequest* request, + AppCacheStorage* storage); + virtual ~AppCacheURLRequestJob(); + + // Informs the job of what response it should deliver. Only one of these + // methods should be called, and only once per job. A job will sit idle and + // wait indefinitely until one of the deliver methods is called. + void DeliverAppCachedResponse(const GURL& manifest_url, int64 cache_id, + const AppCacheEntry& entry); + void DeliverNetworkResponse(); + void DeliverErrorResponse(); + + bool is_waiting() const { + return delivery_type_ == AWAITING_DELIVERY_ORDERS; + } + + bool is_delivering_appcache_response() const { + return delivery_type_ == APPCACHED_DELIVERY; + } + + bool is_delivering_network_response() const { + return delivery_type_ == NETWORK_DELIVERY; + } + + bool is_delivering_error_response() const { + return delivery_type_ == ERROR_DELIVERY; + } + + // Accessors for the info about the appcached response, if any, + // that this job has been instructed to deliver. These are only + // valid to call if is_delivering_appcache_response. + const GURL& manifest_url() { return manifest_url_; } + int64 cache_id() { return cache_id_; } + const AppCacheEntry& entry() { return entry_; } + + // URLRequestJob's Kill method is made public so the users of this + // class in the appcache namespace can call it. + virtual void Kill(); + + // Returns true if the job has been started by the net library. + bool has_been_started() const { + return has_been_started_; + } + + // Returns true if the job has been killed. + bool has_been_killed() const { + return has_been_killed_; + } + + private: + friend class AppCacheRequestHandlerTest; + friend class AppCacheURLRequestJobTest; + + enum DeliveryType { + AWAITING_DELIVERY_ORDERS, + APPCACHED_DELIVERY, + NETWORK_DELIVERY, + ERROR_DELIVERY + }; + + // Returns true if one of the Deliver methods has been called. + bool has_delivery_orders() const { + return !is_waiting(); + } + + void MaybeBeginDelivery(); + void BeginDelivery(); + + // AppCacheStorage::Delegate methods + virtual void OnResponseInfoLoaded( + AppCacheResponseInfo* response_info, int64 response_id); + + const net::HttpResponseInfo* http_info() const { + return info_.get() ? info_->http_response_info() : NULL; + } + + // AppCacheResponseReader completion callback + void OnReadComplete(int result); + + // URLRequestJob methods, see url_request_job.h for doc comments + virtual void Start(); + virtual net::LoadState GetLoadState() const; + virtual bool GetCharset(std::string* charset); + virtual void GetResponseInfo(net::HttpResponseInfo* info); + virtual bool GetMoreData(); + virtual bool ReadRawData(net::IOBuffer* buf, int buf_size, int *bytes_read); + + // Sets extra request headers for Job types that support request headers. + // TODO(michaeln): support for range-requests + virtual void SetExtraRequestHeaders(const std::string& headers) {} + + // TODO(michaeln): does this apply to our cached responses? + // The payload we store should have been fully decoded prior to + // us storing it. Maybe we should strip the content-encoding headers + // when writing the response to the cache. + // virtual bool GetContentEncodings( + // std::vector<Filter::FilterType>* encoding_types); + + // FilterContext methods + virtual bool GetMimeType(std::string* mime_type) const; + virtual int GetResponseCode() const; + virtual bool IsCachedContent() const { + return is_delivering_appcache_response(); + } + + AppCacheStorage* storage_; + bool has_been_started_; + bool has_been_killed_; + DeliveryType delivery_type_; + GURL manifest_url_; + int64 cache_id_; + AppCacheEntry entry_; + scoped_refptr<AppCacheResponseInfo> info_; + scoped_ptr<AppCacheResponseReader> reader_; + net::CompletionCallbackImpl<AppCacheURLRequestJob> read_callback_; +}; + +} // namespace appcache + +#endif // WEBKIT_APPCACHE_APPCACHE_REQUEST_HANDLER_H_ + diff --git a/webkit/appcache/appcache_url_request_job_unittest.cc b/webkit/appcache/appcache_url_request_job_unittest.cc new file mode 100644 index 0000000..b7b452f --- /dev/null +++ b/webkit/appcache/appcache_url_request_job_unittest.cc @@ -0,0 +1,745 @@ +// Copyright (c) 2009 The Chromium Authos. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/compiler_specific.h" +#include "base/pickle.h" +#include "base/thread.h" +#include "base/waitable_event.h" +#include "net/url_request/url_request.h" +#include "net/url_request/url_request_error_job.h" +#include "net/base/io_buffer.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/appcache/appcache_response.h" +#include "webkit/appcache/appcache_url_request_job.h" +#include "webkit/appcache/mock_appcache_service.h" + +using net::IOBuffer; +using net::WrappedIOBuffer; + +namespace appcache { + +static const char kHttpBasicHeaders[] = + "HTTP/1.0 200 OK\0Content-Length: 5\0\0"; +static const char kHttpBasicBody[] = "Hello"; + +static const int kNumBlocks = 4; +static const int kBlockSize = 1024; +static const int kNoSuchResponseId = 123; + +class AppCacheURLRequestJobTest : public testing::Test { + public: + + // Test Harness ------------------------------------------------------------- + // TODO(michaeln): share this test harness with AppCacheResponseTest + + class MockStorageDelegate : public AppCacheStorage::Delegate { + public: + explicit MockStorageDelegate(AppCacheURLRequestJobTest* test) + : loaded_info_id_(0), test_(test) { + } + + virtual void OnResponseInfoLoaded(AppCacheResponseInfo* info, + int64 response_id) { + loaded_info_ = info; + loaded_info_id_ = response_id; + test_->ScheduleNextTask(); + } + + scoped_refptr<AppCacheResponseInfo> loaded_info_; + int64 loaded_info_id_; + AppCacheURLRequestJobTest* test_; + }; + + class MockURLRequestDelegate : public URLRequest::Delegate { + public: + explicit MockURLRequestDelegate(AppCacheURLRequestJobTest* test) + : test_(test), + received_data_(new net::IOBuffer(kNumBlocks * kBlockSize)), + did_receive_headers_(false), amount_received_(0), + kill_after_amount_received_(0), kill_with_io_pending_(false) { + } + + virtual void OnResponseStarted(URLRequest* request) { + amount_received_ = 0; + did_receive_headers_ = false; + if (request->status().is_success()) { + EXPECT_TRUE(request->response_headers()); + did_receive_headers_ = true; + received_info_ = request->response_info(); + ReadSome(request); + } else { + RequestComplete(); + } + } + + virtual void OnReadCompleted(URLRequest* request, int bytes_read) { + if (bytes_read > 0) { + amount_received_ += bytes_read; + + if (kill_after_amount_received_ && !kill_with_io_pending_) { + if (amount_received_ >= kill_after_amount_received_) { + request->Cancel(); + return; + } + } + + ReadSome(request); + + if (kill_after_amount_received_ && kill_with_io_pending_) { + if (amount_received_ >= kill_after_amount_received_) { + request->Cancel(); + return; + } + } + } else { + RequestComplete(); + } + } + + void ReadSome(URLRequest* request) { + DCHECK(amount_received_ + kBlockSize <= kNumBlocks * kBlockSize); + scoped_refptr<IOBuffer> wrapped_buffer = + new net::WrappedIOBuffer(received_data_->data() + amount_received_); + int bytes_read = 0; + EXPECT_FALSE(request->Read(wrapped_buffer, kBlockSize, &bytes_read)); + EXPECT_EQ(0, bytes_read); + } + + void RequestComplete() { + test_->ScheduleNextTask(); + } + + AppCacheURLRequestJobTest* test_; + net::HttpResponseInfo received_info_; + scoped_refptr<net::IOBuffer> received_data_; + bool did_receive_headers_; + int amount_received_; + int kill_after_amount_received_; + bool kill_with_io_pending_; + }; + + static URLRequestJob* MockHttpJobFactory(URLRequest* request, + const std::string& scheme) { + if (mock_factory_job_) { + URLRequestJob* temp = mock_factory_job_; + mock_factory_job_ = NULL; + return temp; + } else { + return new URLRequestErrorJob(request, net::ERR_INTERNET_DISCONNECTED); + } + } + + // Helper class run a test on our io_thread. The io_thread + // is spun up once and reused for all tests. + template <class Method> + class WrapperTask : public Task { + public: + WrapperTask(AppCacheURLRequestJobTest* test, Method method) + : test_(test), method_(method) { + } + + virtual void Run() { + test_->SetUpTest(); + (test_->*method_)(); + } + + private: + AppCacheURLRequestJobTest* test_; + Method method_; + }; + + static void SetUpTestCase() { + io_thread_.reset(new base::Thread("AppCacheURLRequestJobTest Thread")); + base::Thread::Options options(MessageLoop::TYPE_IO, 0); + io_thread_->StartWithOptions(options); + } + + static void TearDownTestCase() { + io_thread_.reset(NULL); + } + + AppCacheURLRequestJobTest() + : ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)), + ALLOW_THIS_IN_INITIALIZER_LIST(read_callback_( + this, &AppCacheURLRequestJobTest::OnReadComplete)), + ALLOW_THIS_IN_INITIALIZER_LIST(read_info_callback_( + this, &AppCacheURLRequestJobTest::OnReadInfoComplete)), + ALLOW_THIS_IN_INITIALIZER_LIST(write_callback_( + this, &AppCacheURLRequestJobTest::OnWriteComplete)), + ALLOW_THIS_IN_INITIALIZER_LIST(write_info_callback_( + this, &AppCacheURLRequestJobTest::OnWriteInfoComplete)) { + } + + template <class Method> + void RunTestOnIOThread(Method method) { + test_finished_event_ .reset(new base::WaitableEvent(false, false)); + io_thread_->message_loop()->PostTask( + FROM_HERE, new WrapperTask<Method>(this, method)); + test_finished_event_->Wait(); + } + + void SetUpTest() { + DCHECK(MessageLoop::current() == io_thread_->message_loop()); + DCHECK(task_stack_.empty()); + orig_http_factory_ = URLRequest::RegisterProtocolFactory( + "http", MockHttpJobFactory); + url_request_delegate_.reset(new MockURLRequestDelegate(this)); + storage_delegate_.reset(new MockStorageDelegate(this)); + service_.reset(new MockAppCacheService()); + expected_read_result_ = 0; + expected_write_result_ = 0; + written_response_id_ = 0; + should_delete_reader_in_completion_callback_ = false; + should_delete_writer_in_completion_callback_ = false; + reader_deletion_count_down_ = 0; + writer_deletion_count_down_ = 0; + read_callback_was_called_ = false; + write_callback_was_called_ = false; + } + + void TearDownTest() { + DCHECK(MessageLoop::current() == io_thread_->message_loop()); + URLRequest::RegisterProtocolFactory("http", orig_http_factory_); + orig_http_factory_ = NULL; + request_.reset(); + url_request_delegate_.reset(); + DCHECK(!mock_factory_job_); + + while (!task_stack_.empty()) { + delete task_stack_.top().first; + task_stack_.pop(); + } + reader_.reset(); + read_buffer_ = NULL; + read_info_buffer_ = NULL; + writer_.reset(); + write_buffer_ = NULL; + write_info_buffer_ = NULL; + storage_delegate_.reset(); + service_.reset(); + } + + void TestFinished() { + // We unwind the stack prior to finishing up to let stack + // based objects get deleted. + DCHECK(MessageLoop::current() == io_thread_->message_loop()); + MessageLoop::current()->PostTask(FROM_HERE, + method_factory_.NewRunnableMethod( + &AppCacheURLRequestJobTest::TestFinishedUnwound)); + } + + void TestFinishedUnwound() { + TearDownTest(); + test_finished_event_->Signal(); + } + + void PushNextTask(Task* task) { + task_stack_.push(std::pair<Task*, bool>(task, false)); + } + + void PushNextTaskAsImmediate(Task* task) { + task_stack_.push(std::pair<Task*, bool>(task, true)); + } + + void ScheduleNextTask() { + DCHECK(MessageLoop::current() == io_thread_->message_loop()); + if (task_stack_.empty()) { + TestFinished(); + return; + } + scoped_ptr<Task> task(task_stack_.top().first); + bool immediate = task_stack_.top().second; + task_stack_.pop(); + if (immediate) + task->Run(); + else + MessageLoop::current()->PostTask(FROM_HERE, task.release()); + } + + // Wrappers to call AppCacheResponseReader/Writer Read and Write methods + + void WriteBasicResponse() { + scoped_refptr<IOBuffer> body = new WrappedIOBuffer(kHttpBasicBody); + std::string raw_headers(kHttpBasicHeaders, arraysize(kHttpBasicHeaders)); + WriteResponse(MakeHttpResponseInfo(raw_headers), body, + strlen(kHttpBasicBody)); + } + + void WriteResponse(net::HttpResponseInfo* head, + IOBuffer* body, int body_len) { + DCHECK(body); + scoped_refptr<IOBuffer> body_ref(body); + PushNextTask(method_factory_.NewRunnableMethod( + &AppCacheURLRequestJobTest::WriteResponseBody, + body_ref, body_len)); + WriteResponseHead(head); + } + + void WriteResponseHead(net::HttpResponseInfo* head) { + EXPECT_FALSE(writer_->IsWritePending()); + expected_write_result_ = GetHttpResponseInfoSize(head); + write_info_buffer_ = new HttpResponseInfoIOBuffer(head); + writer_->WriteInfo(write_info_buffer_, &write_info_callback_); + } + + void WriteResponseBody(scoped_refptr<IOBuffer> io_buffer, int buf_len) { + EXPECT_FALSE(writer_->IsWritePending()); + write_buffer_ = io_buffer; + expected_write_result_ = buf_len; + writer_->WriteData( + write_buffer_, buf_len, &write_callback_); + } + + void ReadResponseBody(scoped_refptr<IOBuffer> io_buffer, int buf_len) { + EXPECT_FALSE(reader_->IsReadPending()); + read_buffer_ = io_buffer; + expected_read_result_ = buf_len; + reader_->ReadData( + read_buffer_, buf_len, &read_callback_); + } + + // AppCacheResponseReader / Writer completion callbacks + + void OnWriteInfoComplete(int result) { + EXPECT_FALSE(writer_->IsWritePending()); + EXPECT_EQ(expected_write_result_, result); + ScheduleNextTask(); + } + + void OnWriteComplete(int result) { + EXPECT_FALSE(writer_->IsWritePending()); + write_callback_was_called_ = true; + EXPECT_EQ(expected_write_result_, result); + if (should_delete_writer_in_completion_callback_ && + --writer_deletion_count_down_ == 0) { + writer_.reset(); + } + ScheduleNextTask(); + } + + void OnReadInfoComplete(int result) { + EXPECT_FALSE(reader_->IsReadPending()); + EXPECT_EQ(expected_read_result_, result); + ScheduleNextTask(); + } + + void OnReadComplete(int result) { + EXPECT_FALSE(reader_->IsReadPending()); + read_callback_was_called_ = true; + EXPECT_EQ(expected_read_result_, result); + if (should_delete_reader_in_completion_callback_ && + --reader_deletion_count_down_ == 0) { + reader_.reset(); + } + ScheduleNextTask(); + } + + // Helpers to work with HttpResponseInfo objects + + net::HttpResponseInfo* MakeHttpResponseInfo(const std::string& raw_headers) { + net::HttpResponseInfo* info = new net::HttpResponseInfo; + info->request_time = base::Time::Now(); + info->response_time = base::Time::Now(); + info->was_cached = false; + info->headers = new net::HttpResponseHeaders(raw_headers); + return info; + } + + int GetHttpResponseInfoSize(const net::HttpResponseInfo* info) { + Pickle pickle; + return PickleHttpResonseInfo(&pickle, info); + } + + bool CompareHttpResponseInfos(const net::HttpResponseInfo* info1, + const net::HttpResponseInfo* info2) { + Pickle pickle1; + Pickle pickle2; + PickleHttpResonseInfo(&pickle1, info1); + PickleHttpResonseInfo(&pickle2, info2); + return (pickle1.size() == pickle2.size()) && + (0 == memcmp(pickle1.data(), pickle2.data(), pickle1.size())); + } + + int PickleHttpResonseInfo(Pickle* pickle, const net::HttpResponseInfo* info) { + const bool kSkipTransientHeaders = true; + const bool kTruncated = false; + info->Persist(pickle, kSkipTransientHeaders, kTruncated); + return pickle->size(); + } + + // Helpers to fill and verify blocks of memory with a value + + void FillData(char value, char* data, int data_len) { + memset(data, value, data_len); + } + + bool CheckData(char value, const char* data, int data_len) { + for (int i = 0; i < data_len; ++i, ++data) { + if (*data != value) + return false; + } + return true; + } + + // Individual Tests --------------------------------------------------------- + // Some of the individual tests involve multiple async steps. Each test + // is delineated with a section header. + + // Basic ------------------------------------------------------------------- + void Basic() { + AppCacheStorage* storage = service_->storage(); + URLRequest request(GURL("http://blah/"), NULL); + scoped_refptr<AppCacheURLRequestJob> job; + + // Create an instance and see that it looks as expected. + + job = new AppCacheURLRequestJob(&request, storage); + EXPECT_TRUE(job->is_waiting()); + EXPECT_FALSE(job->is_delivering_appcache_response()); + EXPECT_FALSE(job->is_delivering_network_response()); + EXPECT_FALSE(job->is_delivering_error_response()); + EXPECT_FALSE(job->has_been_started()); + EXPECT_FALSE(job->has_been_killed()); + EXPECT_EQ(GURL::EmptyGURL(), job->manifest_url()); + EXPECT_EQ(kNoCacheId, job->cache_id()); + EXPECT_FALSE(job->entry().has_response_id()); + + TestFinished(); + } + + // DeliveryOrders ----------------------------------------------------- + void DeliveryOrders() { + AppCacheStorage* storage = service_->storage(); + URLRequest request(GURL("http://blah/"), NULL); + scoped_refptr<AppCacheURLRequestJob> job; + + // Create an instance, give it a delivery order and see that + // it looks as expected. + + job = new AppCacheURLRequestJob(&request, storage); + job->DeliverErrorResponse(); + EXPECT_TRUE(job->is_delivering_error_response()); + EXPECT_FALSE(job->has_been_started()); + + job = new AppCacheURLRequestJob(&request, storage); + job->DeliverNetworkResponse(); + EXPECT_TRUE(job->is_delivering_network_response()); + EXPECT_FALSE(job->has_been_started()); + + job = new AppCacheURLRequestJob(&request, storage); + const GURL kManifestUrl("http://blah/"); + const int64 kCacheId(1); + const AppCacheEntry kEntry(AppCacheEntry::EXPLICIT, 1); + job->DeliverAppCachedResponse(kManifestUrl, kCacheId, kEntry); + EXPECT_FALSE(job->is_waiting()); + EXPECT_TRUE(job->is_delivering_appcache_response()); + EXPECT_FALSE(job->has_been_started()); + EXPECT_EQ(kManifestUrl, job->manifest_url()); + EXPECT_EQ(kCacheId, job->cache_id()); + EXPECT_EQ(kEntry.types(), job->entry().types()); + EXPECT_EQ(kEntry.response_id(), job->entry().response_id()); + + TestFinished(); + } + + // DeliverNetworkResponse -------------------------------------------------- + + void DeliverNetworkResponse() { + // This test has async steps. + PushNextTask(method_factory_.NewRunnableMethod( + &AppCacheURLRequestJobTest::VerifyDeliverNetworkResponse)); + + AppCacheStorage* storage = service_->storage(); + request_.reset( + new URLRequest(GURL("http://blah/"), url_request_delegate_.get())); + + // Setup to create an AppCacheURLRequestJob with orders to deliver + // a network response. + mock_factory_job_ = new AppCacheURLRequestJob(request_.get(), storage); + mock_factory_job_->DeliverNetworkResponse(); + EXPECT_TRUE(mock_factory_job_->is_delivering_network_response()); + EXPECT_FALSE(mock_factory_job_->has_been_started()); + + // Start the request. + request_->Start(); + + // The job should have been picked up. + EXPECT_FALSE(mock_factory_job_); + // Completion is async. + } + + void VerifyDeliverNetworkResponse() { + EXPECT_EQ(request_->status().os_error(), + net::ERR_INTERNET_DISCONNECTED); + TestFinished(); + } + + // DeliverErrorResponse -------------------------------------------------- + + void DeliverErrorResponse() { + // This test has async steps. + PushNextTask(method_factory_.NewRunnableMethod( + &AppCacheURLRequestJobTest::VerifyDeliverErrorResponse)); + + AppCacheStorage* storage = service_->storage(); + request_.reset( + new URLRequest(GURL("http://blah/"), url_request_delegate_.get())); + + // Setup to create an AppCacheURLRequestJob with orders to deliver + // a network response. + mock_factory_job_ = new AppCacheURLRequestJob(request_.get(), storage); + mock_factory_job_->DeliverErrorResponse(); + EXPECT_TRUE(mock_factory_job_->is_delivering_error_response()); + EXPECT_FALSE(mock_factory_job_->has_been_started()); + + // Start the request. + request_->Start(); + + // The job should have been picked up. + EXPECT_FALSE(mock_factory_job_); + // Completion is async. + } + + void VerifyDeliverErrorResponse() { + EXPECT_EQ(request_->status().os_error(), net::ERR_FAILED); + TestFinished(); + } + + // DeliverSmallAppCachedResponse -------------------------------------- + // "Small" being small enough to read completely in a single + // request->Read call. + + void DeliverSmallAppCachedResponse() { + // This test has several async steps. + // 1. Write a small response to response storage. + // 2. Use URLRequest to retrieve it. + // 3. Verify we received what we expected to receive. + + PushNextTask(method_factory_.NewRunnableMethod( + &AppCacheURLRequestJobTest::VerifyDeliverSmallAppCachedResponse)); + PushNextTask(method_factory_.NewRunnableMethod( + &AppCacheURLRequestJobTest::RequestAppCachedResource, false)); + + writer_.reset(service_->storage()->CreateResponseWriter(GURL())); + written_response_id_ = writer_->response_id(); + WriteBasicResponse(); + // Continues async + } + + void RequestAppCachedResource(bool start_after_delivery_orders) { + AppCacheStorage* storage = service_->storage(); + request_.reset( + new URLRequest(GURL("http://blah/"), url_request_delegate_.get())); + + // Setup to create an AppCacheURLRequestJob with orders to deliver + // a network response. + scoped_refptr<AppCacheURLRequestJob> job( + new AppCacheURLRequestJob(request_.get(), storage)); + + if (start_after_delivery_orders) { + job->DeliverAppCachedResponse( + GURL(), 111, + AppCacheEntry(AppCacheEntry::EXPLICIT, written_response_id_)); + EXPECT_TRUE(job->is_delivering_appcache_response()); + } + + // Start the request. + EXPECT_FALSE(job->has_been_started()); + mock_factory_job_ = job; + request_->Start(); + EXPECT_FALSE(mock_factory_job_); + EXPECT_TRUE(job->has_been_started()); + + if (!start_after_delivery_orders) { + job->DeliverAppCachedResponse( + GURL(), 111, + AppCacheEntry(AppCacheEntry::EXPLICIT, written_response_id_)); + EXPECT_TRUE(job->is_delivering_appcache_response()); + } + + // Completion is async. + } + + void VerifyDeliverSmallAppCachedResponse() { + EXPECT_TRUE(request_->status().is_success()); + EXPECT_TRUE(CompareHttpResponseInfos( + write_info_buffer_->http_info.get(), + &url_request_delegate_->received_info_)); + EXPECT_EQ(5, url_request_delegate_->amount_received_); + EXPECT_EQ(0, memcmp(kHttpBasicBody, + url_request_delegate_->received_data_->data(), + strlen(kHttpBasicBody))); + TestFinished(); + } + + // DeliverLargeAppCachedResponse -------------------------------------- + // "Large" enough to require multiple calls to request->Read to complete. + + void DeliverLargeAppCachedResponse() { + // This test has several async steps. + // 1. Write a large response to response storage. + // 2. Use URLRequest to retrieve it. + // 3. Verify we received what we expected to receive. + + PushNextTask(method_factory_.NewRunnableMethod( + &AppCacheURLRequestJobTest::VerifyDeliverLargeAppCachedResponse)); + PushNextTask(method_factory_.NewRunnableMethod( + &AppCacheURLRequestJobTest::RequestAppCachedResource, true)); + + writer_.reset(service_->storage()->CreateResponseWriter(GURL())); + written_response_id_ = writer_->response_id(); + WriteLargeResponse(); + // Continues async + } + + void WriteLargeResponse() { + // 3, 1k blocks + static const char kHttpHeaders[] = + "HTTP/1.0 200 OK\0Content-Length: 3072\0\0"; + scoped_refptr<IOBuffer> body = new IOBuffer(kBlockSize * 3); + char* p = body->data(); + for (int i = 0; i < 3; ++i, p += kBlockSize) + FillData(i + 1, p, kBlockSize); + std::string raw_headers(kHttpHeaders, arraysize(kHttpHeaders)); + WriteResponse(MakeHttpResponseInfo(raw_headers), body, kBlockSize * 3); + } + + void VerifyDeliverLargeAppCachedResponse() { + EXPECT_TRUE(request_->status().is_success()); + EXPECT_TRUE(CompareHttpResponseInfos( + write_info_buffer_->http_info.get(), + &url_request_delegate_->received_info_)); + EXPECT_EQ(3072, url_request_delegate_->amount_received_); + char* p = url_request_delegate_->received_data_->data(); + for (int i = 0; i < 3; ++i, p += kBlockSize) + EXPECT_TRUE(CheckData(i + 1, p, kBlockSize)); + TestFinished(); + } + + // CancelRequest -------------------------------------- + + void CancelRequest() { + // This test has several async steps. + // 1. Write a large response to response storage. + // 2. Use URLRequest to retrieve it. + // 3. Cancel the request after data starts coming in. + + PushNextTask(method_factory_.NewRunnableMethod( + &AppCacheURLRequestJobTest::VerifyCancel)); + PushNextTask(method_factory_.NewRunnableMethod( + &AppCacheURLRequestJobTest::RequestAppCachedResource, true)); + + writer_.reset(service_->storage()->CreateResponseWriter(GURL())); + written_response_id_ = writer_->response_id(); + WriteLargeResponse(); + + url_request_delegate_->kill_after_amount_received_ = kBlockSize; + url_request_delegate_->kill_with_io_pending_ = false; + // Continues async + } + + void VerifyCancel() { + EXPECT_EQ(URLRequestStatus::CANCELED, + request_->status().status()); + TestFinished(); + } + + // CancelRequestWithIOPending -------------------------------------- + + void CancelRequestWithIOPending() { + // This test has several async steps. + // 1. Write a large response to response storage. + // 2. Use URLRequest to retrieve it. + // 3. Cancel the request after data starts coming in. + + PushNextTask(method_factory_.NewRunnableMethod( + &AppCacheURLRequestJobTest::VerifyCancel)); + PushNextTask(method_factory_.NewRunnableMethod( + &AppCacheURLRequestJobTest::RequestAppCachedResource, true)); + + writer_.reset(service_->storage()->CreateResponseWriter(GURL())); + written_response_id_ = writer_->response_id(); + WriteLargeResponse(); + + url_request_delegate_->kill_after_amount_received_ = kBlockSize; + url_request_delegate_->kill_with_io_pending_ = true; + // Continues async + } + + + // Data members -------------------------------------------------------- + + ScopedRunnableMethodFactory<AppCacheURLRequestJobTest> method_factory_; + scoped_ptr<base::WaitableEvent> test_finished_event_; + scoped_ptr<MockStorageDelegate> storage_delegate_; + scoped_ptr<MockAppCacheService> service_; + std::stack<std::pair<Task*, bool> > task_stack_; + + scoped_ptr<AppCacheResponseReader> reader_; + scoped_refptr<HttpResponseInfoIOBuffer> read_info_buffer_; + scoped_refptr<IOBuffer> read_buffer_; + int expected_read_result_; + net::CompletionCallbackImpl<AppCacheURLRequestJobTest> read_callback_; + net::CompletionCallbackImpl<AppCacheURLRequestJobTest> read_info_callback_; + bool should_delete_reader_in_completion_callback_; + int reader_deletion_count_down_; + bool read_callback_was_called_; + + int64 written_response_id_; + scoped_ptr<AppCacheResponseWriter> writer_; + scoped_refptr<HttpResponseInfoIOBuffer> write_info_buffer_; + scoped_refptr<IOBuffer> write_buffer_; + int expected_write_result_; + net::CompletionCallbackImpl<AppCacheURLRequestJobTest> write_callback_; + net::CompletionCallbackImpl<AppCacheURLRequestJobTest> write_info_callback_; + bool should_delete_writer_in_completion_callback_; + int writer_deletion_count_down_; + bool write_callback_was_called_; + + URLRequest::ProtocolFactory* orig_http_factory_; + scoped_ptr<URLRequest> request_; + scoped_ptr<MockURLRequestDelegate> url_request_delegate_; + + static scoped_ptr<base::Thread> io_thread_; + static AppCacheURLRequestJob* mock_factory_job_; +}; + +// static +scoped_ptr<base::Thread> AppCacheURLRequestJobTest::io_thread_; +AppCacheURLRequestJob* AppCacheURLRequestJobTest::mock_factory_job_ = NULL; + +TEST_F(AppCacheURLRequestJobTest, Basic) { + RunTestOnIOThread(&AppCacheURLRequestJobTest::Basic); +} + +TEST_F(AppCacheURLRequestJobTest, DeliveryOrders) { + RunTestOnIOThread(&AppCacheURLRequestJobTest::DeliveryOrders); +} + +TEST_F(AppCacheURLRequestJobTest, DeliverNetworkResponse) { + RunTestOnIOThread(&AppCacheURLRequestJobTest::DeliverNetworkResponse); +} + +TEST_F(AppCacheURLRequestJobTest, DeliverErrorResponse) { + RunTestOnIOThread(&AppCacheURLRequestJobTest::DeliverErrorResponse); +} + +TEST_F(AppCacheURLRequestJobTest, DeliverSmallAppCachedResponse) { + RunTestOnIOThread(&AppCacheURLRequestJobTest::DeliverSmallAppCachedResponse); +} + +TEST_F(AppCacheURLRequestJobTest, DeliverLargeAppCachedResponse) { + RunTestOnIOThread(&AppCacheURLRequestJobTest::DeliverLargeAppCachedResponse); +} + +TEST_F(AppCacheURLRequestJobTest, CancelRequest) { + RunTestOnIOThread(&AppCacheURLRequestJobTest::CancelRequest); +} + +TEST_F(AppCacheURLRequestJobTest, CancelRequestWithIOPending) { + RunTestOnIOThread(&AppCacheURLRequestJobTest::CancelRequestWithIOPending); +} + +} // namespace appcache + diff --git a/webkit/appcache/mock_appcache_storage.cc b/webkit/appcache/mock_appcache_storage.cc index 34bfbb7..d9c63b0 100644 --- a/webkit/appcache/mock_appcache_storage.cc +++ b/webkit/appcache/mock_appcache_storage.cc @@ -29,7 +29,11 @@ MockAppCacheStorage::MockAppCacheStorage(AppCacheService* service) : AppCacheStorage(service), ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)), simulate_make_group_obsolete_failure_(false), - simulate_store_group_and_newest_cache_failure_(false) { + simulate_store_group_and_newest_cache_failure_(false), + simulate_find_main_resource_(false), + simulate_find_sub_resource_(false), + simulated_found_cache_id_(kNoCacheId), + simulated_found_network_namespace_(false) { last_cache_id_ = 0; last_entry_id_ = 0; last_group_id_ = 0; @@ -88,6 +92,25 @@ void MockAppCacheStorage::FindResponseForMainRequest( url, GetOrCreateDelegateReference(delegate))); } +void MockAppCacheStorage::FindResponseForSubRequest( + AppCache* cache, const GURL& url, + AppCacheEntry* found_entry, AppCacheEntry* found_fallback_entry, + bool* found_network_namespace) { + DCHECK(cache && cache->is_complete()); + + // This layer of indirection is here to facilitate testing. + if (simulate_find_sub_resource_) { + *found_entry = simulated_found_entry_; + *found_fallback_entry = simulated_found_fallback_entry_; + *found_network_namespace = simulated_found_network_namespace_; + simulate_find_sub_resource_ = false; + return; + } + + cache->FindResponseForRequest(url, found_entry, found_fallback_entry, + found_network_namespace); +} + void MockAppCacheStorage::MarkEntryAsForeign( const GURL& entry_url, int64 cache_id) { AppCache* cache = working_set_.GetCache(cache_id); @@ -194,9 +217,21 @@ void MockAppCacheStorage::ProcessFindResponseForMainRequest( // look for a fallback namespace // look for a online namespace // } + AppCacheEntry found_entry; + AppCacheEntry found_fallback_entry; + int64 found_cache_id = kNoCacheId; + GURL found_manifest_url = GURL(); + if (simulate_find_main_resource_) { + found_entry = simulated_found_entry_; + found_fallback_entry = simulated_found_fallback_entry_; + found_cache_id = simulated_found_cache_id_; + found_manifest_url = simulated_found_manifest_url_; + simulate_find_main_resource_ = false; + } if (delegate_ref->delegate) { delegate_ref->delegate->OnMainResponseFound( - url, AppCacheEntry(), kNoCacheId, GURL()); + url, found_entry, found_fallback_entry, + found_cache_id, found_manifest_url); } } diff --git a/webkit/appcache/mock_appcache_storage.h b/webkit/appcache/mock_appcache_storage.h index eaf742b..c29959a 100644 --- a/webkit/appcache/mock_appcache_storage.h +++ b/webkit/appcache/mock_appcache_storage.h @@ -32,6 +32,10 @@ class MockAppCacheStorage : public AppCacheStorage { virtual void StoreGroupAndNewestCache( AppCacheGroup* group, Delegate* delegate); virtual void FindResponseForMainRequest(const GURL& url, Delegate* delegate); + virtual void FindResponseForSubRequest( + AppCache* cache, const GURL& url, + AppCacheEntry* found_entry, AppCacheEntry* found_fallback_entry, + bool * found_network_namespace); virtual void MarkEntryAsForeign(const GURL& entry_url, int64 cache_id); virtual void MakeGroupObsolete(AppCacheGroup* group, Delegate* delegate); virtual AppCacheResponseReader* CreateResponseReader( @@ -41,6 +45,7 @@ class MockAppCacheStorage : public AppCacheStorage { const GURL& manifest_url, const std::vector<int64>& response_ids); private: + friend class AppCacheRequestHandlerTest; friend class AppCacheUpdateJobTest; typedef base::hash_map<int64, scoped_refptr<AppCache> > StoredCacheMap; @@ -100,6 +105,32 @@ class MockAppCacheStorage : public AppCacheStorage { simulate_store_group_and_newest_cache_failure_ = true; } + // Simulate FindResponseFor results for testing. + void SimulateFindMainResource( + const AppCacheEntry& entry, + const AppCacheEntry& fallback_entry, + int64 cache_id, const GURL& manifest_url) { + simulate_find_main_resource_ = true; + simulate_find_sub_resource_ = false; + simulated_found_entry_ = entry; + simulated_found_fallback_entry_ = fallback_entry; + simulated_found_cache_id_ = cache_id; + simulated_found_manifest_url_ = manifest_url, + simulated_found_network_namespace_ = false; // N/A to main resource loads + } + void SimulateFindSubResource( + const AppCacheEntry& entry, + const AppCacheEntry& fallback_entry, + bool network_namespace) { + simulate_find_main_resource_ = false; + simulate_find_sub_resource_ = true; + simulated_found_entry_ = entry; + simulated_found_fallback_entry_ = fallback_entry; + simulated_found_cache_id_ = kNoCacheId; // N/A to sub resource loads + simulated_found_manifest_url_ = GURL(); // N/A to sub resource loads + simulated_found_network_namespace_ = network_namespace; + } + StoredCacheMap stored_caches_; StoredGroupMap stored_groups_; DoomedResponseIds doomed_response_ids_; @@ -110,6 +141,14 @@ class MockAppCacheStorage : public AppCacheStorage { bool simulate_make_group_obsolete_failure_; bool simulate_store_group_and_newest_cache_failure_; + bool simulate_find_main_resource_; + bool simulate_find_sub_resource_; + AppCacheEntry simulated_found_entry_; + AppCacheEntry simulated_found_fallback_entry_; + int64 simulated_found_cache_id_; + GURL simulated_found_manifest_url_; + bool simulated_found_network_namespace_; + FRIEND_TEST(MockAppCacheStorageTest, CreateGroup); FRIEND_TEST(MockAppCacheStorageTest, LoadCache_FarHit); FRIEND_TEST(MockAppCacheStorageTest, LoadGroupAndCache_FarHit); diff --git a/webkit/appcache/mock_appcache_storage_unittest.cc b/webkit/appcache/mock_appcache_storage_unittest.cc index 60cd801..911ec7c 100644 --- a/webkit/appcache/mock_appcache_storage_unittest.cc +++ b/webkit/appcache/mock_appcache_storage_unittest.cc @@ -42,9 +42,11 @@ class MockAppCacheStorageTest : public testing::Test { } void OnMainResponseFound(const GURL& url, const AppCacheEntry& entry, + const AppCacheEntry& fallback_entry, int64 cache_id, const GURL& manifest_url) { found_url_ = url; found_entry_ = entry; + found_fallback_entry_ = fallback_entry; found_cache_id_ = cache_id; found_manifest_url_ = manifest_url; } @@ -59,6 +61,7 @@ class MockAppCacheStorageTest : public testing::Test { bool obsoleted_success_; GURL found_url_; AppCacheEntry found_entry_; + AppCacheEntry found_fallback_entry_; int64 found_cache_id_; GURL found_manifest_url_; }; @@ -351,7 +354,9 @@ TEST_F(MockAppCacheStorageTest, FindNoMainResponse) { EXPECT_TRUE(delegate.found_manifest_url_.is_empty()); EXPECT_EQ(kNoCacheId, delegate.found_cache_id_); EXPECT_EQ(kNoResponseId, delegate.found_entry_.response_id()); + EXPECT_EQ(kNoResponseId, delegate.found_fallback_entry_.response_id()); EXPECT_EQ(0, delegate.found_entry_.types()); + EXPECT_EQ(0, delegate.found_fallback_entry_.types()); } } // namespace appcache diff --git a/webkit/tools/test_shell/test_shell.gyp b/webkit/tools/test_shell/test_shell.gyp index de99a6f..c05a9f2 100644 --- a/webkit/tools/test_shell/test_shell.gyp +++ b/webkit/tools/test_shell/test_shell.gyp @@ -367,9 +367,11 @@ '../../appcache/appcache_unittest.cc', '../../appcache/appcache_group_unittest.cc', '../../appcache/appcache_host_unittest.cc', + '../../appcache/appcache_request_handler_unittest.cc', '../../appcache/appcache_response_unittest.cc', '../../appcache/appcache_storage_unittest.cc', '../../appcache/appcache_update_job_unittest.cc', + '../../appcache/appcache_url_request_job_unittest.cc', '../../appcache/mock_appcache_service.h', '../../appcache/mock_appcache_storage_unittest.cc', '../../database/databases_table_unittest.cc', diff --git a/webkit/webkit.gyp b/webkit/webkit.gyp index ddb847b..9bf19c7 100644 --- a/webkit/webkit.gyp +++ b/webkit/webkit.gyp @@ -168,6 +168,8 @@ 'appcache/appcache_working_set.h', 'appcache/appcache_update_job.cc', 'appcache/appcache_update_job.h', + 'appcache/appcache_url_request_job.cc', + 'appcache/appcache_url_request_job.h', 'appcache/manifest_parser.cc', 'appcache/manifest_parser.h', 'appcache/mock_appcache_storage.cc', |