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 |