summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjkarlin <jkarlin@chromium.org>2014-08-25 20:59:29 -0700
committerCommit bot <commit-bot@chromium.org>2014-08-26 04:00:22 +0000
commitb5ca81a3262238668cc4e59a90f51d7e8f6360d4 (patch)
treefe98f98cebc404df3c882837bdb60c7f5d55af22
parent80bc5e3ce73e2a306bec3aca8aa4d54b66b95d04 (diff)
downloadchromium_src-b5ca81a3262238668cc4e59a90f51d7e8f6360d4.zip
chromium_src-b5ca81a3262238668cc4e59a90f51d7e8f6360d4.tar.gz
chromium_src-b5ca81a3262238668cc4e59a90f51d7e8f6360d4.tar.bz2
Initial implementation of ServiceWorkerCache Put and Match functions.
Note that the Match() function copies the data into a memory blob. This is of course only for demo purposes. This will soon be changed to use a streaming API. See bug 403493. BUG=392621 Review URL: https://codereview.chromium.org/465463002 Cr-Commit-Position: refs/heads/master@{#291834}
-rw-r--r--content/browser/service_worker/service_worker_cache.cc676
-rw-r--r--content/browser/service_worker/service_worker_cache.h56
-rw-r--r--content/browser/service_worker/service_worker_cache.proto15
-rw-r--r--content/browser/service_worker/service_worker_cache_storage.cc21
-rw-r--r--content/browser/service_worker/service_worker_cache_storage.h4
-rw-r--r--content/browser/service_worker/service_worker_cache_unittest.cc312
-rw-r--r--content/content_tests.gypi1
-rw-r--r--net/base/io_buffer.cc7
-rw-r--r--net/base/io_buffer.h1
9 files changed, 1069 insertions, 24 deletions
diff --git a/content/browser/service_worker/service_worker_cache.cc b/content/browser/service_worker/service_worker_cache.cc
index 4219f19..7284b6f 100644
--- a/content/browser/service_worker/service_worker_cache.cc
+++ b/content/browser/service_worker/service_worker_cache.cc
@@ -7,11 +7,562 @@
#include <string>
#include "base/files/file_path.h"
+#include "base/guid.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "content/browser/service_worker/service_worker_cache.pb.h"
+#include "content/public/browser/browser_thread.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/disk_cache/disk_cache.h"
#include "net/url_request/url_request_context.h"
+#include "webkit/browser/blob/blob_data_handle.h"
#include "webkit/browser/blob/blob_storage_context.h"
+#include "webkit/browser/blob/blob_url_request_job_factory.h"
namespace content {
+namespace {
+
+typedef scoped_ptr<disk_cache::Backend> ScopedBackendPtr;
+typedef base::Callback<void(bool)> BoolCallback;
+typedef base::Callback<void(disk_cache::ScopedEntryPtr, bool)>
+ EntryBoolCallback;
+enum EntryIndex { INDEX_HEADERS = 0, INDEX_RESPONSE_BODY };
+
+// The maximum size of an individual cache. Ultimately cache size is controlled
+// per-origin.
+const int kMaxCacheBytes = 512 * 1024 * 1024;
+
+// Buffer size for cache and blob reading/writing.
+const int kBufferSize = 1024 * 512;
+
+struct ResponseReadContext {
+ ResponseReadContext(scoped_refptr<net::IOBufferWithSize> buff,
+ scoped_refptr<storage::BlobData> blob)
+ : buffer(buff), blob_data(blob), total_bytes_read(0) {}
+
+ scoped_refptr<net::IOBufferWithSize> buffer;
+ scoped_refptr<storage::BlobData> blob_data;
+ int total_bytes_read;
+
+ DISALLOW_COPY_AND_ASSIGN(ResponseReadContext);
+};
+
+// Streams data from a blob and writes it to a given disk_cache::Entry.
+class BlobReader : public net::URLRequest::Delegate {
+ public:
+ typedef base::Callback<void(disk_cache::ScopedEntryPtr, bool)>
+ EntryBoolCallback;
+
+ BlobReader(disk_cache::ScopedEntryPtr entry)
+ : cache_entry_offset_(0),
+ buffer_(new net::IOBufferWithSize(kBufferSize)),
+ weak_ptr_factory_(this) {
+ DCHECK(entry);
+ entry_ = entry.Pass();
+ }
+
+ void StreamBlobToCache(net::URLRequestContext* request_context,
+ scoped_ptr<storage::BlobDataHandle> blob_data_handle,
+ const EntryBoolCallback& callback) {
+ callback_ = callback;
+ blob_request_ = storage::BlobProtocolHandler::CreateBlobRequest(
+ blob_data_handle.Pass(), request_context, this);
+ blob_request_->Start();
+ }
+
+ // net::URLRequest::Delegate overrides for reading blobs.
+ virtual void OnReceivedRedirect(net::URLRequest* request,
+ const net::RedirectInfo& redirect_info,
+ bool* defer_redirect) OVERRIDE {
+ NOTREACHED();
+ }
+ virtual void OnAuthRequired(net::URLRequest* request,
+ net::AuthChallengeInfo* auth_info) OVERRIDE {
+ NOTREACHED();
+ }
+ virtual void OnCertificateRequested(
+ net::URLRequest* request,
+ net::SSLCertRequestInfo* cert_request_info) OVERRIDE {
+ NOTREACHED();
+ }
+ virtual void OnSSLCertificateError(net::URLRequest* request,
+ const net::SSLInfo& ssl_info,
+ bool fatal) OVERRIDE {
+ NOTREACHED();
+ }
+ virtual void OnBeforeNetworkStart(net::URLRequest* request,
+ bool* defer) OVERRIDE {
+ NOTREACHED();
+ }
+
+ virtual void OnResponseStarted(net::URLRequest* request) OVERRIDE {
+ if (!request->status().is_success()) {
+ callback_.Run(entry_.Pass(), false);
+ return;
+ }
+ ReadFromBlob();
+ }
+
+ virtual void ReadFromBlob() {
+ int bytes_read = 0;
+ bool done =
+ blob_request_->Read(buffer_.get(), buffer_->size(), &bytes_read);
+ if (done)
+ OnReadCompleted(blob_request_.get(), bytes_read);
+ }
+
+ virtual void OnReadCompleted(net::URLRequest* request,
+ int bytes_read) OVERRIDE {
+ if (!request->status().is_success()) {
+ callback_.Run(entry_.Pass(), false);
+ return;
+ }
+
+ if (bytes_read == 0) {
+ callback_.Run(entry_.Pass(), true);
+ return;
+ }
+
+ net::CompletionCallback cache_write_callback =
+ base::Bind(&BlobReader::DidWriteDataToEntry,
+ weak_ptr_factory_.GetWeakPtr(),
+ bytes_read);
+
+ int rv = entry_->WriteData(INDEX_RESPONSE_BODY,
+ cache_entry_offset_,
+ buffer_,
+ bytes_read,
+ cache_write_callback,
+ true /* truncate */);
+ if (rv != net::ERR_IO_PENDING)
+ cache_write_callback.Run(rv);
+ }
+
+ void DidWriteDataToEntry(int expected_bytes, int rv) {
+ if (rv != expected_bytes) {
+ callback_.Run(entry_.Pass(), false);
+ return;
+ }
+
+ cache_entry_offset_ += rv;
+ ReadFromBlob();
+ }
+
+ private:
+ int cache_entry_offset_;
+ disk_cache::ScopedEntryPtr entry_;
+ scoped_ptr<net::URLRequest> blob_request_;
+ EntryBoolCallback callback_;
+ scoped_refptr<net::IOBufferWithSize> buffer_;
+ base::WeakPtrFactory<BlobReader> weak_ptr_factory_;
+};
+
+// Put callbacks
+void PutDidCreateEntry(ServiceWorkerFetchRequest* request,
+ ServiceWorkerResponse* response,
+ const ServiceWorkerCache::ErrorCallback& callback,
+ scoped_ptr<disk_cache::Entry*> entryptr,
+ scoped_ptr<storage::BlobDataHandle> blob_data_handle,
+ net::URLRequestContext* request_context,
+ int rv);
+void PutDidWriteHeaders(ServiceWorkerResponse* response,
+ const ServiceWorkerCache::ErrorCallback& callback,
+ disk_cache::ScopedEntryPtr entry,
+ scoped_ptr<storage::BlobDataHandle> blob_data_handle,
+ net::URLRequestContext* request_context,
+ int expected_bytes,
+ int rv);
+void PutDidWriteBlobToCache(const ServiceWorkerCache::ErrorCallback& callback,
+ scoped_ptr<BlobReader> blob_reader,
+ disk_cache::ScopedEntryPtr entry,
+ bool success);
+
+// Match callbacks
+void MatchDidOpenEntry(ServiceWorkerFetchRequest* request,
+ const ServiceWorkerCache::ResponseCallback& callback,
+ base::WeakPtr<storage::BlobStorageContext> blob_storage,
+ scoped_ptr<disk_cache::Entry*> entryptr,
+ int rv);
+void MatchDidReadHeaderData(
+ ServiceWorkerFetchRequest* request,
+ const ServiceWorkerCache::ResponseCallback& callback,
+ base::WeakPtr<storage::BlobStorageContext> blob_storage,
+ disk_cache::ScopedEntryPtr entry,
+ const scoped_refptr<net::IOBufferWithSize>& buffer,
+ int rv);
+void MatchDidReadResponseBodyData(
+ ServiceWorkerFetchRequest* request,
+ const ServiceWorkerCache::ResponseCallback& callback,
+ base::WeakPtr<storage::BlobStorageContext> blob_storage,
+ disk_cache::ScopedEntryPtr entry,
+ scoped_ptr<ServiceWorkerResponse> response,
+ scoped_ptr<ResponseReadContext> response_context,
+ int rv);
+void MatchDoneWithBody(ServiceWorkerFetchRequest* request,
+ const ServiceWorkerCache::ResponseCallback& callback,
+ base::WeakPtr<storage::BlobStorageContext> blob_storage,
+ scoped_ptr<ServiceWorkerResponse> response,
+ scoped_ptr<ResponseReadContext> response_context);
+
+// Delete callbacks
+void DeleteDidOpenEntry(ServiceWorkerFetchRequest* request,
+ const ServiceWorkerCache::ErrorCallback& callback,
+ scoped_ptr<disk_cache::Entry*> entryptr,
+ int rv);
+
+// CreateBackend callbacks
+void CreateBackendDidCreate(const ServiceWorkerCache::ErrorCallback& callback,
+ scoped_ptr<ScopedBackendPtr> backend_ptr,
+ base::WeakPtr<ServiceWorkerCache> cache,
+ int rv);
+
+void PutDidCreateEntry(ServiceWorkerFetchRequest* request,
+ ServiceWorkerResponse* response,
+ const ServiceWorkerCache::ErrorCallback& callback,
+ scoped_ptr<disk_cache::Entry*> entryptr,
+ scoped_ptr<storage::BlobDataHandle> blob_data_handle,
+ net::URLRequestContext* request_context,
+ int rv) {
+ if (rv != net::OK) {
+ callback.Run(ServiceWorkerCache::ErrorTypeExists);
+ return;
+ }
+
+ DCHECK(entryptr);
+ disk_cache::ScopedEntryPtr entry(*entryptr);
+
+ ServiceWorkerRequestResponseHeaders headers;
+ headers.set_method(request->method);
+
+ headers.set_status_code(response->status_code);
+ headers.set_status_text(response->status_text);
+ for (std::map<std::string, std::string>::const_iterator it =
+ request->headers.begin();
+ it != request->headers.end();
+ ++it) {
+ ServiceWorkerRequestResponseHeaders::HeaderMap* header_map =
+ headers.add_request_headers();
+ header_map->set_name(it->first);
+ header_map->set_value(it->second);
+ }
+
+ for (std::map<std::string, std::string>::const_iterator it =
+ response->headers.begin();
+ it != response->headers.end();
+ ++it) {
+ ServiceWorkerRequestResponseHeaders::HeaderMap* header_map =
+ headers.add_response_headers();
+ header_map->set_name(it->first);
+ header_map->set_value(it->second);
+ }
+
+ scoped_ptr<std::string> serialized(new std::string());
+ if (!headers.SerializeToString(serialized.get())) {
+ callback.Run(ServiceWorkerCache::ErrorTypeStorage);
+ return;
+ }
+
+ scoped_refptr<net::StringIOBuffer> buffer(
+ new net::StringIOBuffer(serialized.Pass()));
+
+ // Get a temporary copy of the entry pointer before passing it in base::Bind.
+ disk_cache::Entry* tmp_entry_ptr = entry.get();
+
+ net::CompletionCallback write_headers_callback =
+ base::Bind(PutDidWriteHeaders,
+ response,
+ callback,
+ base::Passed(entry.Pass()),
+ base::Passed(blob_data_handle.Pass()),
+ request_context,
+ buffer->size());
+
+ rv = tmp_entry_ptr->WriteData(INDEX_HEADERS,
+ 0 /* offset */,
+ buffer,
+ buffer->size(),
+ write_headers_callback,
+ true /* truncate */);
+
+ if (rv != net::ERR_IO_PENDING)
+ write_headers_callback.Run(rv);
+}
+
+void PutDidWriteHeaders(ServiceWorkerResponse* response,
+ const ServiceWorkerCache::ErrorCallback& callback,
+ disk_cache::ScopedEntryPtr entry,
+ scoped_ptr<storage::BlobDataHandle> blob_data_handle,
+ net::URLRequestContext* request_context,
+ int expected_bytes,
+ int rv) {
+ if (rv != expected_bytes) {
+ entry->Doom();
+ callback.Run(ServiceWorkerCache::ErrorTypeStorage);
+ return;
+ }
+
+ // The metadata is written, now for the response content. The data is streamed
+ // from the blob into the cache entry.
+
+ if (response->blob_uuid.empty()) {
+ callback.Run(ServiceWorkerCache::ErrorTypeOK);
+ return;
+ }
+
+ DCHECK(blob_data_handle);
+
+ scoped_ptr<BlobReader> reader(new BlobReader(entry.Pass()));
+ BlobReader* reader_ptr = reader.get();
+
+ reader_ptr->StreamBlobToCache(
+ request_context,
+ blob_data_handle.Pass(),
+ base::Bind(
+ PutDidWriteBlobToCache, callback, base::Passed(reader.Pass())));
+}
+
+void PutDidWriteBlobToCache(const ServiceWorkerCache::ErrorCallback& callback,
+ scoped_ptr<BlobReader> blob_reader,
+ disk_cache::ScopedEntryPtr entry,
+ bool success) {
+ if (!success) {
+ entry->Doom();
+ callback.Run(ServiceWorkerCache::ErrorTypeStorage);
+ return;
+ }
+
+ callback.Run(ServiceWorkerCache::ErrorTypeOK);
+}
+
+void MatchDidOpenEntry(ServiceWorkerFetchRequest* request,
+ const ServiceWorkerCache::ResponseCallback& callback,
+ base::WeakPtr<storage::BlobStorageContext> blob_storage,
+ scoped_ptr<disk_cache::Entry*> entryptr,
+ int rv) {
+ if (rv != net::OK) {
+ callback.Run(ServiceWorkerCache::ErrorTypeNotFound,
+ scoped_ptr<ServiceWorkerResponse>(),
+ scoped_ptr<storage::BlobDataHandle>());
+ return;
+ }
+
+ DCHECK(entryptr);
+ disk_cache::ScopedEntryPtr entry(*entryptr);
+
+ scoped_refptr<net::IOBufferWithSize> buffer(
+ new net::IOBufferWithSize(entry->GetDataSize(INDEX_HEADERS)));
+
+ // Copy the entry pointer before passing it in base::Bind.
+ disk_cache::Entry* tmp_entry_ptr = entry.get();
+
+ net::CompletionCallback read_header_callback =
+ base::Bind(MatchDidReadHeaderData,
+ request,
+ callback,
+ blob_storage,
+ base::Passed(entry.Pass()),
+ buffer);
+
+ int read_rv = tmp_entry_ptr->ReadData(
+ INDEX_HEADERS, 0, buffer.get(), buffer->size(), read_header_callback);
+
+ if (read_rv != net::ERR_IO_PENDING)
+ read_header_callback.Run(read_rv);
+}
+
+void MatchDidReadHeaderData(
+ ServiceWorkerFetchRequest* request,
+ const ServiceWorkerCache::ResponseCallback& callback,
+ base::WeakPtr<storage::BlobStorageContext> blob_storage,
+ disk_cache::ScopedEntryPtr entry,
+ const scoped_refptr<net::IOBufferWithSize>& buffer,
+ int rv) {
+ if (rv != buffer->size()) {
+ callback.Run(ServiceWorkerCache::ErrorTypeStorage,
+ scoped_ptr<ServiceWorkerResponse>(),
+ scoped_ptr<storage::BlobDataHandle>());
+
+ return;
+ }
+
+ ServiceWorkerRequestResponseHeaders headers;
+
+ if (!headers.ParseFromArray(buffer->data(), buffer->size())) {
+ callback.Run(ServiceWorkerCache::ErrorTypeStorage,
+ scoped_ptr<ServiceWorkerResponse>(),
+ scoped_ptr<storage::BlobDataHandle>());
+
+ return;
+ }
+
+ scoped_ptr<ServiceWorkerResponse> response(
+ new ServiceWorkerResponse(request->url,
+ headers.status_code(),
+ headers.status_text(),
+ std::map<std::string, std::string>(),
+ ""));
+
+ for (int i = 0; i < headers.response_headers_size(); ++i) {
+ const ServiceWorkerRequestResponseHeaders::HeaderMap header =
+ headers.response_headers(i);
+ response->headers.insert(std::make_pair(header.name(), header.value()));
+ }
+
+ // TODO(jkarlin): Insert vary validation here.
+
+ if (entry->GetDataSize(INDEX_RESPONSE_BODY) == 0) {
+ callback.Run(ServiceWorkerCache::ErrorTypeOK,
+ response.Pass(),
+ scoped_ptr<storage::BlobDataHandle>());
+ return;
+ }
+
+ // Stream the response body into a blob.
+ if (!blob_storage) {
+ callback.Run(ServiceWorkerCache::ErrorTypeStorage,
+ scoped_ptr<ServiceWorkerResponse>(),
+ scoped_ptr<storage::BlobDataHandle>());
+
+ return;
+ }
+
+ response->blob_uuid = base::GenerateGUID();
+
+ scoped_refptr<storage::BlobData> blob_data =
+ new storage::BlobData(response->blob_uuid);
+ scoped_refptr<net::IOBufferWithSize> response_body_buffer(
+ new net::IOBufferWithSize(kBufferSize));
+
+ scoped_ptr<ResponseReadContext> read_context(
+ new ResponseReadContext(response_body_buffer, blob_data));
+
+ // Copy the entry pointer before passing it in base::Bind.
+ disk_cache::Entry* tmp_entry_ptr = entry.get();
+
+ net::CompletionCallback read_callback =
+ base::Bind(MatchDidReadResponseBodyData,
+ request,
+ callback,
+ blob_storage,
+ base::Passed(entry.Pass()),
+ base::Passed(response.Pass()),
+ base::Passed(read_context.Pass()));
+
+ int read_rv = tmp_entry_ptr->ReadData(INDEX_RESPONSE_BODY,
+ 0,
+ response_body_buffer.get(),
+ response_body_buffer->size(),
+ read_callback);
+
+ if (read_rv != net::ERR_IO_PENDING)
+ read_callback.Run(read_rv);
+}
+
+void MatchDidReadResponseBodyData(
+ ServiceWorkerFetchRequest* request,
+ const ServiceWorkerCache::ResponseCallback& callback,
+ base::WeakPtr<storage::BlobStorageContext> blob_storage,
+ disk_cache::ScopedEntryPtr entry,
+ scoped_ptr<ServiceWorkerResponse> response,
+ scoped_ptr<ResponseReadContext> response_context,
+ int rv) {
+ if (rv < 0) {
+ callback.Run(ServiceWorkerCache::ErrorTypeStorage,
+ scoped_ptr<ServiceWorkerResponse>(),
+ scoped_ptr<storage::BlobDataHandle>());
+ return;
+ }
+
+ if (rv == 0) {
+ MatchDoneWithBody(request,
+ callback,
+ blob_storage,
+ response.Pass(),
+ response_context.Pass());
+ return;
+ }
+
+ // TODO(jkarlin): This copying of the the entire cache response into memory is
+ // awful. Create a new interface around SimpleCache that provides access the
+ // data directly from the file. See bug http://crbug.com/403493.
+ response_context->blob_data->AppendData(response_context->buffer->data(), rv);
+ response_context->total_bytes_read += rv;
+ int total_bytes_read = response_context->total_bytes_read;
+
+ // Grab some pointers before passing them in bind.
+ net::IOBufferWithSize* buffer = response_context->buffer;
+ disk_cache::Entry* tmp_entry_ptr = entry.get();
+
+ net::CompletionCallback read_callback =
+ base::Bind(MatchDidReadResponseBodyData,
+ request,
+ callback,
+ blob_storage,
+ base::Passed(entry.Pass()),
+ base::Passed(response.Pass()),
+ base::Passed(response_context.Pass()));
+
+ int read_rv = tmp_entry_ptr->ReadData(INDEX_RESPONSE_BODY,
+ total_bytes_read,
+ buffer,
+ buffer->size(),
+ read_callback);
+
+ if (read_rv != net::ERR_IO_PENDING)
+ read_callback.Run(read_rv);
+}
+
+void MatchDoneWithBody(ServiceWorkerFetchRequest* request,
+ const ServiceWorkerCache::ResponseCallback& callback,
+ base::WeakPtr<storage::BlobStorageContext> blob_storage,
+ scoped_ptr<ServiceWorkerResponse> response,
+ scoped_ptr<ResponseReadContext> response_context) {
+ if (!blob_storage) {
+ callback.Run(ServiceWorkerCache::ErrorTypeStorage,
+ scoped_ptr<ServiceWorkerResponse>(),
+ scoped_ptr<storage::BlobDataHandle>());
+ return;
+ }
+
+ scoped_ptr<storage::BlobDataHandle> blob_data_handle(
+ blob_storage->AddFinishedBlob(response_context->blob_data.get()));
+
+ callback.Run(ServiceWorkerCache::ErrorTypeOK,
+ response.Pass(),
+ blob_data_handle.Pass());
+}
+
+void DeleteDidOpenEntry(ServiceWorkerFetchRequest* request,
+ const ServiceWorkerCache::ErrorCallback& callback,
+ scoped_ptr<disk_cache::Entry*> entryptr,
+ int rv) {
+ if (rv != net::OK) {
+ callback.Run(ServiceWorkerCache::ErrorTypeNotFound);
+ return;
+ }
+
+ DCHECK(entryptr);
+ disk_cache::ScopedEntryPtr entry(*entryptr);
+
+ entry->Doom();
+ callback.Run(ServiceWorkerCache::ErrorTypeOK);
+}
+
+void CreateBackendDidCreate(const ServiceWorkerCache::ErrorCallback& callback,
+ scoped_ptr<ScopedBackendPtr> backend_ptr,
+ base::WeakPtr<ServiceWorkerCache> cache,
+ int rv) {
+ if (rv != net::OK || !cache) {
+ callback.Run(ServiceWorkerCache::ErrorTypeStorage);
+ return;
+ }
+ cache->set_backend(backend_ptr->Pass());
+ callback.Run(ServiceWorkerCache::ErrorTypeOK);
+}
+
+} // namespace
+
// static
scoped_ptr<ServiceWorkerCache> ServiceWorkerCache::CreateMemoryCache(
const std::string& name,
@@ -31,15 +582,131 @@ scoped_ptr<ServiceWorkerCache> ServiceWorkerCache::CreatePersistentCache(
new ServiceWorkerCache(path, name, request_context, blob_context));
}
-void ServiceWorkerCache::CreateBackend(
- const base::Callback<void(bool)>& callback) {
- callback.Run(true);
+ServiceWorkerCache::~ServiceWorkerCache() {
}
base::WeakPtr<ServiceWorkerCache> ServiceWorkerCache::AsWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
+void ServiceWorkerCache::CreateBackend(const ErrorCallback& callback) {
+ DCHECK(!backend_);
+
+ // Use APP_CACHE as opposed to DISK_CACHE to prevent cache eviction.
+ net::CacheType cache_type =
+ path_.empty() ? net::MEMORY_CACHE : net::APP_CACHE;
+
+ scoped_ptr<ScopedBackendPtr> backend_ptr(new ScopedBackendPtr());
+
+ // Temporary pointer so that backend_ptr can be Pass()'d in Bind below.
+ ScopedBackendPtr* backend = backend_ptr.get();
+
+ net::CompletionCallback create_cache_callback =
+ base::Bind(CreateBackendDidCreate,
+ callback,
+ base::Passed(backend_ptr.Pass()),
+ weak_ptr_factory_.GetWeakPtr());
+
+ // TODO(jkarlin): Use the cache MessageLoopProxy that ServiceWorkerCacheCore
+ // has for disk caches.
+ // TODO(jkarlin): Switch to SimpleCache after it supports APP_CACHE and after
+ // debugging why the QuickStressBody unittest fails with it.
+ int rv = disk_cache::CreateCacheBackend(
+ cache_type,
+ net::CACHE_BACKEND_DEFAULT,
+ path_,
+ kMaxCacheBytes,
+ false, /* force */
+ BrowserThread::GetMessageLoopProxyForThread(BrowserThread::CACHE).get(),
+ NULL,
+ backend,
+ create_cache_callback);
+ if (rv != net::ERR_IO_PENDING)
+ create_cache_callback.Run(rv);
+}
+
+void ServiceWorkerCache::Put(ServiceWorkerFetchRequest* request,
+ ServiceWorkerResponse* response,
+ const ErrorCallback& callback) {
+ DCHECK(backend_);
+
+ scoped_ptr<disk_cache::Entry*> entry(new disk_cache::Entry*);
+
+ disk_cache::Entry** entry_ptr = entry.get();
+
+ scoped_ptr<storage::BlobDataHandle> blob_data_handle;
+
+ if (!response->blob_uuid.empty()) {
+ if (!blob_storage_context_) {
+ callback.Run(ErrorTypeStorage);
+ return;
+ }
+ blob_data_handle =
+ blob_storage_context_->GetBlobDataFromUUID(response->blob_uuid);
+ if (!blob_data_handle) {
+ callback.Run(ErrorTypeStorage);
+ return;
+ }
+ }
+
+ net::CompletionCallback create_entry_callback =
+ base::Bind(PutDidCreateEntry,
+ request,
+ response,
+ callback,
+ base::Passed(entry.Pass()),
+ base::Passed(blob_data_handle.Pass()),
+ request_context_);
+
+ int rv = backend_->CreateEntry(
+ request->url.spec(), entry_ptr, create_entry_callback);
+
+ if (rv != net::ERR_IO_PENDING)
+ create_entry_callback.Run(rv);
+}
+
+void ServiceWorkerCache::Match(ServiceWorkerFetchRequest* request,
+ const ResponseCallback& callback) {
+ DCHECK(backend_);
+
+ scoped_ptr<disk_cache::Entry*> entry(new disk_cache::Entry*);
+
+ disk_cache::Entry** entry_ptr = entry.get();
+
+ net::CompletionCallback open_entry_callback =
+ base::Bind(MatchDidOpenEntry,
+ request,
+ callback,
+ blob_storage_context_,
+ base::Passed(entry.Pass()));
+
+ int rv =
+ backend_->OpenEntry(request->url.spec(), entry_ptr, open_entry_callback);
+ if (rv != net::ERR_IO_PENDING)
+ open_entry_callback.Run(rv);
+}
+
+void ServiceWorkerCache::Delete(ServiceWorkerFetchRequest* request,
+ const ErrorCallback& callback) {
+ DCHECK(backend_);
+
+ scoped_ptr<disk_cache::Entry*> entry(new disk_cache::Entry*);
+
+ disk_cache::Entry** entry_ptr = entry.get();
+
+ net::CompletionCallback open_entry_callback = base::Bind(
+ DeleteDidOpenEntry, request, callback, base::Passed(entry.Pass()));
+
+ int rv =
+ backend_->OpenEntry(request->url.spec(), entry_ptr, open_entry_callback);
+ if (rv != net::ERR_IO_PENDING)
+ open_entry_callback.Run(rv);
+}
+
+bool ServiceWorkerCache::HasCreatedBackend() const {
+ return backend_;
+}
+
ServiceWorkerCache::ServiceWorkerCache(
const base::FilePath& path,
const std::string& name,
@@ -53,7 +720,4 @@ ServiceWorkerCache::ServiceWorkerCache(
weak_ptr_factory_(this) {
}
-ServiceWorkerCache::~ServiceWorkerCache() {
-}
-
} // namespace content
diff --git a/content/browser/service_worker/service_worker_cache.h b/content/browser/service_worker/service_worker_cache.h
index 6956cc1..6eac2a7 100644
--- a/content/browser/service_worker/service_worker_cache.h
+++ b/content/browser/service_worker/service_worker_cache.h
@@ -8,28 +8,44 @@
#include "base/callback.h"
#include "base/files/file_path.h"
#include "base/memory/weak_ptr.h"
+#include "content/common/service_worker/service_worker_types.h"
+#include "net/base/completion_callback.h"
+#include "net/disk_cache/disk_cache.h"
namespace net {
class URLRequestContext;
+class IOBufferWithSize;
}
namespace storage {
+class BlobData;
+class BlobDataHandle;
class BlobStorageContext;
}
namespace content {
+class ChromeBlobStorageContext;
-// TODO(jkarlin): Fill this in with a real Cache implementation as
-// specified in
-// https://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html.
// TODO(jkarlin): Unload cache backend from memory once the cache object is no
// longer referenced in javascript.
// Represents a ServiceWorker Cache as seen in
// https://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html.
// InitializeIfNeeded must be called before calling the other public members.
-class ServiceWorkerCache {
+class CONTENT_EXPORT ServiceWorkerCache {
public:
+ enum ErrorType {
+ ErrorTypeOK = 0,
+ ErrorTypeExists,
+ ErrorTypeStorage,
+ ErrorTypeNotFound
+ };
+ enum EntryIndex { INDEX_HEADERS = 0, INDEX_RESPONSE_BODY };
+ typedef base::Callback<void(ErrorType)> ErrorCallback;
+ typedef base::Callback<void(ErrorType,
+ scoped_ptr<ServiceWorkerResponse>,
+ scoped_ptr<storage::BlobDataHandle>)>
+ ResponseCallback;
static scoped_ptr<ServiceWorkerCache> CreateMemoryCache(
const std::string& name,
net::URLRequestContext* request_context,
@@ -44,9 +60,34 @@ class ServiceWorkerCache {
// Loads the backend and calls the callback with the result (true for
// success). This must be called before member functions that require a
- // backend are called.
- void CreateBackend(const base::Callback<void(bool)>& callback);
-
+ // backend are called. The callback will always be called.
+ void CreateBackend(const ErrorCallback& callback);
+
+ // Returns ErrorTypeNotFound if not found. The callback will always be called.
+ // |request| must remain valid until the callback is called.
+ void Match(ServiceWorkerFetchRequest* request,
+ const ResponseCallback& callback);
+
+ // Puts the request and response object in the cache. The response body (if
+ // present) is stored in the cache, but not the request body. Returns
+ // ErrorTypeOK on success. The callback will always be called. |request| and
+ // |response| must remain valid until the callback is called.
+ void Put(ServiceWorkerFetchRequest* request,
+ ServiceWorkerResponse* response,
+ const ErrorCallback& callback);
+
+ // Returns ErrorNotFound if not found. Otherwise deletes and returns
+ // ErrorTypeOK. The callback will always be called. |request| must remain
+ // valid until the callback is called.
+ void Delete(ServiceWorkerFetchRequest* request,
+ const ErrorCallback& callback);
+
+ // Call to determine if CreateBackend must be called.
+ bool HasCreatedBackend() const;
+
+ void set_backend(scoped_ptr<disk_cache::Backend> backend) {
+ backend_ = backend.Pass();
+ }
void set_name(const std::string& name) { name_ = name; }
const std::string& name() const { return name_; }
int32 id() const { return id_; }
@@ -60,6 +101,7 @@ class ServiceWorkerCache {
net::URLRequestContext* request_context,
base::WeakPtr<storage::BlobStorageContext> blob_context);
+ scoped_ptr<disk_cache::Backend> backend_;
base::FilePath path_;
std::string name_;
net::URLRequestContext* request_context_;
diff --git a/content/browser/service_worker/service_worker_cache.proto b/content/browser/service_worker/service_worker_cache.proto
index b271df6..928bcb4 100644
--- a/content/browser/service_worker/service_worker_cache.proto
+++ b/content/browser/service_worker/service_worker_cache.proto
@@ -11,7 +11,20 @@ package content;
message ServiceWorkerCacheStorageIndex {
message Cache {
required string name = 1;
- required int32 size = 2;
+ optional int32 DEPRECATED_size = 2;
+ optional int64 size = 3;
}
repeated Cache cache = 1;
}
+
+message ServiceWorkerRequestResponseHeaders {
+ message HeaderMap {
+ required string name = 1;
+ required string value = 2;
+ }
+ required string method = 1;
+ required int32 status_code = 2;
+ required string status_text = 3;
+ repeated HeaderMap request_headers = 4;
+ repeated HeaderMap response_headers = 5;
+}
diff --git a/content/browser/service_worker/service_worker_cache_storage.cc b/content/browser/service_worker/service_worker_cache_storage.cc
index d7dfbac..90d12df 100644
--- a/content/browser/service_worker/service_worker_cache_storage.cc
+++ b/content/browser/service_worker/service_worker_cache_storage.cc
@@ -63,7 +63,7 @@ class ServiceWorkerCacheStorage::CacheLoader
friend class base::RefCountedThreadSafe<
ServiceWorkerCacheStorage::CacheLoader>;
- virtual ~CacheLoader() {};
+ virtual ~CacheLoader() {}
virtual void LoadCacheImpl(const std::string&) {}
scoped_refptr<base::SequencedTaskRunner> cache_task_runner_;
@@ -342,11 +342,11 @@ class ServiceWorkerCacheStorage::SimpleCacheLoader
DCHECK_CURRENTLY_ON(BrowserThread::IO);
ServiceWorkerCacheStorageIndex index;
- index.ParseFromString(serialized);
-
- for (int i = 0, max = index.cache_size(); i < max; ++i) {
- const ServiceWorkerCacheStorageIndex::Cache& cache = index.cache(i);
- names->push_back(cache.name());
+ if (index.ParseFromString(serialized)) {
+ for (int i = 0, max = index.cache_size(); i < max; ++i) {
+ const ServiceWorkerCacheStorageIndex::Cache& cache = index.cache(i);
+ names->push_back(cache.name());
+ }
}
// TODO(jkarlin): Delete caches that are in the directory and not returned
@@ -446,6 +446,9 @@ void ServiceWorkerCacheStorage::GetCache(
return;
}
+ if (cache->HasCreatedBackend())
+ return callback.Run(cache->id(), CACHE_STORAGE_ERROR_NO_ERROR);
+
cache->CreateBackend(base::Bind(&ServiceWorkerCacheStorage::DidCreateBackend,
weak_factory_.GetWeakPtr(),
cache->AsWeakPtr(),
@@ -533,15 +536,16 @@ void ServiceWorkerCacheStorage::EnumerateCaches(
void ServiceWorkerCacheStorage::DidCreateBackend(
base::WeakPtr<ServiceWorkerCache> cache,
const CacheAndErrorCallback& callback,
- bool success) {
+ ServiceWorkerCache::ErrorType error) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
- if (!success || !cache) {
+ if (error != ServiceWorkerCache::ErrorTypeOK || !cache) {
// TODO(jkarlin): This should delete the directory and try again in case
// the cache is simply corrupt.
callback.Run(0, CACHE_STORAGE_ERROR_STORAGE);
return;
}
+
callback.Run(cache->id(), CACHE_STORAGE_ERROR_NO_ERROR);
}
@@ -672,6 +676,7 @@ void ServiceWorkerCacheStorage::CreateCacheDidWriteIndex(
callback.Run(false, CACHE_STORAGE_ERROR_STORAGE);
return;
}
+
cache->CreateBackend(base::Bind(&ServiceWorkerCacheStorage::DidCreateBackend,
weak_factory_.GetWeakPtr(),
cache,
diff --git a/content/browser/service_worker/service_worker_cache_storage.h b/content/browser/service_worker/service_worker_cache_storage.h
index 566921a..52c602d 100644
--- a/content/browser/service_worker/service_worker_cache_storage.h
+++ b/content/browser/service_worker/service_worker_cache_storage.h
@@ -12,6 +12,7 @@
#include "base/files/file_path.h"
#include "base/id_map.h"
#include "base/memory/weak_ptr.h"
+#include "content/browser/service_worker/service_worker_cache.h"
namespace base {
class SequencedTaskRunner;
@@ -26,7 +27,6 @@ class BlobStorageContext;
}
namespace content {
-class ServiceWorkerCache;
// TODO(jkarlin): Constrain the total bytes used per origin.
@@ -108,7 +108,7 @@ class ServiceWorkerCacheStorage {
void DidCreateBackend(base::WeakPtr<ServiceWorkerCache> cache,
const CacheAndErrorCallback& callback,
- bool success);
+ ServiceWorkerCache::ErrorType error);
void AddCacheToMaps(scoped_ptr<ServiceWorkerCache> cache);
diff --git a/content/browser/service_worker/service_worker_cache_unittest.cc b/content/browser/service_worker/service_worker_cache_unittest.cc
new file mode 100644
index 0000000..a90b972
--- /dev/null
+++ b/content/browser/service_worker/service_worker_cache_unittest.cc
@@ -0,0 +1,312 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/browser/service_worker/service_worker_cache.h"
+
+#include "base/files/file_path.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/run_loop.h"
+#include "content/browser/fileapi/chrome_blob_storage_context.h"
+#include "content/browser/fileapi/mock_url_request_delegate.h"
+#include "content/common/service_worker/service_worker_types.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/test/test_browser_context.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "net/url_request/url_request_job_factory_impl.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webkit/browser/blob/blob_data_handle.h"
+#include "webkit/browser/blob/blob_storage_context.h"
+#include "webkit/browser/blob/blob_url_request_job_factory.h"
+#include "webkit/common/blob/blob_data.h"
+
+namespace content {
+
+namespace {
+const char kTestData[] = "Hello World";
+
+// Returns a BlobProtocolHandler that uses |blob_storage_context|. Caller owns
+// the memory.
+storage::BlobProtocolHandler* CreateMockBlobProtocolHandler(
+ storage::BlobStorageContext* blob_storage_context) {
+ // The FileSystemContext and MessageLoopProxy are not actually used but a
+ // MessageLoopProxy is needed to avoid a DCHECK in BlobURLRequestJob ctor.
+ return new storage::BlobProtocolHandler(
+ blob_storage_context, NULL, base::MessageLoopProxy::current().get());
+}
+
+} // namespace
+
+class ServiceWorkerCacheTest : public testing::Test {
+ public:
+ ServiceWorkerCacheTest()
+ : browser_thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP),
+ callback_error_(ServiceWorkerCache::ErrorTypeOK) {}
+
+ virtual void SetUp() OVERRIDE {
+ ChromeBlobStorageContext* blob_storage_context =
+ ChromeBlobStorageContext::GetFor(&browser_context_);
+ // Wait for chrome_blob_storage_context to finish initializing.
+ base::RunLoop().RunUntilIdle();
+ blob_storage_context_ = blob_storage_context->context();
+
+ url_request_job_factory_.reset(new net::URLRequestJobFactoryImpl);
+ url_request_job_factory_->SetProtocolHandler(
+ "blob", CreateMockBlobProtocolHandler(blob_storage_context->context()));
+
+ net::URLRequestContext* url_request_context =
+ browser_context_.GetRequestContext()->GetURLRequestContext();
+
+ url_request_context->set_job_factory(url_request_job_factory_.get());
+
+ CreateRequests(blob_storage_context);
+
+ if (MemoryOnly()) {
+ cache_ = ServiceWorkerCache::CreateMemoryCache(
+ "test",
+ url_request_context,
+ blob_storage_context->context()->AsWeakPtr());
+ } else {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ cache_ = ServiceWorkerCache::CreatePersistentCache(
+ temp_dir_.path(),
+ "test",
+ url_request_context,
+ blob_storage_context->context()->AsWeakPtr());
+ }
+ CreateBackend();
+ }
+
+ void CreateRequests(ChromeBlobStorageContext* blob_storage_context) {
+ std::map<std::string, std::string> headers;
+ headers.insert(std::make_pair("a", "a"));
+ headers.insert(std::make_pair("b", "b"));
+ body_request_.reset(new ServiceWorkerFetchRequest(
+ GURL("http://example.com/body.html"), "GET", headers, GURL(""), false));
+ no_body_request_.reset(
+ new ServiceWorkerFetchRequest(GURL("http://example.com/no_body.html"),
+ "GET",
+ headers,
+ GURL(""),
+ false));
+
+ std::string expected_response;
+ for (int i = 0; i < 100; ++i)
+ expected_blob_data_ += kTestData;
+
+ scoped_refptr<storage::BlobData> blob_data(
+ new storage::BlobData("blob-id:myblob"));
+ blob_data->AppendData(expected_blob_data_);
+
+ blob_handle_ = blob_storage_context->context()->AddFinishedBlob(blob_data);
+
+ body_response_.reset(
+ new ServiceWorkerResponse(GURL("http://example.com/body.html"),
+ 200,
+ "OK",
+ headers,
+ blob_handle_->uuid()));
+
+ no_body_response_.reset(new ServiceWorkerResponse(
+ GURL("http://example.com/no_body.html"), 200, "OK", headers, ""));
+ }
+
+ void CreateBackend() {
+ scoped_ptr<base::RunLoop> loop(new base::RunLoop());
+ cache_->CreateBackend(base::Bind(&ServiceWorkerCacheTest::ErrorTypeCallback,
+ base::Unretained(this),
+ base::Unretained(loop.get())));
+ loop->Run();
+ EXPECT_EQ(ServiceWorkerCache::ErrorTypeOK, callback_error_);
+ }
+
+ bool Put(ServiceWorkerFetchRequest* request,
+ ServiceWorkerResponse* response) {
+ scoped_ptr<base::RunLoop> loop(new base::RunLoop());
+
+ cache_->Put(request,
+ response,
+ base::Bind(&ServiceWorkerCacheTest::ErrorTypeCallback,
+ base::Unretained(this),
+ base::Unretained(loop.get())));
+ // TODO(jkarlin): These functions should use base::RunLoop().RunUntilIdle()
+ // once the cache uses a passed in MessageLoopProxy instead of the CACHE
+ // thread.
+ loop->Run();
+
+ return callback_error_ == ServiceWorkerCache::ErrorTypeOK;
+ }
+
+ bool Match(ServiceWorkerFetchRequest* request) {
+ scoped_ptr<base::RunLoop> loop(new base::RunLoop());
+
+ cache_->Match(request,
+ base::Bind(&ServiceWorkerCacheTest::ResponseAndErrorCallback,
+ base::Unretained(this),
+ base::Unretained(loop.get())));
+ loop->Run();
+
+ return callback_error_ == ServiceWorkerCache::ErrorTypeOK;
+ }
+
+ bool Delete(ServiceWorkerFetchRequest* request) {
+ scoped_ptr<base::RunLoop> loop(new base::RunLoop());
+
+ cache_->Delete(request,
+ base::Bind(&ServiceWorkerCacheTest::ErrorTypeCallback,
+ base::Unretained(this),
+ base::Unretained(loop.get())));
+ loop->Run();
+
+ return callback_error_ == ServiceWorkerCache::ErrorTypeOK;
+ }
+
+ void ErrorTypeCallback(base::RunLoop* run_loop,
+ ServiceWorkerCache::ErrorType error) {
+ callback_error_ = error;
+ run_loop->Quit();
+ }
+
+ void ResponseAndErrorCallback(
+ base::RunLoop* run_loop,
+ ServiceWorkerCache::ErrorType error,
+ scoped_ptr<ServiceWorkerResponse> response,
+ scoped_ptr<storage::BlobDataHandle> body_handle) {
+ callback_error_ = error;
+ callback_response_ = response.Pass();
+
+ if (error == ServiceWorkerCache::ErrorTypeOK &&
+ !callback_response_->blob_uuid.empty()) {
+ callback_response_data_ = body_handle.Pass();
+ }
+
+ run_loop->Quit();
+ }
+
+ void CopyBody(storage::BlobDataHandle* blob_handle, std::string* output) {
+ storage::BlobData* data = blob_handle->data();
+ std::vector<storage::BlobData::Item> items = data->items();
+ for (size_t i = 0, max = items.size(); i < max; ++i)
+ output->append(items[i].bytes(), items[i].length());
+ }
+
+ virtual bool MemoryOnly() { return false; }
+
+ protected:
+ TestBrowserContext browser_context_;
+ TestBrowserThreadBundle browser_thread_bundle_;
+ scoped_ptr<net::URLRequestJobFactoryImpl> url_request_job_factory_;
+ storage::BlobStorageContext* blob_storage_context_;
+
+ base::ScopedTempDir temp_dir_;
+ scoped_ptr<ServiceWorkerCache> cache_;
+
+ scoped_ptr<ServiceWorkerFetchRequest> body_request_;
+ scoped_ptr<ServiceWorkerResponse> body_response_;
+ scoped_ptr<ServiceWorkerFetchRequest> no_body_request_;
+ scoped_ptr<ServiceWorkerResponse> no_body_response_;
+ scoped_ptr<storage::BlobDataHandle> blob_handle_;
+ std::string expected_blob_data_;
+
+ ServiceWorkerCache::ErrorType callback_error_;
+ scoped_ptr<ServiceWorkerResponse> callback_response_;
+ scoped_ptr<storage::BlobDataHandle> callback_response_data_;
+};
+
+class ServiceWorkerCacheTestP : public ServiceWorkerCacheTest,
+ public testing::WithParamInterface<bool> {
+ virtual bool MemoryOnly() OVERRIDE { return !GetParam(); }
+};
+
+TEST_P(ServiceWorkerCacheTestP, PutNoBody) {
+ EXPECT_TRUE(Put(no_body_request_.get(), no_body_response_.get()));
+}
+
+TEST_P(ServiceWorkerCacheTestP, PutBody) {
+ EXPECT_TRUE(Put(body_request_.get(), body_response_.get()));
+}
+
+TEST_P(ServiceWorkerCacheTestP, PutBodyDropBlobRef) {
+ scoped_ptr<base::RunLoop> loop(new base::RunLoop());
+ cache_->Put(body_request_.get(),
+ body_response_.get(),
+ base::Bind(&ServiceWorkerCacheTestP::ErrorTypeCallback,
+ base::Unretained(this),
+ base::Unretained(loop.get())));
+ // The handle should be held by the cache now so the deref here should be
+ // okay.
+ blob_handle_.reset();
+ loop->Run();
+
+ EXPECT_EQ(ServiceWorkerCache::ErrorTypeOK, callback_error_);
+}
+
+TEST_P(ServiceWorkerCacheTestP, DeleteNoBody) {
+ EXPECT_TRUE(Put(no_body_request_.get(), no_body_response_.get()));
+ EXPECT_TRUE(Match(no_body_request_.get()));
+ EXPECT_TRUE(Delete(no_body_request_.get()));
+ EXPECT_FALSE(Match(no_body_request_.get()));
+ EXPECT_FALSE(Delete(no_body_request_.get()));
+ EXPECT_TRUE(Put(no_body_request_.get(), no_body_response_.get()));
+ EXPECT_TRUE(Match(no_body_request_.get()));
+ EXPECT_TRUE(Delete(no_body_request_.get()));
+}
+
+TEST_P(ServiceWorkerCacheTestP, DeleteBody) {
+ EXPECT_TRUE(Put(body_request_.get(), body_response_.get()));
+ EXPECT_TRUE(Match(body_request_.get()));
+ EXPECT_TRUE(Delete(body_request_.get()));
+ EXPECT_FALSE(Match(body_request_.get()));
+ EXPECT_FALSE(Delete(body_request_.get()));
+ EXPECT_TRUE(Put(body_request_.get(), body_response_.get()));
+ EXPECT_TRUE(Match(body_request_.get()));
+ EXPECT_TRUE(Delete(body_request_.get()));
+}
+
+TEST_P(ServiceWorkerCacheTestP, MatchNoBody) {
+ EXPECT_TRUE(Put(no_body_request_.get(), no_body_response_.get()));
+ EXPECT_TRUE(Match(no_body_request_.get()));
+ EXPECT_EQ(200, callback_response_->status_code);
+ EXPECT_STREQ("OK", callback_response_->status_text.c_str());
+ EXPECT_STREQ("http://example.com/no_body.html",
+ callback_response_->url.spec().c_str());
+}
+
+TEST_P(ServiceWorkerCacheTestP, MatchBody) {
+ EXPECT_TRUE(Put(body_request_.get(), body_response_.get()));
+ EXPECT_TRUE(Match(body_request_.get()));
+ EXPECT_EQ(200, callback_response_->status_code);
+ EXPECT_STREQ("OK", callback_response_->status_text.c_str());
+ EXPECT_STREQ("http://example.com/body.html",
+ callback_response_->url.spec().c_str());
+ std::string response_body;
+ CopyBody(callback_response_data_.get(), &response_body);
+ EXPECT_STREQ(expected_blob_data_.c_str(), response_body.c_str());
+}
+
+TEST_P(ServiceWorkerCacheTestP, QuickStressNoBody) {
+ for (int i = 0; i < 100; ++i) {
+ EXPECT_FALSE(Match(no_body_request_.get()));
+ EXPECT_TRUE(Put(no_body_request_.get(), no_body_response_.get()));
+ EXPECT_TRUE(Match(no_body_request_.get()));
+ EXPECT_TRUE(Delete(no_body_request_.get()));
+ }
+}
+
+TEST_P(ServiceWorkerCacheTestP, QuickStressBody) {
+ for (int i = 0; i < 100; ++i) {
+ ASSERT_FALSE(Match(body_request_.get()));
+ ASSERT_TRUE(Put(body_request_.get(), body_response_.get()));
+ ASSERT_TRUE(Match(body_request_.get()));
+ ASSERT_TRUE(Delete(body_request_.get()));
+ }
+}
+
+INSTANTIATE_TEST_CASE_P(ServiceWorkerCacheTest,
+ ServiceWorkerCacheTestP,
+ ::testing::Values(false, true));
+
+} // namespace content
diff --git a/content/content_tests.gypi b/content/content_tests.gypi
index 0014055..0796e0e 100644
--- a/content/content_tests.gypi
+++ b/content/content_tests.gypi
@@ -589,6 +589,7 @@
'browser/service_worker/embedded_worker_instance_unittest.cc',
'browser/service_worker/embedded_worker_test_helper.cc',
'browser/service_worker/embedded_worker_test_helper.h',
+ 'browser/service_worker/service_worker_cache_unittest.cc',
'browser/service_worker/service_worker_cache_storage_manager_unittest.cc',
'browser/service_worker/service_worker_context_unittest.cc',
'browser/service_worker/service_worker_controllee_request_handler_unittest.cc',
diff --git a/net/base/io_buffer.cc b/net/base/io_buffer.cc
index dd1d451..a375381 100644
--- a/net/base/io_buffer.cc
+++ b/net/base/io_buffer.cc
@@ -46,6 +46,13 @@ StringIOBuffer::StringIOBuffer(const std::string& s)
data_ = const_cast<char*>(string_data_.data());
}
+StringIOBuffer::StringIOBuffer(scoped_ptr<std::string> s)
+ : IOBuffer(static_cast<char*>(NULL)) {
+ CHECK_LT(s->size(), static_cast<size_t>(INT_MAX));
+ string_data_.swap(*s.get());
+ data_ = const_cast<char*>(string_data_.data());
+}
+
StringIOBuffer::~StringIOBuffer() {
// We haven't allocated the buffer, so remove it before the base class
// destructor tries to delete[] it.
diff --git a/net/base/io_buffer.h b/net/base/io_buffer.h
index 0ce52e8..2d87058 100644
--- a/net/base/io_buffer.h
+++ b/net/base/io_buffer.h
@@ -114,6 +114,7 @@ class NET_EXPORT IOBufferWithSize : public IOBuffer {
class NET_EXPORT StringIOBuffer : public IOBuffer {
public:
explicit StringIOBuffer(const std::string& s);
+ explicit StringIOBuffer(scoped_ptr<std::string> s);
int size() const { return static_cast<int>(string_data_.size()); }