summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormichaeln@google.com <michaeln@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2009-11-09 00:09:21 +0000
committermichaeln@google.com <michaeln@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2009-11-09 00:09:21 +0000
commit3367fc1dd134eeaf9ab6e62397f05ad3964319fe (patch)
tree78fb4d7ac912bbaf47b64f0aaefef331906b5fca
parent9f9e1fcbbd52267fa657b3b0b80ece683ceb6dd6 (diff)
downloadchromium_src-3367fc1dd134eeaf9ab6e62397f05ad3964319fe.zip
chromium_src-3367fc1dd134eeaf9ab6e62397f05ad3964319fe.tar.gz
chromium_src-3367fc1dd134eeaf9ab6e62397f05ad3964319fe.tar.bz2
AppCache changes to the networking model. Added new classes AppCacheRequestHandler and AppCacheURLRequestJob.
TEST=appcache_url_request_job_unittest.cc, appcache_request_handler_unittest.cc BUG=none Review URL: http://codereview.chromium.org/338034 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@31411 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--webkit/appcache/appcache.h5
-rw-r--r--webkit/appcache/appcache_entry.h1
-rw-r--r--webkit/appcache/appcache_host.cc29
-rw-r--r--webkit/appcache/appcache_host.h14
-rw-r--r--webkit/appcache/appcache_interceptor.cc1
-rw-r--r--webkit/appcache/appcache_request_handler.cc291
-rw-r--r--webkit/appcache/appcache_request_handler.h74
-rw-r--r--webkit/appcache/appcache_request_handler_unittest.cc653
-rw-r--r--webkit/appcache/appcache_response.cc9
-rw-r--r--webkit/appcache/appcache_response.h10
-rw-r--r--webkit/appcache/appcache_response_unittest.cc24
-rw-r--r--webkit/appcache/appcache_storage.h28
-rw-r--r--webkit/appcache/appcache_storage_unittest.cc3
-rw-r--r--webkit/appcache/appcache_url_request_job.cc197
-rw-r--r--webkit/appcache/appcache_url_request_job.h142
-rw-r--r--webkit/appcache/appcache_url_request_job_unittest.cc745
-rw-r--r--webkit/appcache/mock_appcache_storage.cc39
-rw-r--r--webkit/appcache/mock_appcache_storage.h39
-rw-r--r--webkit/appcache/mock_appcache_storage_unittest.cc5
-rw-r--r--webkit/tools/test_shell/test_shell.gyp2
-rw-r--r--webkit/webkit.gyp2
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',