Chromium Code Reviews| Index: content/browser/service_worker/service_worker_cache.cc |
| diff --git a/content/browser/service_worker/service_worker_cache.cc b/content/browser/service_worker/service_worker_cache.cc |
| index d5028f00854f1f2d1241105442ee5f1ec94bb289..c9d5c1f04c79d224f5c895aa99f73844232b502a 100644 |
| --- a/content/browser/service_worker/service_worker_cache.cc |
| +++ b/content/browser/service_worker/service_worker_cache.cc |
| @@ -7,11 +7,149 @@ |
| #include <string> |
| #include "base/files/file_path.h" |
| +#include "base/guid.h" |
| +#include "base/message_loop/message_loop_proxy.h" |
| +#include "content/browser/fileapi/chrome_blob_storage_context.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 { |
| +// The maximum size of an individual cache. |
| +const int kMaxCacheBytes = 5 * 1024 * 1024; |
|
michaeln
2014/08/14 10:35:20
why so small?
jkarlin
2014/08/14 19:53:32
Great question. No idea what's proper. Upped to
|
| + |
| +// Buffer size for cache and blob reading/writing. |
| +const int kBufferSize = 1024 * 512; |
|
michaeln
2014/08/14 10:35:19
why so big :)
jkarlin
2014/08/14 19:53:33
This is the size that we read from the cache when
|
| +} |
| + |
| +struct ServiceWorkerCache::ResponseReadContext { |
| + explicit ResponseReadContext(scoped_refptr<net::IOBufferWithSize> buff, |
|
michaeln
2014/08/14 10:35:20
is explicit needed with two args
jkarlin
2014/08/14 19:53:32
Done.
|
| + scoped_refptr<webkit_blob::BlobData> blob) |
| + : buffer(buff), blob_data(blob), total_bytes_read(0) {} |
| + |
| + scoped_refptr<net::IOBufferWithSize> buffer; |
| + scoped_refptr<webkit_blob::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. |
| +// This class is owned by the callback function passed to Start(). |
| +class ServiceWorkerCache::BlobReader : public net::URLRequest::Delegate { |
|
michaeln
2014/08/14 10:35:20
this is very similar to what ServiceWorkerWriteToC
jkarlin
2014/08/14 19:53:33
True, but not a straight-forward adaptation.
|
| + public: |
| + BlobReader(disk_cache::Entry* entry) |
| + : cache_entry_offset_(0), |
| + entry_(entry), |
| + buffer_(new net::IOBufferWithSize(kBufferSize)) { |
| + DCHECK(entry_); |
| + } |
| + |
| + void StreamBlobToCache( |
| + net::URLRequestContext* request_context, |
| + scoped_ptr<webkit_blob::BlobDataHandle> blob_data_handle, |
| + const BoolCallback& callback) { |
| + callback_ = callback; |
| + blob_request_ = webkit_blob::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 GURL& new_url, |
| + 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(false); |
| + return; |
| + } |
| + ReadFromBlob(); |
| + } |
| + |
| + virtual void ReadFromBlob() { |
| + int bytes_read; |
| + 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(false); |
| + return; |
| + } |
| + |
| + if (bytes_read == 0) { |
| + callback_.Run(true); // what should we really return here? |
| + return; |
| + } |
| + |
| + net::CompletionCallback cache_write_callback = base::Bind( |
| + &BlobReader::DidWriteDataToEntry, base::Unretained(this), bytes_read); |
| + |
| + LOG(ERROR) << "Writing: " << std::string(buffer_->data(), bytes_read); |
| + |
| + int rv = entry_->WriteData(ServiceWorkerCache::INDEX_RESPONSE_BODY, |
| + cache_entry_offset_, |
| + buffer_, |
| + bytes_read, |
| + cache_write_callback, |
| + false /* truncate */); // what should this be? |
| + if (rv != net::ERR_IO_PENDING) { |
| + DidWriteDataToEntry(bytes_read, rv); |
| + } |
| + } |
| + |
| + void DidWriteDataToEntry(int expected_bytes, int rv) { |
| + if (rv != expected_bytes) { |
| + callback_.Run(false); |
| + return; |
| + } |
| + |
| + cache_entry_offset_ += rv; |
| + ReadFromBlob(); |
| + } |
| + |
| + private: |
| + int cache_entry_offset_; |
| + disk_cache::Entry* entry_; |
| + scoped_ptr<net::URLRequest> blob_request_; |
| + BoolCallback callback_; |
| + scoped_refptr<net::IOBufferWithSize> buffer_; |
| +}; |
| + |
| // static |
| scoped_ptr<ServiceWorkerCache> ServiceWorkerCache::CreateMemoryCache( |
| const std::string& name, |
| @@ -31,15 +169,412 @@ 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) { |
| + net::CacheType cache_type = |
| + path_.empty() ? net::MEMORY_CACHE : net::DISK_CACHE; |
| + |
| + net::CompletionCallback create_cache_callback = |
| + base::Bind(&ServiceWorkerCache::CreateBackendDidCreate, |
| + base::Unretained(this), |
| + callback); |
| + int rv = disk_cache::CreateCacheBackend( |
| + cache_type, |
| + net::CACHE_BACKEND_SIMPLE, |
| + path_, |
| + kMaxCacheBytes, |
| + true, /* force */ |
| + BrowserThread::GetMessageLoopProxyForThread(BrowserThread::CACHE).get(), |
|
michaeln
2014/08/14 10:35:20
ServiceWorkerContextCore constructor has the cache
jkarlin
2014/08/14 19:53:32
Agree, added a TODO to do that in another CL.
|
| + NULL, |
| + &backend_, |
|
michaeln
2014/08/14 10:35:20
initialization is treacherous, i think if ServiceW
jkarlin
2014/08/14 19:53:33
The ServiceWorkerCacheStorage::Delete(cache) can't
michaeln1
2014/08/14 22:57:09
The callback won't get called but &backend_ is wri
jkarlin
2014/08/15 11:49:44
Done.
|
| + create_cache_callback); |
| + if (rv != net::ERR_IO_PENDING) |
| + CreateBackendDidCreate(callback, rv); |
| +} |
| + |
| +void ServiceWorkerCache::CreateBackendDidCreate(const ErrorCallback& callback, |
| + int rv) { |
| + if (rv != net::OK) { |
| + callback.Run(ErrorTypeStorage); |
| + return; |
| + } |
| + callback.Run(ErrorTypeOK); |
| +} |
| + |
| +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(); |
| + |
| + net::CompletionCallback create_entry_callback = |
| + base::Bind(&ServiceWorkerCache::PutDidCreateEntry, |
| + base::Unretained(this), |
| + request, |
| + response, |
| + callback, |
| + base::Passed(entry.Pass())); |
| + |
| + 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(&ServiceWorkerCache::MatchDidOpenEntry, |
| + weak_ptr_factory_.GetWeakPtr(), |
| + 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); |
| +} |
| + |
| +void ServiceWorkerCache::MatchDidOpenEntry( |
| + ServiceWorkerFetchRequest* request, |
| + const ResponseCallback& callback, |
| + scoped_ptr<disk_cache::Entry*> entryptr, |
| + int rv) { |
| + if (rv != net::OK) { |
| + callback.Run(ErrorNotFound, |
| + scoped_ptr<ServiceWorkerResponse>(), |
| + scoped_ptr<webkit_blob::BlobDataHandle>()); |
| + return; |
| + } |
| + |
| + DCHECK(entryptr); |
| + disk_cache::Entry* entry = *entryptr; |
| + |
| + scoped_refptr<net::IOBufferWithSize> buffer( |
| + new net::IOBufferWithSize(entry->GetDataSize(INDEX_HEADERS))); |
| + |
| + int read_rv = |
| + entry->ReadData(INDEX_HEADERS, |
| + 0, |
| + buffer.get(), |
| + buffer->size(), |
| + base::Bind(&ServiceWorkerCache::MatchDidReadHeaderData, |
| + weak_ptr_factory_.GetWeakPtr(), |
| + request, |
| + callback, |
| + base::Unretained(entry), |
|
michaeln
2014/08/14 10:35:20
if ServiceWorkerCache is deleted while in flight,
jkarlin
2014/08/14 19:53:32
Yes, thanks. Hadn't gotten to all of the pointers
|
| + buffer)); |
| + if (read_rv != net::ERR_IO_PENDING) |
| + MatchDidReadHeaderData(request, callback, entry, buffer, read_rv); |
| +} |
| + |
| +void ServiceWorkerCache::MatchDidReadHeaderData( |
| + ServiceWorkerFetchRequest* request, |
| + const ResponseCallback& callback, |
| + disk_cache::Entry* entry, |
| + const scoped_refptr<net::IOBufferWithSize>& buffer, |
| + int rv) { |
| + if (rv != buffer->size()) { |
| + entry->Close(); |
| + callback.Run(ErrorTypeStorage, |
| + scoped_ptr<ServiceWorkerResponse>(), |
| + scoped_ptr<webkit_blob::BlobDataHandle>()); |
| + return; |
| + } |
| + |
| + ServiceWorkerRequestResponseHeaders headers; |
| + |
| + if (!headers.ParseFromArray(buffer->data(), buffer->size())) { |
| + entry->Close(); |
| + callback.Run(ErrorTypeStorage, |
| + scoped_ptr<ServiceWorkerResponse>(), |
| + scoped_ptr<webkit_blob::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) { |
| + entry->Close(); |
| + callback.Run(ErrorTypeOK, |
| + response.Pass(), |
| + scoped_ptr<webkit_blob::BlobDataHandle>()); |
| + return; |
| + } |
| + |
| + // Stream the response body into a blob. |
| + if (!blob_storage_context_) { |
| + entry->Close(); |
| + callback.Run(ErrorTypeStorage, |
| + scoped_ptr<ServiceWorkerResponse>(), |
| + scoped_ptr<webkit_blob::BlobDataHandle>()); |
| + return; |
| + } |
| + |
| + response->blob_uuid = base::GenerateGUID(); |
| + |
| + scoped_refptr<webkit_blob::BlobData> blob_data = |
| + new webkit_blob::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)); |
| + |
| + net::CompletionCallback read_callback = |
| + base::Bind(&ServiceWorkerCache::MatchDidReadResponseBodyData, |
| + weak_ptr_factory_.GetWeakPtr(), |
| + request, |
| + callback, |
| + base::Unretained(entry), |
| + base::Passed(response.Pass()), |
| + base::Passed(read_context.Pass())); |
| + |
| + int read_rv = entry->ReadData( |
| + INDEX_RESPONSE_BODY, 0, buffer.get(), buffer->size(), read_callback); |
| + |
| + if (read_rv != net::ERR_IO_PENDING) |
| + read_callback.Run(read_rv); |
| +} |
| + |
| +void ServiceWorkerCache::MatchDidReadResponseBodyData( |
| + ServiceWorkerFetchRequest* request, |
| + const ResponseCallback& callback, |
| + disk_cache::Entry* entry, |
| + scoped_ptr<ServiceWorkerResponse> response, |
| + scoped_ptr<ResponseReadContext> response_context, |
| + int rv) { |
| + if (entry < 0) { |
| + entry->Close(); |
| + callback.Run(ErrorTypeStorage, |
| + scoped_ptr<ServiceWorkerResponse>(), |
| + scoped_ptr<webkit_blob::BlobDataHandle>()); |
| + } |
| + |
| + if (rv == 0) { |
| + entry->Close(); |
| + |
| + MatchDoneWithBody( |
| + request, callback, 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; |
| + |
| + LOG(ERROR) << "Reading: " << std::string(response_context->buffer->data(), |
| + rv); |
| + // Grab the pointer before response_context is Pass()ed. |
| + net::IOBufferWithSize* buffer = response_context->buffer; |
| + |
| + net::CompletionCallback read_callback = |
| + base::Bind(&ServiceWorkerCache::MatchDidReadResponseBodyData, |
| + weak_ptr_factory_.GetWeakPtr(), |
| + request, |
| + callback, |
| + base::Unretained(entry), |
| + base::Passed(response.Pass()), |
| + base::Passed(response_context.Pass())); |
| + |
| + int read_rv = entry->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 ServiceWorkerCache::MatchDoneWithBody( |
| + ServiceWorkerFetchRequest* request, |
| + const ResponseCallback& callback, |
| + scoped_ptr<ServiceWorkerResponse> response, |
| + scoped_ptr<ResponseReadContext> response_context) { |
| + // TODO(jkarlin): Create a blob and pass it back to the renderer. How do we |
| + // reference count that? |
|
michaeln
2014/08/14 10:35:20
1) the uuid is sent from browser->renderer in the
jkarlin
2014/08/14 19:53:32
Acknowledged.
|
| + if (!blob_storage_context_) { |
| + callback.Run(ErrorTypeStorage, |
| + scoped_ptr<ServiceWorkerResponse>(), |
| + scoped_ptr<webkit_blob::BlobDataHandle>()); |
| + return; |
| + } |
| + |
| + scoped_ptr<webkit_blob::BlobDataHandle> blob_data_handle( |
| + blob_storage_context_->AddFinishedBlob( |
| + response_context->blob_data.get())); |
| + |
| + callback.Run(ErrorTypeOK, response.Pass(), blob_data_handle.Pass()); |
| +} |
| + |
| +void ServiceWorkerCache::PutDidCreateEntry( |
| + ServiceWorkerFetchRequest* request, |
| + ServiceWorkerResponse* response, |
| + const ErrorCallback& callback, |
| + scoped_ptr<disk_cache::Entry*> entryptr, |
| + int rv) { |
| + if (rv != net::OK) { |
| + callback.Run(ErrorTypeExists); |
| + return; |
| + } |
| + |
| + DCHECK(entryptr); |
| + disk_cache::Entry* 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_refptr<net::ZeroCopyStringIOBuffer> buffer( |
|
michaeln
2014/08/14 10:35:20
would it make sense for the existing StringIOBuffe
jkarlin
2014/08/14 19:53:33
I like the idea of using std::string:swap. I chan
|
| + new net::ZeroCopyStringIOBuffer()); |
| + if (!headers.SerializeToString(buffer->string())) { |
| + callback.Run(ErrorTypeStorage); |
| + } |
| + |
| + buffer->Done(); |
| + |
| + net::CompletionCallback write_headers_callback = |
| + base::Bind(&ServiceWorkerCache::PutDidWriteHeaders, |
| + weak_ptr_factory_.GetWeakPtr(), |
| + request, |
| + response, |
| + callback, |
| + entry, |
| + buffer->string()->size()); |
| + |
| + rv = entry->WriteData(INDEX_HEADERS, |
| + 0 /* offset */, |
| + buffer, |
| + buffer->string()->size(), |
| + write_headers_callback, |
| + true /* truncate */); |
| + |
| + if (rv != net::ERR_IO_PENDING) |
| + write_headers_callback.Run(rv); |
| +} |
| + |
| +void ServiceWorkerCache::PutDidWriteHeaders(ServiceWorkerFetchRequest* request, |
| + ServiceWorkerResponse* response, |
| + const ErrorCallback& callback, |
| + disk_cache::Entry* entry, |
| + int expected_bytes, |
| + int rv) { |
| + if (rv != expected_bytes) { |
| + entry->Doom(); |
| + entry->Close(); |
| + callback.Run(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()) { |
| + entry->Close(); |
| + callback.Run(ErrorTypeOK); |
| + return; |
| + } |
| + |
| + if (!blob_storage_context_) { |
| + entry->Doom(); |
| + entry->Close(); |
| + callback.Run(ErrorTypeStorage); |
| + return; |
| + } |
| + |
| + scoped_ptr<webkit_blob::BlobDataHandle> blob_data_handle = |
| + blob_storage_context_->GetBlobDataFromUUID(response->blob_uuid); |
|
michaeln
2014/08/14 10:35:20
you should get this handle up front in ServiceWork
jkarlin
2014/08/14 19:53:32
Thanks! Done and added test.
|
| + |
| + if (!blob_data_handle) { |
| + entry->Doom(); |
| + entry->Close(); |
| + callback.Run(ErrorTypeStorage); |
| + return; |
| + } |
| + |
| + scoped_ptr<BlobReader> reader(new BlobReader(entry)); |
| + |
| + reader->StreamBlobToCache( |
| + request_context_, |
| + blob_data_handle.Pass(), |
| + base::Bind(&ServiceWorkerCache::PutDidWriteBlobToCache, |
| + weak_ptr_factory_.GetWeakPtr(), |
| + callback, |
| + entry, |
|
michaeln
2014/08/14 10:35:20
if ServiceWorkerCache is deleted prior to completi
jkarlin
2014/08/14 19:53:33
Done.
|
| + base::Passed(reader.Pass()))); |
| +} |
| + |
| +void ServiceWorkerCache::PutDidWriteBlobToCache( |
| + const ErrorCallback& callback, |
| + disk_cache::Entry* entry, |
| + scoped_ptr<BlobReader> blob_reader, |
| + bool success) { |
| + if (!success) { |
| + entry->Doom(); |
| + entry->Close(); |
| + callback.Run(ErrorTypeStorage); |
| + return; |
| + } |
| + |
| + entry->Close(); |
| + callback.Run(ErrorTypeOK); |
| +} |
| + |
| ServiceWorkerCache::ServiceWorkerCache( |
| const base::FilePath& path, |
| const std::string& name, |
| @@ -53,7 +588,4 @@ ServiceWorkerCache::ServiceWorkerCache( |
| weak_ptr_factory_(this) { |
| } |
| -ServiceWorkerCache::~ServiceWorkerCache() { |
| -} |
| - |
| } // namespace content |