diff options
author | jkarlin <jkarlin@chromium.org> | 2014-08-25 20:59:29 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2014-08-26 04:00:22 +0000 |
commit | b5ca81a3262238668cc4e59a90f51d7e8f6360d4 (patch) | |
tree | fe98f98cebc404df3c882837bdb60c7f5d55af22 | |
parent | 80bc5e3ce73e2a306bec3aca8aa4d54b66b95d04 (diff) | |
download | chromium_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.cc | 676 | ||||
-rw-r--r-- | content/browser/service_worker/service_worker_cache.h | 56 | ||||
-rw-r--r-- | content/browser/service_worker/service_worker_cache.proto | 15 | ||||
-rw-r--r-- | content/browser/service_worker/service_worker_cache_storage.cc | 21 | ||||
-rw-r--r-- | content/browser/service_worker/service_worker_cache_storage.h | 4 | ||||
-rw-r--r-- | content/browser/service_worker/service_worker_cache_unittest.cc | 312 | ||||
-rw-r--r-- | content/content_tests.gypi | 1 | ||||
-rw-r--r-- | net/base/io_buffer.cc | 7 | ||||
-rw-r--r-- | net/base/io_buffer.h | 1 |
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()); } |