diff --git a/content/browser/appcache/appcache_response.h b/content/browser/appcache/appcache_response.h index a4c87db3d562ac..8d6c79060bdcf2 100644 --- a/content/browser/appcache/appcache_response.h +++ b/content/browser/appcache/appcache_response.h @@ -209,9 +209,8 @@ class CONTENT_EXPORT AppCacheResponseWriter // negative error code or the number of bytes written. The 'callback' is a // required parameter. The contents of 'info_buf' are not modified. // Should only be called where there is no Write operation in progress. - // (virtual for testing) - virtual void WriteInfo(HttpResponseInfoIOBuffer* info_buf, - const net::CompletionCallback& callback); + void WriteInfo(HttpResponseInfoIOBuffer* info_buf, + const net::CompletionCallback& callback); // Writes data to storage. Always returns the result of the write // asynchronously through the 'callback'. Returns the number of bytes written @@ -221,10 +220,8 @@ class CONTENT_EXPORT AppCacheResponseWriter // the number of bytes written. The 'callback' is a required parameter. // The contents of 'buf' are not modified. // Should only be called where there is no Write operation in progress. - // (virtual for testing) - virtual void WriteData(net::IOBuffer* buf, - int buf_len, - const net::CompletionCallback& callback); + void WriteData(net::IOBuffer* buf, int buf_len, + const net::CompletionCallback& callback); // Returns true if there is a write pending. bool IsWritePending() { return IsIOPending(); } diff --git a/content/browser/service_worker/service_worker_cache_writer.cc b/content/browser/service_worker/service_worker_cache_writer.cc deleted file mode 100644 index e9343009910e63..00000000000000 --- a/content/browser/service_worker/service_worker_cache_writer.cc +++ /dev/null @@ -1,498 +0,0 @@ -// Copyright 2015 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_writer.h" - -#include -#include - -#include "content/browser/appcache/appcache_response.h" -#include "content/browser/service_worker/service_worker_disk_cache.h" -#include "content/browser/service_worker/service_worker_storage.h" - -namespace { - -const size_t kCopyBufferSize = 16 * 1024; - -// Shim class used to turn always-async functions into async-or-result -// functions. See the comments below near ReadInfoHelper. -class AsyncOnlyCompletionCallbackAdaptor - : public base::RefCounted { - public: - explicit AsyncOnlyCompletionCallbackAdaptor( - const net::CompletionCallback& callback) - : async_(false), result_(net::ERR_IO_PENDING), callback_(callback) {} - - void set_async(bool async) { async_ = async; } - bool async() { return async_; } - int result() { return result_; } - - void WrappedCallback(int result) { - result_ = result; - if (async_) - callback_.Run(result); - } - - private: - friend class base::RefCounted; - virtual ~AsyncOnlyCompletionCallbackAdaptor() {} - - bool async_; - int result_; - net::CompletionCallback callback_; -}; - -} // namespace - -namespace content { - -int ServiceWorkerCacheWriter::DoLoop(int status) { - do { - switch (state_) { - case STATE_START: - status = DoStart(status); - break; - case STATE_READ_HEADERS_FOR_COMPARE: - status = DoReadHeadersForCompare(status); - break; - case STATE_READ_HEADERS_FOR_COMPARE_DONE: - status = DoReadHeadersForCompareDone(status); - break; - case STATE_READ_DATA_FOR_COMPARE: - status = DoReadDataForCompare(status); - break; - case STATE_READ_DATA_FOR_COMPARE_DONE: - status = DoReadDataForCompareDone(status); - break; - case STATE_READ_HEADERS_FOR_COPY: - status = DoReadHeadersForCopy(status); - break; - case STATE_READ_HEADERS_FOR_COPY_DONE: - status = DoReadHeadersForCopyDone(status); - break; - case STATE_READ_DATA_FOR_COPY: - status = DoReadDataForCopy(status); - break; - case STATE_READ_DATA_FOR_COPY_DONE: - status = DoReadDataForCopyDone(status); - break; - case STATE_WRITE_HEADERS_FOR_PASSTHROUGH: - status = DoWriteHeadersForPassthrough(status); - break; - case STATE_WRITE_HEADERS_FOR_PASSTHROUGH_DONE: - status = DoWriteHeadersForPassthroughDone(status); - break; - case STATE_WRITE_DATA_FOR_PASSTHROUGH: - status = DoWriteDataForPassthrough(status); - break; - case STATE_WRITE_DATA_FOR_PASSTHROUGH_DONE: - status = DoWriteDataForPassthroughDone(status); - break; - case STATE_WRITE_HEADERS_FOR_COPY: - status = DoWriteHeadersForCopy(status); - break; - case STATE_WRITE_HEADERS_FOR_COPY_DONE: - status = DoWriteHeadersForCopyDone(status); - break; - case STATE_WRITE_DATA_FOR_COPY: - status = DoWriteDataForCopy(status); - break; - case STATE_WRITE_DATA_FOR_COPY_DONE: - status = DoWriteDataForCopyDone(status); - break; - case STATE_DONE: - status = DoDone(status); - break; - default: - NOTREACHED() << "Unknown state in DoLoop"; - state_ = STATE_DONE; - break; - } - } while (status >= net::OK && state_ != STATE_DONE); - io_pending_ = (status == net::ERR_IO_PENDING); - return status; -} - -ServiceWorkerCacheWriter::ServiceWorkerCacheWriter( - const ResponseReaderCreator& reader_creator, - const ResponseWriterCreator& writer_creator) - : state_(STATE_START), - io_pending_(false), - comparing_(false), - did_replace_(false), - reader_creator_(reader_creator), - writer_creator_(writer_creator), - weak_factory_(this) {} - -ServiceWorkerCacheWriter::~ServiceWorkerCacheWriter() {} - -net::Error ServiceWorkerCacheWriter::MaybeWriteHeaders( - HttpResponseInfoIOBuffer* headers, - const OnWriteCompleteCallback& callback) { - DCHECK(!io_pending_); - - headers_to_write_ = headers; - pending_callback_ = callback; - DCHECK_EQ(state_, STATE_START); - int result = DoLoop(net::OK); - - // Synchronous errors and successes always go to STATE_DONE. - if (result != net::ERR_IO_PENDING) - DCHECK_EQ(state_, STATE_DONE); - - // ERR_IO_PENDING has to have one of the STATE_*_DONE states as the next state - // (not STATE_DONE itself). - if (result == net::ERR_IO_PENDING) { - DCHECK(state_ == STATE_READ_HEADERS_FOR_COMPARE_DONE || - state_ == STATE_WRITE_HEADERS_FOR_COPY_DONE || - state_ == STATE_WRITE_HEADERS_FOR_PASSTHROUGH_DONE) - << "Unexpected state: " << state_; - io_pending_ = true; - } - - return result >= 0 ? net::OK : static_cast(result); -} - -net::Error ServiceWorkerCacheWriter::MaybeWriteData( - net::IOBuffer* buf, - size_t buf_size, - const OnWriteCompleteCallback& callback) { - DCHECK(!io_pending_); - - data_to_write_ = buf; - len_to_write_ = buf_size; - pending_callback_ = callback; - - if (comparing_) - state_ = STATE_READ_DATA_FOR_COMPARE; - else - state_ = STATE_WRITE_DATA_FOR_PASSTHROUGH; - - int result = DoLoop(net::OK); - - // Synchronous completions are always STATE_DONE. - if (result != net::ERR_IO_PENDING) - DCHECK_EQ(state_, STATE_DONE); - - // Asynchronous completion means the state machine must be waiting in one of - // the Done states for an IO operation to complete: - if (result == net::ERR_IO_PENDING) { - // Note that STATE_READ_HEADERS_FOR_COMPARE_DONE is excluded because the - // headers are compared in MaybeWriteHeaders, not here, and - // STATE_WRITE_HEADERS_FOR_PASSTHROUGH_DONE is excluded because that write - // is done by MaybeWriteHeaders. - DCHECK(state_ == STATE_READ_DATA_FOR_COMPARE_DONE || - state_ == STATE_READ_HEADERS_FOR_COPY_DONE || - state_ == STATE_READ_DATA_FOR_COPY_DONE || - state_ == STATE_WRITE_HEADERS_FOR_COPY_DONE || - state_ == STATE_WRITE_DATA_FOR_COPY_DONE || - state_ == STATE_WRITE_DATA_FOR_PASSTHROUGH_DONE) - << "Unexpected state: " << state_; - } - - return result >= 0 ? net::OK : static_cast(result); -} - -int ServiceWorkerCacheWriter::DoStart(int result) { - bytes_written_ = 0; - compare_reader_ = reader_creator_.Run(); - if (compare_reader_.get()) { - state_ = STATE_READ_HEADERS_FOR_COMPARE; - comparing_ = true; - } else { - // No existing reader, just write the headers back directly. - state_ = STATE_WRITE_HEADERS_FOR_PASSTHROUGH; - comparing_ = false; - } - return net::OK; -} - -int ServiceWorkerCacheWriter::DoReadHeadersForCompare(int result) { - DCHECK(headers_to_write_); - - headers_to_read_ = new HttpResponseInfoIOBuffer; - state_ = STATE_READ_HEADERS_FOR_COMPARE_DONE; - return ReadInfoHelper(compare_reader_, headers_to_read_.get()); -} - -int ServiceWorkerCacheWriter::DoReadHeadersForCompareDone(int result) { - if (result < 0) { - state_ = STATE_DONE; - return result; - } - cached_length_ = headers_to_read_->response_data_size; - bytes_compared_ = 0; - state_ = STATE_DONE; - return net::OK; -} - -int ServiceWorkerCacheWriter::DoReadDataForCompare(int result) { - DCHECK(data_to_write_); - - data_to_read_ = new net::IOBuffer(len_to_write_); - len_to_read_ = len_to_write_; - state_ = STATE_READ_DATA_FOR_COMPARE_DONE; - compare_offset_ = 0; - // If this was an EOF, don't issue a read. - if (len_to_write_ > 0) - result = ReadDataHelper(compare_reader_, data_to_read_.get(), len_to_read_); - return result; -} - -int ServiceWorkerCacheWriter::DoReadDataForCompareDone(int result) { - DCHECK(data_to_read_); - DCHECK(data_to_write_); - DCHECK_EQ(len_to_read_, len_to_write_); - DCHECK_LE(result + compare_offset_, static_cast(len_to_write_)); - - if (result < 0) { - state_ = STATE_DONE; - return result; - } - - // Premature EOF while reading the service worker script cache data to - // compare. Fail the comparison. - if (result == 0 && len_to_write_ != 0) { - comparing_ = false; - state_ = STATE_READ_HEADERS_FOR_COPY; - return net::OK; - } - - // Compare the data from the ServiceWorker script cache to the data from the - // network. - if (memcmp(data_to_read_->data(), data_to_write_->data() + compare_offset_, - result)) { - // Data mismatched. This method already validated that all the bytes through - // |bytes_compared_| were identical, so copy the first |bytes_compared_| - // over, then start writing network data back after the changed point. - comparing_ = false; - state_ = STATE_READ_HEADERS_FOR_COPY; - return net::OK; - } - - compare_offset_ += result; - - // This is a little bit tricky. It is possible that not enough data was read - // to finish comparing the entire block of data from the network (which is - // kept in len_to_write_), so this method may need to issue another read and - // return to this state. - // - // Compare isn't complete yet. Issue another read for the remaining data. Note - // that this reuses the same IOBuffer. - if (compare_offset_ < static_cast(len_to_read_)) { - state_ = STATE_READ_DATA_FOR_COMPARE_DONE; - return ReadDataHelper(compare_reader_, data_to_read_.get(), - len_to_read_ - compare_offset_); - } - - // Cached entry is longer than the network entry but the prefix matches. Copy - // just the prefix. - if (len_to_read_ == 0 && bytes_compared_ + compare_offset_ < cached_length_) { - comparing_ = false; - state_ = STATE_READ_HEADERS_FOR_COPY; - return net::OK; - } - - // bytes_compared_ only gets incremented when a full block is compared, to - // avoid having to use only parts of the buffered network data. - bytes_compared_ += result; - state_ = STATE_DONE; - return net::OK; -} - -int ServiceWorkerCacheWriter::DoReadHeadersForCopy(int result) { - bytes_copied_ = 0; - copy_reader_ = reader_creator_.Run(); - headers_to_read_ = new HttpResponseInfoIOBuffer; - data_to_copy_ = new net::IOBuffer(kCopyBufferSize); - state_ = STATE_READ_HEADERS_FOR_COPY_DONE; - return ReadInfoHelper(copy_reader_, headers_to_read_.get()); -} - -int ServiceWorkerCacheWriter::DoReadHeadersForCopyDone(int result) { - if (result < 0) { - state_ = STATE_DONE; - return result; - } - state_ = STATE_WRITE_HEADERS_FOR_COPY; - return net::OK; -} - -// Write the just-read headers back to the cache. -// Note that this method must create |writer_|, since the only paths to this -// state never create a writer. -// Also note that this *discards* the read headers and replaces them with the -// net headers. -int ServiceWorkerCacheWriter::DoWriteHeadersForCopy(int result) { - DCHECK(!writer_); - writer_ = writer_creator_.Run(); - state_ = STATE_WRITE_HEADERS_FOR_COPY_DONE; - return WriteInfoHelper(writer_, headers_to_write_.get()); -} - -int ServiceWorkerCacheWriter::DoWriteHeadersForCopyDone(int result) { - if (result < 0) { - state_ = STATE_DONE; - return result; - } - state_ = STATE_READ_DATA_FOR_COPY; - return net::OK; -} - -int ServiceWorkerCacheWriter::DoReadDataForCopy(int result) { - size_t to_read = std::min(kCopyBufferSize, bytes_compared_ - bytes_copied_); - // At this point, all compared bytes have been read. Currently - // |data_to_write_| and |len_to_write_| hold the chunk of network input that - // caused the comparison failure, so those need to be written back and this - // object needs to go into passthrough mode. - if (to_read == 0) { - state_ = STATE_WRITE_DATA_FOR_PASSTHROUGH; - return net::OK; - } - state_ = STATE_READ_DATA_FOR_COPY_DONE; - return ReadDataHelper(copy_reader_, data_to_copy_.get(), to_read); -} - -int ServiceWorkerCacheWriter::DoReadDataForCopyDone(int result) { - if (result < 0) { - state_ = STATE_DONE; - return result; - } - state_ = STATE_WRITE_DATA_FOR_COPY; - return result; -} - -int ServiceWorkerCacheWriter::DoWriteDataForCopy(int result) { - state_ = STATE_WRITE_DATA_FOR_COPY_DONE; - DCHECK_GT(result, 0); - return WriteDataHelper(writer_, data_to_copy_.get(), result); -} - -int ServiceWorkerCacheWriter::DoWriteDataForCopyDone(int result) { - if (result < 0) { - state_ = STATE_DONE; - return result; - } - bytes_written_ += result; - bytes_copied_ += result; - state_ = STATE_READ_DATA_FOR_COPY; - return result; -} - -int ServiceWorkerCacheWriter::DoWriteHeadersForPassthrough(int result) { - writer_ = writer_creator_.Run(); - state_ = STATE_WRITE_HEADERS_FOR_PASSTHROUGH_DONE; - return WriteInfoHelper(writer_, headers_to_write_.get()); -} - -int ServiceWorkerCacheWriter::DoWriteHeadersForPassthroughDone(int result) { - state_ = STATE_DONE; - return net::OK; -} - -int ServiceWorkerCacheWriter::DoWriteDataForPassthrough(int result) { - state_ = STATE_WRITE_DATA_FOR_PASSTHROUGH_DONE; - if (len_to_write_ > 0) - result = WriteDataHelper(writer_, data_to_write_.get(), len_to_write_); - return result; -} - -int ServiceWorkerCacheWriter::DoWriteDataForPassthroughDone(int result) { - if (result < 0) { - state_ = STATE_DONE; - return result; - } - bytes_written_ += result; - state_ = STATE_DONE; - return net::OK; -} - -int ServiceWorkerCacheWriter::DoDone(int result) { - state_ = STATE_DONE; - return net::OK; -} - -// These helpers adapt the AppCache "always use the callback" pattern to the -// //net "only use the callback for async" pattern using -// AsyncCompletionCallbackAdaptor. -// -// Specifically, these methods return result codes directly for synchronous -// completions, and only run their callback (which is AsyncDoLoop) for -// asynchronous completions. - -int ServiceWorkerCacheWriter::ReadInfoHelper( - const scoped_ptr& reader, - HttpResponseInfoIOBuffer* buf) { - net::CompletionCallback run_callback = base::Bind( - &ServiceWorkerCacheWriter::AsyncDoLoop, weak_factory_.GetWeakPtr()); - scoped_refptr adaptor( - new AsyncOnlyCompletionCallbackAdaptor(run_callback)); - reader->ReadInfo( - buf, base::Bind(&AsyncOnlyCompletionCallbackAdaptor::WrappedCallback, - adaptor)); - adaptor->set_async(true); - return adaptor->result(); -} - -int ServiceWorkerCacheWriter::ReadDataHelper( - const scoped_ptr& reader, - net::IOBuffer* buf, - int buf_len) { - net::CompletionCallback run_callback = base::Bind( - &ServiceWorkerCacheWriter::AsyncDoLoop, weak_factory_.GetWeakPtr()); - scoped_refptr adaptor( - new AsyncOnlyCompletionCallbackAdaptor(run_callback)); - reader->ReadData( - buf, buf_len, - base::Bind(&AsyncOnlyCompletionCallbackAdaptor::WrappedCallback, - adaptor)); - adaptor->set_async(true); - return adaptor->result(); -} - -int ServiceWorkerCacheWriter::WriteInfoHelper( - const scoped_ptr& writer, - HttpResponseInfoIOBuffer* buf) { - did_replace_ = true; - net::CompletionCallback run_callback = base::Bind( - &ServiceWorkerCacheWriter::AsyncDoLoop, weak_factory_.GetWeakPtr()); - scoped_refptr adaptor( - new AsyncOnlyCompletionCallbackAdaptor(run_callback)); - writer->WriteInfo( - buf, base::Bind(&AsyncOnlyCompletionCallbackAdaptor::WrappedCallback, - adaptor)); - adaptor->set_async(true); - return adaptor->result(); -} - -int ServiceWorkerCacheWriter::WriteDataHelper( - const scoped_ptr& writer, - net::IOBuffer* buf, - int buf_len) { - net::CompletionCallback run_callback = base::Bind( - &ServiceWorkerCacheWriter::AsyncDoLoop, weak_factory_.GetWeakPtr()); - scoped_refptr adaptor( - new AsyncOnlyCompletionCallbackAdaptor(run_callback)); - writer->WriteData( - buf, buf_len, - base::Bind(&AsyncOnlyCompletionCallbackAdaptor::WrappedCallback, - adaptor)); - adaptor->set_async(true); - return adaptor->result(); -} - -void ServiceWorkerCacheWriter::AsyncDoLoop(int result) { - result = DoLoop(result); - // If the result is ERR_IO_PENDING, the pending callback will be run by a - // later invocation of AsyncDoLoop. - if (result != net::ERR_IO_PENDING) { - OnWriteCompleteCallback callback = pending_callback_; - pending_callback_.Reset(); - net::Error error = result >= 0 ? net::OK : static_cast(result); - io_pending_ = false; - callback.Run(error); - } -} - -} // namespace content diff --git a/content/browser/service_worker/service_worker_cache_writer.h b/content/browser/service_worker/service_worker_cache_writer.h deleted file mode 100644 index dde71a69fe799a..00000000000000 --- a/content/browser/service_worker/service_worker_cache_writer.h +++ /dev/null @@ -1,230 +0,0 @@ -// Copyright 2015 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_CACHE_WRITER_H_ -#define CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_CACHE_WRITER_H_ - -#include -#include - -#include "base/callback.h" -#include "base/memory/scoped_ptr.h" -#include "base/memory/weak_ptr.h" -#include "content/common/content_export.h" -#include "net/base/io_buffer.h" -#include "net/base/net_errors.h" - -namespace content { - -struct HttpResponseInfoIOBuffer; -class ServiceWorkerCacheWriterCore; -class ServiceWorkerResponseReader; -class ServiceWorkerResponseWriter; -class ServiceWorkerStorage; - -// This class is responsible for possibly updating the ServiceWorker script -// cache for an installed ServiceWorker main script. If there is no existing -// cache entry, this class always writes supplied data back to the cache; if -// there is an existing cache entry, this class only writes supplied data back -// if there is a cache mismatch. -// -// Note that writes done by this class cannot be "short" - ie, if they succeed, -// they always write all the supplied data back. Therefore completions are -// signalled with net::Error without a count of bytes written. -// -// This class's behavior is modelled as a state machine; see the DoLoop function -// for comments about this. -class CONTENT_EXPORT ServiceWorkerCacheWriter { - public: - using OnWriteCompleteCallback = base::Callback; - - // The types for the factory functions passed into the constructor. These are - // responsible for creating readers from the existing cache entry and writers - // to the new cache entry when called. These are passed in as factories - // instead of passing readers and writers in directly to avoid creating - // writers to entries that won't be updated, and because this class may need - // multiple readers internally. - using ResponseReaderCreator = - base::Callback(void)>; - using ResponseWriterCreator = - base::Callback(void)>; - - // The existing reader may be null, in which case this instance will - // unconditionally write back data supplied to |MaybeWriteHeaders| and - // |MaybeWriteData|. - ServiceWorkerCacheWriter(const ResponseReaderCreator& reader_creator, - const ResponseWriterCreator& writer_creator); - - ~ServiceWorkerCacheWriter(); - - // Writes the supplied |headers| back to the cache. Returns ERR_IO_PENDING if - // the write will complete asynchronously, in which case |callback| will be - // called when it completes. Otherwise, returns a code other than - // ERR_IO_PENDING and does not invoke |callback|. Note that this method will - // not necessarily write data back to the cache if the incoming data is - // equivalent to the existing cached data. See the source of this function for - // details about how this function drives the state machine. - net::Error MaybeWriteHeaders(HttpResponseInfoIOBuffer* headers, - const OnWriteCompleteCallback& callback); - - // Writes the supplied body data |data| back to the cache. Returns - // ERR_IO_PENDING if the write will complete asynchronously, in which case - // |callback| will be called when it completes. Otherwise, returns a code - // other than ERR_IO_PENDING and does not invoke |callback|. Note that this - // method will not necessarily write data back to the cache if the incoming - // data is equivalent to the existing cached data. See the source of this - // function for details about how this function drives the state machine. - net::Error MaybeWriteData(net::IOBuffer* buf, - size_t buf_size, - const OnWriteCompleteCallback& callback); - - // Returns a count of bytes written back to the cache. - size_t bytes_written() const { return bytes_written_; } - bool did_replace() const { return did_replace_; } - - private: - // States for the state machine. - // - // The state machine flows roughly like this: if there is no existing cache - // entry, incoming headers and data are written directly back to the cache - // ("passthrough mode", the PASSTHROUGH states). If there is an existing cache - // entry, incoming headers and data are compared to the existing cache entry - // ("compare mode", the COMPARE states); if at any point the incoming - // headers/data are not equal to the cached headers/data, this class copies - // the cached data up to the point where the incoming data and the cached data - // diverged ("copy mode", the COPY states), then switches to "passthrough - // mode" to write the remainder of the incoming data. The overall effect is to - // avoid rewriting the cache entry if the incoming data is identical to the - // cached data. - // - // Note that after a call to MaybeWriteHeaders or MaybeWriteData completes, - // the machine is always in STATE_DONE, indicating that the call is finished; - // those methods are responsible for setting a new initial state. - enum State { - STATE_START, - // Control flows linearly through these four states, then loops from - // READ_DATA_FOR_COMPARE_DONE to READ_DATA_FOR_COMPARE, or exits to - // READ_HEADERS_FOR_COPY. - STATE_READ_HEADERS_FOR_COMPARE, - STATE_READ_HEADERS_FOR_COMPARE_DONE, - STATE_READ_DATA_FOR_COMPARE, - STATE_READ_DATA_FOR_COMPARE_DONE, - - // Control flows linearly through these states, with each pass from - // READ_DATA_FOR_COPY to WRITE_DATA_FOR_COPY_DONE copying one block of data - // at a time. Control loops from WRITE_DATA_FOR_COPY_DONE back to - // READ_DATA_FOR_COPY if there is more data to copy, or exits to - // WRITE_DATA_FOR_PASSTHROUGH. - STATE_READ_HEADERS_FOR_COPY, - STATE_READ_HEADERS_FOR_COPY_DONE, - STATE_WRITE_HEADERS_FOR_COPY, - STATE_WRITE_HEADERS_FOR_COPY_DONE, - STATE_READ_DATA_FOR_COPY, - STATE_READ_DATA_FOR_COPY_DONE, - STATE_WRITE_DATA_FOR_COPY, - STATE_WRITE_DATA_FOR_COPY_DONE, - - // Control flows linearly through these states, with a loop between - // WRITE_DATA_FOR_PASSTHROUGH and WRITE_DATA_FOR_PASSTHROUGH_DONE. - STATE_WRITE_HEADERS_FOR_PASSTHROUGH, - STATE_WRITE_HEADERS_FOR_PASSTHROUGH_DONE, - STATE_WRITE_DATA_FOR_PASSTHROUGH, - STATE_WRITE_DATA_FOR_PASSTHROUGH_DONE, - - // This state means "done with the current call; ready for another one." - STATE_DONE, - }; - - // Drives this class's state machine. This function steps the state machine - // until one of: - // a) One of the state functions returns an error - // b) The state machine reaches STATE_DONE - // A successful value (net::OK or greater) indicates that the requested - // operation completed synchronously. A return value of ERR_IO_PENDING - // indicates that some step had to submit asynchronous IO for later - // completion, and the state machine will resume running (via AsyncDoLoop) - // when that asynchronous IO completes. Any other return value indicates that - // the requested operation failed synchronously. - int DoLoop(int result); - - // State handlers. See function comments in the corresponding source file for - // details on these. - int DoStart(int result); - int DoReadHeadersForCompare(int result); - int DoReadHeadersForCompareDone(int result); - int DoReadDataForCompare(int result); - int DoReadDataForCompareDone(int result); - int DoReadHeadersForCopy(int result); - int DoReadHeadersForCopyDone(int result); - int DoWriteHeadersForCopy(int result); - int DoWriteHeadersForCopyDone(int result); - int DoReadDataForCopy(int result); - int DoReadDataForCopyDone(int result); - int DoWriteDataForCopy(int result); - int DoWriteDataForCopyDone(int result); - int DoWriteHeadersForPassthrough(int result); - int DoWriteHeadersForPassthroughDone(int result); - int DoWriteDataForPassthrough(int result); - int DoWriteDataForPassthroughDone(int result); - int DoDone(int result); - - // Wrappers for asynchronous calls. These are responsible for scheduling a - // callback to drive the state machine if needed. These either: - // a) Return ERR_IO_PENDING, and schedule a callback to run the state - // machine's Run() later, or - // b) Return some other value and do not schedule a callback. - int ReadInfoHelper(const scoped_ptr& reader, - HttpResponseInfoIOBuffer* buf); - int ReadDataHelper(const scoped_ptr& reader, - net::IOBuffer* buf, - int buf_len); - int WriteInfoHelper(const scoped_ptr& writer, - HttpResponseInfoIOBuffer* buf); - int WriteDataHelper(const scoped_ptr& writer, - net::IOBuffer* buf, - int buf_len); - - // Callback used by the above helpers for their IO operations. This is only - // run when those IO operations complete asynchronously, in which case it - // invokes the synchronous DoLoop function and runs the client callback (the - // one passed into MaybeWriteData/MaybeWriteHeaders) if that invocation - // of DoLoop completes synchronously. - void AsyncDoLoop(int result); - - State state_; - // Note that this variable is only used for assertions; it reflects "state != - // DONE && not in synchronous DoLoop". - bool io_pending_; - bool comparing_; - - scoped_refptr headers_to_read_; - scoped_refptr headers_to_write_; - scoped_refptr data_to_read_; - int len_to_read_; - scoped_refptr data_to_copy_; - scoped_refptr data_to_write_; - int len_to_write_; - OnWriteCompleteCallback pending_callback_; - - size_t cached_length_; - - size_t bytes_compared_; - size_t bytes_copied_; - size_t bytes_written_; - - bool did_replace_; - - size_t compare_offset_; - - ResponseReaderCreator reader_creator_; - ResponseWriterCreator writer_creator_; - scoped_ptr compare_reader_; - scoped_ptr copy_reader_; - scoped_ptr writer_; - base::WeakPtrFactory weak_factory_; -}; - -} // namespace content - -#endif // CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_CACHE_WRITER_H_ diff --git a/content/browser/service_worker/service_worker_cache_writer_unittest.cc b/content/browser/service_worker/service_worker_cache_writer_unittest.cc deleted file mode 100644 index c58b57b52c117e..00000000000000 --- a/content/browser/service_worker/service_worker_cache_writer_unittest.cc +++ /dev/null @@ -1,697 +0,0 @@ -// Copyright 2015 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_writer.h" - -#include -#include -#include - -#include "base/stl_util.h" -#include "content/browser/service_worker/service_worker_disk_cache.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace content { -namespace { - -// A test implementation of ServiceWorkerResponseReader. -// -// This class exposes the ability to expect reads (see ExpectRead*() below). -// Each call to ReadInfo() or ReadData() consumes another expected read, in the -// order those reads were expected, so: -// reader->ExpectReadInfoOk(5, false); -// reader->ExpectReadDataOk("abcdef", false); -// reader->ExpectReadDataOk("ghijkl", false); -// Expects these calls, in this order: -// reader->ReadInfo(...); // reader writes 5 into -// // |info_buf->response_data_size| -// reader->ReadData(...); // reader writes "abcdef" into |buf| -// reader->ReadData(...); // reader writes "ghijkl" into |buf| -// If an unexpected call happens, this class DCHECKs. -// If an expected read is marked "async", it will not complete immediately, but -// must be completed by the test using CompletePendingRead(). -// These is a convenience method AllExpectedReadsDone() which returns whether -// there are any expected reads that have not yet happened. -class MockServiceWorkerResponseReader : public ServiceWorkerResponseReader { - public: - MockServiceWorkerResponseReader() : ServiceWorkerResponseReader(0, nullptr) {} - ~MockServiceWorkerResponseReader() override {} - - // ServiceWorkerResponseReader overrides - void ReadInfo(HttpResponseInfoIOBuffer* info_buf, - const net::CompletionCallback& callback) override; - void ReadData(net::IOBuffer* buf, - int buf_len, - const net::CompletionCallback& callback) override; - - // Test helpers. ExpectReadInfo() and ExpectReadData() give precise control - // over both the data to be written and the result to return. - // ExpectReadInfoOk() and ExpectReadDataOk() are convenience functions for - // expecting successful reads, which always have their length as their result. - - // Expect a call to ReadInfo() on this reader. For these functions, |len| will - // be used as |response_data_size|, not as the length of this particular read. - void ExpectReadInfo(size_t len, bool async, int result); - void ExpectReadInfoOk(size_t len, bool async); - - // Expect a call to ReadData() on this reader. For these functions, |len| is - // the length of the data to be written back; in ExpectReadDataOk(), |len| is - // implicitly the length of |data|. - void ExpectReadData(const char* data, size_t len, bool async, int result); - void ExpectReadDataOk(const std::string& data, bool async); - - // Complete a pending async read. It is an error to call this function without - // a pending async read (ie, a previous call to ReadInfo() or ReadData() - // having not run its callback yet). - void CompletePendingRead(); - - // Returns whether all expected reads have occurred. - bool AllExpectedReadsDone() { return expected_reads_.size() == 0; } - - private: - struct ExpectedRead { - ExpectedRead(size_t len, bool async, int result) - : data(nullptr), len(len), info(true), async(async), result(result) {} - ExpectedRead(const char* data, size_t len, bool async, int result) - : data(data), len(len), info(false), async(async), result(result) {} - const char* data; - size_t len; - bool info; - bool async; - int result; - }; - - std::queue expected_reads_; - scoped_refptr pending_buffer_; - size_t pending_buffer_len_; - scoped_refptr pending_info_; - net::CompletionCallback pending_callback_; -}; - -void MockServiceWorkerResponseReader::ReadInfo( - HttpResponseInfoIOBuffer* info_buf, - const net::CompletionCallback& callback) { - DCHECK(!expected_reads_.empty()); - ExpectedRead expected = expected_reads_.front(); - EXPECT_TRUE(expected.info); - if (expected.async) { - pending_info_ = info_buf; - pending_callback_ = callback; - } else { - expected_reads_.pop(); - info_buf->response_data_size = expected.len; - callback.Run(expected.result); - } -} - -void MockServiceWorkerResponseReader::ReadData( - net::IOBuffer* buf, - int buf_len, - const net::CompletionCallback& callback) { - DCHECK(!expected_reads_.empty()); - ExpectedRead expected = expected_reads_.front(); - EXPECT_FALSE(expected.info); - if (expected.async) { - pending_callback_ = callback; - pending_buffer_ = buf; - pending_buffer_len_ = static_cast(buf_len); - } else { - expected_reads_.pop(); - if (expected.len > 0) { - size_t to_read = std::min(static_cast(buf_len), expected.len); - memcpy(buf->data(), expected.data, to_read); - } - callback.Run(expected.result); - } -} - -void MockServiceWorkerResponseReader::ExpectReadInfo(size_t len, - bool async, - int result) { - expected_reads_.push(ExpectedRead(len, async, result)); -} - -void MockServiceWorkerResponseReader::ExpectReadInfoOk(size_t len, bool async) { - expected_reads_.push(ExpectedRead(len, async, len)); -} - -void MockServiceWorkerResponseReader::ExpectReadData(const char* data, - size_t len, - bool async, - int result) { - expected_reads_.push(ExpectedRead(data, len, async, result)); -} - -void MockServiceWorkerResponseReader::ExpectReadDataOk(const std::string& data, - bool async) { - expected_reads_.push( - ExpectedRead(data.data(), data.size(), async, data.size())); -} - -void MockServiceWorkerResponseReader::CompletePendingRead() { - DCHECK(!expected_reads_.empty()); - ExpectedRead expected = expected_reads_.front(); - expected_reads_.pop(); - EXPECT_TRUE(expected.async); - if (expected.info) { - pending_info_->response_data_size = expected.len; - } else { - size_t to_read = std::min(pending_buffer_len_, expected.len); - if (to_read > 0) - memcpy(pending_buffer_->data(), expected.data, to_read); - } - pending_info_ = nullptr; - pending_buffer_ = nullptr; - net::CompletionCallback callback = pending_callback_; - pending_callback_.Reset(); - callback.Run(expected.result); -} - -// A test implementation of ServiceWorkerResponseWriter. -// -// This class exposes the ability to expect writes (see ExpectWrite*Ok() below). -// Each write to this class via WriteInfo() or WriteData() consumes another -// expected write, in the order they were added, so: -// writer->ExpectWriteInfoOk(5, false); -// writer->ExpectWriteDataOk(6, false); -// writer->ExpectWriteDataOk(6, false); -// Expects these calls, in this order: -// writer->WriteInfo(...); // checks that |buf->response_data_size| == 5 -// writer->WriteData(...); // checks that 6 bytes are being written -// writer->WriteData(...); // checks that another 6 bytes are being written -// If this class receives an unexpected call to WriteInfo() or WriteData(), it -// DCHECKs. -// Expected writes marked async do not complete synchronously, but rather return -// without running their callback and need to be completed with -// CompletePendingWrite(). -// A convenience method AllExpectedWritesDone() is exposed so tests can ensure -// that all expected writes have been consumed by matching calls to WriteInfo() -// or WriteData(). -class MockServiceWorkerResponseWriter : public ServiceWorkerResponseWriter { - public: - MockServiceWorkerResponseWriter() - : ServiceWorkerResponseWriter(0, nullptr), - info_written_(0), - data_written_(0) {} - ~MockServiceWorkerResponseWriter() override {} - - // ServiceWorkerResponseWriter overrides - void WriteInfo(HttpResponseInfoIOBuffer* info_buf, - const net::CompletionCallback& callback) override; - void WriteData(net::IOBuffer* buf, - int buf_len, - const net::CompletionCallback& callback) override; - - // Enqueue expected writes. - void ExpectWriteInfoOk(size_t len, bool async); - void ExpectWriteDataOk(size_t len, bool async); - - // Complete a pending asynchronous write. This method DCHECKs unless there is - // a pending write (a write for which WriteInfo() or WriteData() has been - // called but the callback has not yet been run). - void CompletePendingWrite(); - - // Returns whether all expected reads have been consumed. - bool AllExpectedWritesDone() { return expected_writes_.size() == 0; } - - private: - struct ExpectedWrite { - ExpectedWrite(bool is_info, size_t length, bool async, int result) - : is_info(is_info), length(length), async(async), result(result) {} - bool is_info; - size_t length; - bool async; - int result; - }; - - std::queue expected_writes_; - - size_t info_written_; - size_t data_written_; - - net::CompletionCallback pending_callback_; -}; - -void MockServiceWorkerResponseWriter::WriteInfo( - HttpResponseInfoIOBuffer* info_buf, - const net::CompletionCallback& callback) { - DCHECK(!expected_writes_.empty()); - ExpectedWrite write = expected_writes_.front(); - EXPECT_TRUE(write.is_info); - EXPECT_EQ(write.length, static_cast(info_buf->response_data_size)); - info_written_ += info_buf->response_data_size; - if (!write.async) { - expected_writes_.pop(); - callback.Run(write.result); - } else { - pending_callback_ = callback; - } -} - -void MockServiceWorkerResponseWriter::WriteData( - net::IOBuffer* buf, - int buf_len, - const net::CompletionCallback& callback) { - DCHECK(!expected_writes_.empty()); - ExpectedWrite write = expected_writes_.front(); - EXPECT_FALSE(write.is_info); - EXPECT_EQ(write.length, static_cast(buf_len)); - data_written_ += buf_len; - if (!write.async) { - expected_writes_.pop(); - callback.Run(write.result); - } else { - pending_callback_ = callback; - } -} - -void MockServiceWorkerResponseWriter::ExpectWriteInfoOk(size_t length, - bool async) { - ExpectedWrite expected(true, length, async, length); - expected_writes_.push(expected); -} - -void MockServiceWorkerResponseWriter::ExpectWriteDataOk(size_t length, - bool async) { - ExpectedWrite expected(false, length, async, length); - expected_writes_.push(expected); -} - -void MockServiceWorkerResponseWriter::CompletePendingWrite() { - DCHECK(!expected_writes_.empty()); - ExpectedWrite write = expected_writes_.front(); - DCHECK(write.async); - expected_writes_.pop(); - pending_callback_.Run(write.result); -} - -class ServiceWorkerCacheWriterTest : public ::testing::Test { - public: - ServiceWorkerCacheWriterTest() - : readers_deleter_(&readers_), writers_deleter_(&writers_) {} - - void SetUp() override { - ::testing::Test::SetUp(); - cache_writer_.reset(new ServiceWorkerCacheWriter( - base::Bind(&ServiceWorkerCacheWriterTest::CreateReader, - base::Unretained(this)), - base::Bind(&ServiceWorkerCacheWriterTest::CreateWriter, - base::Unretained(this)))); - write_complete_ = false; - } - - MockServiceWorkerResponseReader* ExpectReader() { - scoped_ptr reader( - new MockServiceWorkerResponseReader); - MockServiceWorkerResponseReader* borrowed_reader = reader.get(); - readers_.push_back(reader.release()); // give ownership to |readers_| - return borrowed_reader; - } - - MockServiceWorkerResponseWriter* ExpectWriter() { - scoped_ptr writer( - new MockServiceWorkerResponseWriter); - MockServiceWorkerResponseWriter* borrowed_writer = writer.get(); - writers_.push_back(writer.release()); // give ownership to |writers_| - return borrowed_writer; - } - - protected: - // TODO(ellyjones): when unique_ptr<> is allowed, make these instead: - // std::list> - // Right now, these cannot use scoped_ptr. - // Their elements are deleted by the STLElementDeleters below when this object - // goes out of scope. - std::list readers_; - std::list writers_; - STLElementDeleter> - readers_deleter_; - STLElementDeleter> - writers_deleter_; - scoped_ptr cache_writer_; - bool write_complete_; - net::Error last_error_; - - scoped_ptr CreateReader() { - if (readers_.empty()) - return make_scoped_ptr(nullptr); - scoped_ptr reader(readers_.front()); - readers_.pop_front(); - return reader.Pass(); - } - scoped_ptr CreateWriter() { - if (writers_.empty()) - return make_scoped_ptr(nullptr); - scoped_ptr writer(writers_.front()); - writers_.pop_front(); - return writer.Pass(); - } - - ServiceWorkerCacheWriter::OnWriteCompleteCallback CreateWriteCallback() { - return base::Bind(&ServiceWorkerCacheWriterTest::OnWriteComplete, - base::Unretained(this)); - } - - void OnWriteComplete(net::Error error) { - write_complete_ = true; - last_error_ = error; - } - - net::Error WriteHeaders(size_t len) { - scoped_refptr buf(new HttpResponseInfoIOBuffer); - buf->response_data_size = len; - return cache_writer_->MaybeWriteHeaders(buf.get(), CreateWriteCallback()); - } - - net::Error WriteData(const std::string& data) { - scoped_refptr buf = new net::StringIOBuffer(data); - return cache_writer_->MaybeWriteData(buf.get(), data.size(), - CreateWriteCallback()); - } -}; - -// Passthrough tests: -// In these tests, the ServiceWorkerCacheWriter under test has no existing -// reader, since no calls to ExpectReader() have been made; this means that -// there is no existing cached response and the incoming data is written back to -// the cache directly. - -TEST_F(ServiceWorkerCacheWriterTest, PassthroughHeadersSync) { - const size_t kHeaderSize = 16; - MockServiceWorkerResponseWriter* writer = ExpectWriter(); - writer->ExpectWriteInfoOk(kHeaderSize, false); - - net::Error error = WriteHeaders(kHeaderSize); - EXPECT_EQ(net::OK, error); - EXPECT_FALSE(write_complete_); - EXPECT_TRUE(writer->AllExpectedWritesDone()); - EXPECT_EQ(0U, cache_writer_->bytes_written()); -} - -TEST_F(ServiceWorkerCacheWriterTest, PassthroughHeadersAsync) { - size_t kHeaderSize = 16; - MockServiceWorkerResponseWriter* writer = ExpectWriter(); - writer->ExpectWriteInfoOk(kHeaderSize, true); - - net::Error error = WriteHeaders(kHeaderSize); - EXPECT_EQ(net::ERR_IO_PENDING, error); - EXPECT_FALSE(write_complete_); - writer->CompletePendingWrite(); - EXPECT_TRUE(write_complete_); - EXPECT_TRUE(writer->AllExpectedWritesDone()); - EXPECT_EQ(0U, cache_writer_->bytes_written()); -} - -TEST_F(ServiceWorkerCacheWriterTest, PassthroughDataSync) { - const std::string data1 = "abcdef"; - const std::string data2 = "ghijklmno"; - size_t response_size = data1.size() + data2.size(); - - MockServiceWorkerResponseWriter* writer = ExpectWriter(); - writer->ExpectWriteInfoOk(response_size, false); - writer->ExpectWriteDataOk(data1.size(), false); - writer->ExpectWriteDataOk(data2.size(), false); - - net::Error error = WriteHeaders(response_size); - EXPECT_EQ(net::OK, error); - - error = WriteData(data1); - EXPECT_EQ(net::OK, error); - - error = WriteData(data2); - EXPECT_EQ(net::OK, error); - EXPECT_TRUE(writer->AllExpectedWritesDone()); -} - -TEST_F(ServiceWorkerCacheWriterTest, PassthroughDataAsync) { - const std::string data1 = "abcdef"; - const std::string data2 = "ghijklmno"; - size_t response_size = data1.size() + data2.size(); - - MockServiceWorkerResponseWriter* writer = ExpectWriter(); - writer->ExpectWriteInfoOk(response_size, false); - writer->ExpectWriteDataOk(data1.size(), true); - writer->ExpectWriteDataOk(data2.size(), true); - - net::Error error = WriteHeaders(response_size); - EXPECT_EQ(net::OK, error); - - error = WriteData(data1); - EXPECT_EQ(net::ERR_IO_PENDING, error); - writer->CompletePendingWrite(); - EXPECT_TRUE(write_complete_); - - write_complete_ = false; - error = WriteData(data2); - EXPECT_EQ(net::ERR_IO_PENDING, error); - writer->CompletePendingWrite(); - EXPECT_TRUE(write_complete_); - EXPECT_TRUE(writer->AllExpectedWritesDone()); -} - -// Comparison tests: -// For the Compare* tests below, the ServiceWorkerCacheWriter under test has a -// reader for an existing cached response, so it will compare the response being -// written to it against the existing cached response. - -TEST_F(ServiceWorkerCacheWriterTest, CompareHeadersSync) { - size_t response_size = 3; - MockServiceWorkerResponseWriter* writer = ExpectWriter(); - MockServiceWorkerResponseReader* reader = ExpectReader(); - - reader->ExpectReadInfoOk(response_size, false); - - net::Error error = WriteHeaders(response_size); - EXPECT_EQ(net::OK, error); - EXPECT_TRUE(writer->AllExpectedWritesDone()); - EXPECT_TRUE(reader->AllExpectedReadsDone()); -} - -TEST_F(ServiceWorkerCacheWriterTest, CompareDataOkSync) { - const std::string data1 = "abcdef"; - size_t response_size = data1.size(); - - MockServiceWorkerResponseWriter* writer = ExpectWriter(); - MockServiceWorkerResponseReader* reader = ExpectReader(); - - reader->ExpectReadInfoOk(response_size, false); - reader->ExpectReadDataOk(data1, false); - - net::Error error = WriteHeaders(response_size); - EXPECT_EQ(net::OK, error); - - error = WriteData(data1); - EXPECT_EQ(net::OK, error); - - EXPECT_TRUE(writer->AllExpectedWritesDone()); - EXPECT_TRUE(reader->AllExpectedReadsDone()); - EXPECT_EQ(0U, cache_writer_->bytes_written()); -} - -TEST_F(ServiceWorkerCacheWriterTest, CompareShortCacheReads) { - const size_t kHeaderSize = 16; - const std::string& data1 = "abcdef"; - const std::string& cache_data2 = "ghi"; - const std::string& cache_data3 = "j"; - const std::string& cache_data4 = "kl"; - const std::string& net_data2 = "ghijkl"; - const std::string& data5 = "mnopqrst"; - - MockServiceWorkerResponseReader* reader = ExpectReader(); - reader->ExpectReadInfo(kHeaderSize, false, kHeaderSize); - reader->ExpectReadDataOk(data1, false); - reader->ExpectReadDataOk(cache_data2, false); - reader->ExpectReadDataOk(cache_data3, false); - reader->ExpectReadDataOk(cache_data4, false); - reader->ExpectReadDataOk(data5, false); - - net::Error error = WriteHeaders(kHeaderSize); - EXPECT_EQ(net::OK, error); - error = WriteData(data1); - EXPECT_EQ(net::OK, error); - error = WriteData(net_data2); - EXPECT_EQ(net::OK, error); - error = WriteData(data5); - EXPECT_EQ(net::OK, error); - EXPECT_TRUE(reader->AllExpectedReadsDone()); - EXPECT_EQ(0U, cache_writer_->bytes_written()); -} - -TEST_F(ServiceWorkerCacheWriterTest, CompareDataOkAsync) { - const std::string data1 = "abcdef"; - size_t response_size = data1.size(); - - MockServiceWorkerResponseReader* reader = ExpectReader(); - - reader->ExpectReadInfoOk(response_size, true); - reader->ExpectReadDataOk(data1, true); - - net::Error error = WriteHeaders(response_size); - EXPECT_EQ(net::ERR_IO_PENDING, error); - reader->CompletePendingRead(); - - error = WriteData(data1); - EXPECT_EQ(net::ERR_IO_PENDING, error); - reader->CompletePendingRead(); - - EXPECT_TRUE(reader->AllExpectedReadsDone()); - EXPECT_EQ(0U, cache_writer_->bytes_written()); -} - -TEST_F(ServiceWorkerCacheWriterTest, CompareDataManyOkAsync) { - const std::string expected_data[] = { - "abcdef", "ghijkl", "mnopqr", "stuvwxyz", - }; - size_t response_size = 0; - for (size_t i = 0; i < arraysize(expected_data); ++i) - response_size += expected_data[i].size(); - - MockServiceWorkerResponseReader* reader = ExpectReader(); - - reader->ExpectReadInfoOk(response_size, true); - for (size_t i = 0; i < arraysize(expected_data); ++i) { - reader->ExpectReadDataOk(expected_data[i], true); - } - - net::Error error = WriteHeaders(response_size); - EXPECT_EQ(net::ERR_IO_PENDING, error); - reader->CompletePendingRead(); - - for (size_t i = 0; i < arraysize(expected_data); ++i) { - error = WriteData(expected_data[i]); - EXPECT_EQ(net::ERR_IO_PENDING, error); - reader->CompletePendingRead(); - EXPECT_EQ(net::OK, last_error_); - } - - EXPECT_TRUE(reader->AllExpectedReadsDone()); - EXPECT_EQ(0U, cache_writer_->bytes_written()); -} - -// This test writes headers and three data blocks data1, data2, data3; data2 -// differs in the cached version. The writer should be asked to rewrite the -// headers and body with the new value, and the copy reader should be asked to -// read the header and data1. -TEST_F(ServiceWorkerCacheWriterTest, CompareFailedCopySync) { - std::string data1 = "abcdef"; - std::string cache_data2 = "ghijkl"; - std::string net_data2 = "mnopqr"; - std::string data3 = "stuvwxyz"; - size_t cache_response_size = data1.size() + cache_data2.size() + data3.size(); - size_t net_response_size = data1.size() + net_data2.size() + data3.size(); - - MockServiceWorkerResponseWriter* writer = ExpectWriter(); - MockServiceWorkerResponseReader* compare_reader = ExpectReader(); - MockServiceWorkerResponseReader* copy_reader = ExpectReader(); - - compare_reader->ExpectReadInfoOk(cache_response_size, false); - compare_reader->ExpectReadDataOk(data1, false); - compare_reader->ExpectReadDataOk(cache_data2, false); - - copy_reader->ExpectReadInfoOk(cache_response_size, false); - copy_reader->ExpectReadDataOk(data1, false); - - writer->ExpectWriteInfoOk(net_response_size, false); - writer->ExpectWriteDataOk(data1.size(), false); - writer->ExpectWriteDataOk(net_data2.size(), false); - writer->ExpectWriteDataOk(data3.size(), false); - - net::Error error = WriteHeaders(net_response_size); - EXPECT_EQ(net::OK, error); - error = WriteData(data1); - EXPECT_EQ(net::OK, error); - error = WriteData(net_data2); - EXPECT_EQ(net::OK, error); - error = WriteData(data3); - EXPECT_EQ(net::OK, error); - - EXPECT_TRUE(writer->AllExpectedWritesDone()); - EXPECT_TRUE(compare_reader->AllExpectedReadsDone()); - EXPECT_TRUE(copy_reader->AllExpectedReadsDone()); -} - -// Tests behavior when the cached data is shorter than the network data. -TEST_F(ServiceWorkerCacheWriterTest, CompareFailedCopyShort) { - std::string data1 = "abcdef"; - std::string cache_data2 = "mnop"; - std::string net_data2 = "mnopqr"; - std::string data3 = "stuvwxyz"; - size_t cache_response_size = data1.size() + cache_data2.size() + data3.size(); - size_t net_response_size = data1.size() + net_data2.size() + data3.size(); - - MockServiceWorkerResponseWriter* writer = ExpectWriter(); - MockServiceWorkerResponseReader* compare_reader = ExpectReader(); - MockServiceWorkerResponseReader* copy_reader = ExpectReader(); - - compare_reader->ExpectReadInfoOk(cache_response_size, false); - compare_reader->ExpectReadDataOk(data1, false); - compare_reader->ExpectReadDataOk(cache_data2, false); - compare_reader->ExpectReadDataOk("", false); // EOF read - - copy_reader->ExpectReadInfoOk(cache_response_size, false); - copy_reader->ExpectReadDataOk(data1, false); - - writer->ExpectWriteInfoOk(net_response_size, false); - writer->ExpectWriteDataOk(data1.size(), false); - writer->ExpectWriteDataOk(net_data2.size(), false); - writer->ExpectWriteDataOk(data3.size(), false); - - net::Error error = WriteHeaders(net_response_size); - EXPECT_EQ(net::OK, error); - error = WriteData(data1); - EXPECT_EQ(net::OK, error); - error = WriteData(net_data2); - EXPECT_EQ(net::OK, error); - error = WriteData(data3); - EXPECT_EQ(net::OK, error); - - EXPECT_TRUE(writer->AllExpectedWritesDone()); - EXPECT_TRUE(compare_reader->AllExpectedReadsDone()); - EXPECT_TRUE(copy_reader->AllExpectedReadsDone()); -} - -// Tests behavior when the cached data is longer than the network data. -TEST_F(ServiceWorkerCacheWriterTest, CompareFailedCopyLong) { - std::string data1 = "abcdef"; - std::string cache_data2 = "mnop"; - std::string net_data2 = "mnop"; - std::string cache_data3 = "qr"; - size_t cached_size = data1.size() + cache_data2.size() + cache_data3.size(); - size_t net_size = data1.size() + net_data2.size(); - - MockServiceWorkerResponseWriter* writer = ExpectWriter(); - MockServiceWorkerResponseReader* compare_reader = ExpectReader(); - MockServiceWorkerResponseReader* copy_reader = ExpectReader(); - - compare_reader->ExpectReadInfoOk(cached_size, false); - compare_reader->ExpectReadDataOk(data1, false); - compare_reader->ExpectReadDataOk(cache_data2, false); - - // The comparison should fail at the end of |cache_data2|, when the cache - // writer realizes the two responses are different sizes, and then the network - // data should be written back starting with |net_data2|. - copy_reader->ExpectReadInfoOk(cached_size, false); - copy_reader->ExpectReadDataOk(data1, false); - copy_reader->ExpectReadDataOk(net_data2, false); - - writer->ExpectWriteInfoOk(net_size, false); - writer->ExpectWriteDataOk(data1.size(), false); - writer->ExpectWriteDataOk(net_data2.size(), false); - - net::Error error = WriteHeaders(net_size); - EXPECT_EQ(net::OK, error); - error = WriteData(data1); - EXPECT_EQ(net::OK, error); - error = WriteData(net_data2); - EXPECT_EQ(net::OK, error); - error = WriteData(""); - EXPECT_EQ(net::OK, error); - - EXPECT_TRUE(writer->AllExpectedWritesDone()); - EXPECT_TRUE(compare_reader->AllExpectedReadsDone()); - EXPECT_TRUE(copy_reader->AllExpectedReadsDone()); -} - -} // namespace -} // namespace content diff --git a/content/browser/service_worker/service_worker_write_to_cache_job.cc b/content/browser/service_worker/service_worker_write_to_cache_job.cc index ca801ca39c9bb7..781a65257b61f1 100644 --- a/content/browser/service_worker/service_worker_write_to_cache_job.cc +++ b/content/browser/service_worker/service_worker_write_to_cache_job.cc @@ -4,11 +4,10 @@ #include "content/browser/service_worker/service_worker_write_to_cache_job.h" -#include "base/bind.h" -#include "base/callback.h" +#include + #include "base/strings/stringprintf.h" #include "base/trace_event/trace_event.h" -#include "content/browser/service_worker/service_worker_cache_writer.h" #include "content/browser/service_worker/service_worker_context_core.h" #include "content/browser/service_worker/service_worker_disk_cache.h" #include "content/browser/service_worker/service_worker_metrics.h" @@ -19,6 +18,7 @@ #include "net/http/http_network_session.h" #include "net/http/http_request_headers.h" #include "net/http/http_response_headers.h" +#include "net/http/http_util.h" #include "net/url_request/url_request.h" #include "net/url_request/url_request_context.h" #include "net/url_request/url_request_status.h" @@ -40,8 +40,260 @@ const char kRedirectError[] = "The script resource is behind a redirect, which is disallowed."; const char kServiceWorkerAllowed[] = "Service-Worker-Allowed"; +const int kBufferSize = 16 * 1024; + } // namespace +// Reads an existing resource and copies it via +// ServiceWorkerWriteToCacheJob::WriteData. +class ServiceWorkerWriteToCacheJob::Copier : public base::RefCounted { + public: + Copier(base::WeakPtr owner, + scoped_ptr reader, + int bytes_to_copy, + const base::Callback& callback) + : owner_(owner), + reader_(reader.release()), + bytes_to_copy_(bytes_to_copy), + callback_(callback) { + DCHECK_LT(0, bytes_to_copy_); + } + + void Start() { + io_buffer_ = new net::IOBuffer(kBufferSize); + ReadSomeData(); + } + + private: + friend class base::RefCounted; + ~Copier() {} + + void ReadSomeData() { + reader_->ReadData(io_buffer_.get(), kBufferSize, + base::Bind(&Copier::OnReadDataComplete, this)); + } + + void OnReadDataComplete(int result) { + if (!owner_) + return; + if (result <= 0) { + // We hit EOF or error but weren't done copying. + Complete(SERVICE_WORKER_ERROR_FAILED); + return; + } + + int bytes_to_write = std::min(bytes_to_copy_, result); + owner_->WriteData(io_buffer_.get(), bytes_to_write, + base::Bind(&Copier::OnWriteDataComplete, this)); + } + + void OnWriteDataComplete(int result) { + if (result < 0) { + Complete(SERVICE_WORKER_ERROR_FAILED); + return; + } + + DCHECK_LE(result, bytes_to_copy_); + bytes_to_copy_ -= result; + if (bytes_to_copy_ == 0) { + Complete(SERVICE_WORKER_OK); + return; + } + + ReadSomeData(); + } + + void Complete(ServiceWorkerStatusCode status) { + if (!owner_) + return; + callback_.Run(status); + } + + base::WeakPtr owner_; + scoped_ptr reader_; + int bytes_to_copy_ = 0; + base::Callback callback_; + scoped_refptr io_buffer_; +}; + +// Abstract consumer for ServiceWorkerWriteToCacheJob that processes data from +// the network. +class ServiceWorkerWriteToCacheJob::NetDataConsumer { + public: + virtual ~NetDataConsumer() {} + + // Called by |owner_|'s OnResponseStarted. + virtual void OnResponseStarted() = 0; + + // HandleData should call |owner_|->NotifyReadComplete() when done. + virtual void HandleData(net::IOBuffer* buf, int size) = 0; +}; + +// Dumb consumer that just writes everything it sees to disk. +class ServiceWorkerWriteToCacheJob::PassThroughConsumer + : public ServiceWorkerWriteToCacheJob::NetDataConsumer { + public: + explicit PassThroughConsumer(ServiceWorkerWriteToCacheJob* owner) + : owner_(owner), weak_factory_(this) {} + ~PassThroughConsumer() override {} + + void OnResponseStarted() override { + owner_->WriteHeaders( + base::Bind(&PassThroughConsumer::OnWriteHeadersComplete, + weak_factory_.GetWeakPtr())); + owner_->SetPendingIO(); + } + + void HandleData(net::IOBuffer* buf, int size) override { + if (size == 0) { + owner_->OnPassThroughComplete(); + return; + } + + owner_->WriteData(buf, size, + base::Bind(&PassThroughConsumer::OnWriteDataComplete, + weak_factory_.GetWeakPtr())); + owner_->SetPendingIO(); + } + + private: + void OnWriteHeadersComplete() { + owner_->ClearPendingIO(); + owner_->CommitHeadersAndNotifyHeadersComplete(); + } + + void OnWriteDataComplete(int result) { + owner_->ClearPendingIO(); + owner_->NotifyReadComplete(result); + } + + ServiceWorkerWriteToCacheJob* owner_; + base::WeakPtrFactory weak_factory_; +}; + +// Compares an existing resource with data progressively fed to it. +// Calls back to |owner|->OnCompareComplete once done. +class ServiceWorkerWriteToCacheJob::Comparer + : public ServiceWorkerWriteToCacheJob::NetDataConsumer { + public: + Comparer(ServiceWorkerWriteToCacheJob* owner, + scoped_ptr reader) + : owner_(owner), reader_(reader.release()), weak_factory_(this) {} + ~Comparer() override {} + + void OnResponseStarted() override { + owner_->CommitHeadersAndNotifyHeadersComplete(); + } + + void HandleData(net::IOBuffer* buf, int size) override { + net_data_ = buf; + net_data_offset_ = 0; + net_data_size_ = size; + + if (size == 0 && info_) { + Complete(bytes_matched_ == info_->response_data_size); + return; + } + + if (!info_) { + read_buffer_ = new net::IOBuffer(kBufferSize); + info_ = new HttpResponseInfoIOBuffer; + reader_->ReadInfo(info_.get(), base::Bind(&Comparer::OnReadInfoComplete, + weak_factory_.GetWeakPtr())); + owner_->SetPendingIO(); + return; + } + + ReadSomeData(); + owner_->SetPendingIO(); + } + + private: + int bytes_remaining() { + DCHECK(net_data_); + DCHECK_LE(0, net_data_offset_); + DCHECK_LE(net_data_offset_, net_data_size_); + return net_data_size_ - net_data_offset_; + } + + void OnReadInfoComplete(int result) { + if (result < 0) { + Complete(false); + return; + } + + if (bytes_remaining() == 0) { + Complete(bytes_matched_ == info_->response_data_size); + return; + } + + ReadSomeData(); + } + + void ReadSomeData() { + DCHECK_LT(0, bytes_remaining()); + int bytes_to_read = std::min(bytes_remaining(), kBufferSize); + reader_->ReadData( + read_buffer_.get(), bytes_to_read, + base::Bind(&Comparer::OnReadDataComplete, weak_factory_.GetWeakPtr())); + } + + void OnReadDataComplete(int result) { + if (result <= 0) { + // We hit error or EOF but had more to compare. + Complete(false); + return; + } + + DCHECK_LE(result, bytes_remaining()); + if (memcmp(net_data_->data() + net_data_offset_, read_buffer_->data(), + result) != 0) { + Complete(false); + return; + } + + net_data_offset_ += result; + if (bytes_remaining() == 0) { + NotifyReadComplete(); + return; + } + + ReadSomeData(); + } + + // Completes one HandleData() call. + void NotifyReadComplete() { + int size = net_data_size_; + net_data_ = nullptr; + net_data_offset_ = 0; + net_data_size_ = 0; + + bytes_matched_ += size; + owner_->ClearPendingIO(); + owner_->NotifyReadComplete(size); + } + + // Completes the entire Comparer. + void Complete(bool is_equal) { + owner_->OnCompareComplete(bytes_matched_, is_equal); + } + + ServiceWorkerWriteToCacheJob* owner_; + scoped_ptr reader_; + scoped_refptr read_buffer_; + scoped_refptr info_; + + // Cumulative number of bytes successfully compared. + int bytes_matched_ = 0; + + // State used for one HandleData() call. + scoped_refptr net_data_; + int net_data_offset_ = 0; + int net_data_size_ = 0; + + base::WeakPtrFactory weak_factory_; +}; + ServiceWorkerWriteToCacheJob::ServiceWorkerWriteToCacheJob( net::URLRequest* request, net::NetworkDelegate* network_delegate, @@ -79,14 +331,14 @@ void ServiceWorkerWriteToCacheJob::Start() { net::URLRequestStatus::FAILED, net::ERR_FAILED)); return; } + if (incumbent_response_id_ != kInvalidServiceWorkerResourceId) { + scoped_ptr incumbent_reader = + context_->storage()->CreateResponseReader(incumbent_response_id_); + consumer_.reset(new Comparer(this, incumbent_reader.Pass())); + } else { + consumer_.reset(new PassThroughConsumer(this)); + } - // These uses of Unretained are safe because this object is the sole owner of - // |cache_writer_|, which in turn is the sole user of these callbacks. - cache_writer_.reset(new ServiceWorkerCacheWriter( - base::Bind(&ServiceWorkerWriteToCacheJob::CreateCacheResponseReader, - base::Unretained(this)), - base::Bind(&ServiceWorkerWriteToCacheJob::CreateCacheResponseWriter, - base::Unretained(this)))); version_->script_cache_map()->NotifyStartedCaching( url_, response_id_); did_notify_started_ = true; @@ -99,9 +351,12 @@ void ServiceWorkerWriteToCacheJob::Kill() { weak_factory_.InvalidateWeakPtrs(); has_been_killed_ = true; net_request_.reset(); - if (did_notify_started_) { - NotifyFinishedCaching(net::URLRequestStatus::FromError(net::ERR_ABORTED), - kKilledError); + if (did_notify_started_ && !did_notify_finished_) { + version_->script_cache_map()->NotifyFinishedCaching( + url_, -1, + net::URLRequestStatus(net::URLRequestStatus::FAILED, net::ERR_ABORTED), + kKilledError); + did_notify_finished_ = true; } writer_.reset(); context_.reset(); @@ -157,21 +412,17 @@ bool ServiceWorkerWriteToCacheJob::ReadRawData(net::IOBuffer* buf, return false; if (!status.is_success()) { - NotifyDoneHelper(status, kFetchScriptError); + AsyncNotifyDoneHelper(status, kFetchScriptError); return false; } - HandleNetData(*bytes_read); - - if (!status.is_success()) { - NotifyDoneHelper(status, ""); + DCHECK_EQ(0, *bytes_read); + consumer_->HandleData(buf, 0); + if (did_notify_finished_) + return GetStatus().is_success(); + if (GetStatus().is_io_pending()) return false; - } - - // Since URLRequestStatus::is_success() means "SUCCESS or IO_PENDING", but the - // contract of this function is "return true for synchronous successes only", - // it is important to test against SUCCESS explicitly here. - return GetStatus().status() == net::URLRequestStatus::SUCCESS; + return status.is_success(); } const net::HttpResponseInfo* ServiceWorkerWriteToCacheJob::http_info() const { @@ -210,14 +461,104 @@ net::URLRequestStatus ServiceWorkerWriteToCacheJob::ReadNetData( int* bytes_read) { DCHECK_GT(buf_size, 0); DCHECK(bytes_read); + *bytes_read = 0; io_buffer_ = buf; io_buffer_bytes_ = 0; - if (!net_request_->Read(buf, buf_size, bytes_read)) - DCHECK_NE(net::URLRequestStatus::SUCCESS, net_request_->status().status()); + int net_bytes_read = 0; + if (!net_request_->Read(buf, buf_size, &net_bytes_read)) { + if (net_request_->status().is_io_pending()) + return net_request_->status(); + DCHECK(!net_request_->status().is_success()); + return net_request_->status(); + } + + if (net_bytes_read != 0) { + HandleNetData(net_bytes_read); + DCHECK(GetStatus().is_io_pending()); + return GetStatus(); + } + DCHECK(net_request_->status().is_success()); return net_request_->status(); } +void ServiceWorkerWriteToCacheJob::WriteHeaders(const base::Closure& callback) { + if (!context_) { + AsyncNotifyDoneHelper( + net::URLRequestStatus(net::URLRequestStatus::FAILED, net::ERR_FAILED), + kFetchScriptError); + return; + } + TRACE_EVENT_ASYNC_STEP_INTO0("ServiceWorker", + "ServiceWorkerWriteToCacheJob::ExecutingJob", + this, "WriteHeaders"); + writer_ = context_->storage()->CreateResponseWriter(response_id_); + scoped_refptr info_buffer = + new HttpResponseInfoIOBuffer( + new net::HttpResponseInfo(net_request_->response_info())); + writer_->WriteInfo( + info_buffer.get(), + base::Bind(&ServiceWorkerWriteToCacheJob::OnWriteHeadersComplete, + weak_factory_.GetWeakPtr(), callback)); +} + +void ServiceWorkerWriteToCacheJob::OnWriteHeadersComplete( + const base::Closure& callback, + int result) { + if (result < 0) { + ServiceWorkerMetrics::CountWriteResponseResult( + ServiceWorkerMetrics::WRITE_HEADERS_ERROR); + AsyncNotifyDoneHelper( + net::URLRequestStatus(net::URLRequestStatus::FAILED, result), + kFetchScriptError); + return; + } + TRACE_EVENT_ASYNC_STEP_INTO0("ServiceWorker", + "ServiceWorkerWriteToCacheJob::ExecutingJob", + this, "WriteHeadersCompleted"); + callback.Run(); +} + +void ServiceWorkerWriteToCacheJob::WriteData( + net::IOBuffer* buf, + int bytes_to_write, + const base::Callback& callback) { + DCHECK_LT(0, bytes_to_write); + TRACE_EVENT_ASYNC_STEP_INTO1( + "ServiceWorker", "ServiceWorkerWriteToCacheJob::ExecutingJob", this, + "WriteData", "Amount to write", bytes_to_write); + + writer_->WriteData( + buf, bytes_to_write, + base::Bind(&ServiceWorkerWriteToCacheJob::OnWriteDataComplete, + weak_factory_.GetWeakPtr(), callback)); +} + +void ServiceWorkerWriteToCacheJob::OnWriteDataComplete( + const base::Callback& callback, + int result) { + DCHECK_NE(0, result); + if (!context_) { + AsyncNotifyDoneHelper( + net::URLRequestStatus(net::URLRequestStatus::FAILED, net::ERR_FAILED), + kFetchScriptError); + return; + } + if (result < 0) { + ServiceWorkerMetrics::CountWriteResponseResult( + ServiceWorkerMetrics::WRITE_DATA_ERROR); + AsyncNotifyDoneHelper( + net::URLRequestStatus(net::URLRequestStatus::FAILED, result), + kFetchScriptError); + return; + } + ServiceWorkerMetrics::CountWriteResponseResult( + ServiceWorkerMetrics::WRITE_OK); + callback.Run(result); + TRACE_EVENT_ASYNC_END0("ServiceWorker", + "ServiceWorkerWriteToCacheJob::ExecutingJob", this); +} + void ServiceWorkerWriteToCacheJob::OnReceivedRedirect( net::URLRequest* request, const net::RedirectInfo& redirect_info, @@ -226,9 +567,9 @@ void ServiceWorkerWriteToCacheJob::OnReceivedRedirect( TRACE_EVENT0("ServiceWorker", "ServiceWorkerWriteToCacheJob::OnReceivedRedirect"); // Script resources can't redirect. - NotifyDoneHelper(net::URLRequestStatus(net::URLRequestStatus::FAILED, - net::ERR_UNSAFE_REDIRECT), - kRedirectError); + AsyncNotifyDoneHelper(net::URLRequestStatus(net::URLRequestStatus::FAILED, + net::ERR_UNSAFE_REDIRECT), + kRedirectError); } void ServiceWorkerWriteToCacheJob::OnAuthRequired( @@ -238,7 +579,7 @@ void ServiceWorkerWriteToCacheJob::OnAuthRequired( TRACE_EVENT0("ServiceWorker", "ServiceWorkerWriteToCacheJob::OnAuthRequired"); // TODO(michaeln): Pass this thru to our jobs client. - NotifyDoneHelper( + AsyncNotifyDoneHelper( net::URLRequestStatus(net::URLRequestStatus::FAILED, net::ERR_FAILED), kClientAuthenticationError); } @@ -251,7 +592,7 @@ void ServiceWorkerWriteToCacheJob::OnCertificateRequested( "ServiceWorkerWriteToCacheJob::OnCertificateRequested"); // TODO(michaeln): Pass this thru to our jobs client. // see NotifyCertificateRequested. - NotifyDoneHelper( + AsyncNotifyDoneHelper( net::URLRequestStatus(net::URLRequestStatus::FAILED, net::ERR_FAILED), kClientAuthenticationError); } @@ -265,9 +606,9 @@ void ServiceWorkerWriteToCacheJob::OnSSLCertificateError( "ServiceWorkerWriteToCacheJob::OnSSLCertificateError"); // TODO(michaeln): Pass this thru to our jobs client, // see NotifySSLCertificateError. - NotifyDoneHelper(net::URLRequestStatus(net::URLRequestStatus::FAILED, - net::ERR_INSECURE_RESPONSE), - kSSLError); + AsyncNotifyDoneHelper(net::URLRequestStatus(net::URLRequestStatus::FAILED, + net::ERR_INSECURE_RESPONSE), + kSSLError); } void ServiceWorkerWriteToCacheJob::OnBeforeNetworkStart( @@ -283,15 +624,15 @@ void ServiceWorkerWriteToCacheJob::OnResponseStarted( net::URLRequest* request) { DCHECK_EQ(net_request_, request); if (!request->status().is_success()) { - NotifyDoneHelper(request->status(), kFetchScriptError); + AsyncNotifyDoneHelper(request->status(), kFetchScriptError); return; } if (request->GetResponseCode() / 100 != 2) { std::string error_message = base::StringPrintf(kBadHTTPResponseError, request->GetResponseCode()); - NotifyDoneHelper(net::URLRequestStatus(net::URLRequestStatus::FAILED, - net::ERR_INVALID_RESPONSE), - error_message); + AsyncNotifyDoneHelper(net::URLRequestStatus(net::URLRequestStatus::FAILED, + net::ERR_INVALID_RESPONSE), + error_message); // TODO(michaeln): Instead of error'ing immediately, send the net // response to our consumer, just don't cache it? return; @@ -302,9 +643,9 @@ void ServiceWorkerWriteToCacheJob::OnResponseStarted( const net::HttpNetworkSession::Params* session_params = request->context()->GetNetworkSessionParams(); if (!session_params || !session_params->ignore_certificate_errors) { - NotifyDoneHelper(net::URLRequestStatus(net::URLRequestStatus::FAILED, - net::ERR_INSECURE_RESPONSE), - kSSLError); + AsyncNotifyDoneHelper(net::URLRequestStatus(net::URLRequestStatus::FAILED, + net::ERR_INSECURE_RESPONSE), + kSSLError); return; } } @@ -319,9 +660,9 @@ void ServiceWorkerWriteToCacheJob::OnResponseStarted( mime_type.empty() ? kNoMIMEError : base::StringPrintf(kBadMIMEError, mime_type.c_str()); - NotifyDoneHelper(net::URLRequestStatus(net::URLRequestStatus::FAILED, - net::ERR_INSECURE_RESPONSE), - error_message); + AsyncNotifyDoneHelper(net::URLRequestStatus(net::URLRequestStatus::FAILED, + net::ERR_INSECURE_RESPONSE), + error_message); return; } @@ -334,46 +675,17 @@ void ServiceWorkerWriteToCacheJob::OnResponseStarted( if (net_request_->response_info().network_accessed) version_->embedded_worker()->OnNetworkAccessedForScriptLoad(); - http_info_.reset(new net::HttpResponseInfo(net_request_->response_info())); - scoped_refptr info_buffer = - new HttpResponseInfoIOBuffer( - new net::HttpResponseInfo(net_request_->response_info())); - net::Error error = cache_writer_->MaybeWriteHeaders( - info_buffer.get(), - base::Bind(&ServiceWorkerWriteToCacheJob::OnWriteHeadersComplete, - weak_factory_.GetWeakPtr())); - SetStatus(net::URLRequestStatus::FromError(error)); - if (error != net::ERR_IO_PENDING) - NotifyHeadersComplete(); + consumer_->OnResponseStarted(); } -void ServiceWorkerWriteToCacheJob::OnWriteHeadersComplete(net::Error error) { - SetStatus(net::URLRequestStatus::FromError(error)); +void ServiceWorkerWriteToCacheJob::CommitHeadersAndNotifyHeadersComplete() { + http_info_.reset(new net::HttpResponseInfo(net_request_->response_info())); NotifyHeadersComplete(); } void ServiceWorkerWriteToCacheJob::HandleNetData(int bytes_read) { io_buffer_bytes_ = bytes_read; - net::Error error = cache_writer_->MaybeWriteData( - io_buffer_.get(), bytes_read, - base::Bind(&ServiceWorkerWriteToCacheJob::OnWriteDataComplete, - weak_factory_.GetWeakPtr())); - SetStatus(net::URLRequestStatus::FromError(error)); - - // In case of ERR_IO_PENDING, this logic is done in OnWriteDataComplete. - if (error != net::ERR_IO_PENDING && bytes_read == 0) { - NotifyFinishedCaching(net::URLRequestStatus::FromError(error), - std::string()); - } -} - -void ServiceWorkerWriteToCacheJob::OnWriteDataComplete(net::Error error) { - SetStatus(net::URLRequestStatus::FromError(error)); - DCHECK_NE(net::ERR_IO_PENDING, error); - if (io_buffer_bytes_ == 0) { - NotifyDoneHelper(net::URLRequestStatus::FromError(error), std::string()); - } - NotifyReadComplete(error == net::OK ? io_buffer_bytes_ : error); + consumer_->HandleData(io_buffer_.get(), bytes_read); } void ServiceWorkerWriteToCacheJob::OnReadCompleted( @@ -382,23 +694,18 @@ void ServiceWorkerWriteToCacheJob::OnReadCompleted( DCHECK_EQ(net_request_, request); if (bytes_read < 0) { DCHECK(!request->status().is_success()); - NotifyDoneHelper(request->status(), kFetchScriptError); + AsyncNotifyDoneHelper(request->status(), kFetchScriptError); return; } - HandleNetData(bytes_read); - // HandleNetData can cause status of this job to change. If the status changes - // to IO_PENDING, that means HandleNetData has pending IO, and - // NotifyReadComplete will be called later by the appropriate callback. - if (!GetStatus().is_io_pending()) { - int result = GetStatus().status() == net::URLRequestStatus::SUCCESS - ? bytes_read - : GetStatus().error(); - // If bytes_read is 0, HandleNetData synchronously completed and this job is - // at EOF. - if (bytes_read == 0) - NotifyDoneHelper(GetStatus(), std::string()); - NotifyReadComplete(result); + if (bytes_read > 0) { + HandleNetData(bytes_read); + DCHECK(GetStatus().is_io_pending()); + return; } + + // No more data to process, the job is complete. + DCHECK(request->status().is_success()); + HandleNetData(0); } bool ServiceWorkerWriteToCacheJob::CheckPathRestriction( @@ -412,15 +719,89 @@ bool ServiceWorkerWriteToCacheJob::CheckPathRestriction( if (!ServiceWorkerUtils::IsPathRestrictionSatisfied( version_->scope(), url_, has_header ? &service_worker_allowed : nullptr, &error_message)) { - NotifyDoneHelper(net::URLRequestStatus(net::URLRequestStatus::FAILED, - net::ERR_INSECURE_RESPONSE), - error_message); + AsyncNotifyDoneHelper(net::URLRequestStatus(net::URLRequestStatus::FAILED, + net::ERR_INSECURE_RESPONSE), + error_message); return false; } return true; } -void ServiceWorkerWriteToCacheJob::NotifyDoneHelper( +void ServiceWorkerWriteToCacheJob::SetPendingIO() { + SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING, 0)); +} + +void ServiceWorkerWriteToCacheJob::ClearPendingIO() { + SetStatus(net::URLRequestStatus()); +} + +void ServiceWorkerWriteToCacheJob::OnPassThroughComplete() { + NotifyFinishedCaching(net::URLRequestStatus(), std::string()); + if (GetStatus().is_io_pending()) { + ClearPendingIO(); + NotifyReadComplete(0); + } +} + +void ServiceWorkerWriteToCacheJob::OnCompareComplete(int bytes_matched, + bool is_equal) { + if (is_equal) { + // This version is identical to the incumbent, so discard it and fail this + // job. + version_->SetStartWorkerStatusCode(SERVICE_WORKER_ERROR_EXISTS); + AsyncNotifyDoneHelper( + net::URLRequestStatus(net::URLRequestStatus::FAILED, net::ERR_FAILED), + kFetchScriptError); + return; + } + + // We must switch to the pass through consumer. Write what is known + // (headers + bytes matched) to disk. + WriteHeaders(base::Bind(&ServiceWorkerWriteToCacheJob::CopyIncumbent, + weak_factory_.GetWeakPtr(), bytes_matched)); + SetPendingIO(); +} + +void ServiceWorkerWriteToCacheJob::CopyIncumbent(int bytes_to_copy) { + if (bytes_to_copy == 0) { + OnCopyComplete(SERVICE_WORKER_OK); + return; + } + scoped_ptr incumbent_reader = + context_->storage()->CreateResponseReader(incumbent_response_id_); + scoped_refptr copier = new Copier( + weak_factory_.GetWeakPtr(), incumbent_reader.Pass(), bytes_to_copy, + base::Bind(&ServiceWorkerWriteToCacheJob::OnCopyComplete, + weak_factory_.GetWeakPtr())); + copier->Start(); // It deletes itself when done. + DCHECK(GetStatus().is_io_pending()); +} + +void ServiceWorkerWriteToCacheJob::OnCopyComplete( + ServiceWorkerStatusCode status) { + if (status != SERVICE_WORKER_OK) { + AsyncNotifyDoneHelper( + net::URLRequestStatus(net::URLRequestStatus::FAILED, net::ERR_FAILED), + kFetchScriptError); + return; + } + + // Continue processing the net data that triggered the comparison fail and + // copy. + if (io_buffer_bytes_ > 0) { + consumer_.reset(new PassThroughConsumer(this)); + consumer_->HandleData(io_buffer_.get(), io_buffer_bytes_); + return; + } + + // The copy was triggered by EOF from the network, which + // means the job is now done. + NotifyFinishedCaching(net::URLRequestStatus(), std::string()); + ClearPendingIO(); + NotifyReadComplete(0); +} + +void ServiceWorkerWriteToCacheJob::AsyncNotifyDoneHelper( const net::URLRequestStatus& status, const std::string& status_message) { DCHECK(!status.is_io_pending()); @@ -432,39 +813,13 @@ void ServiceWorkerWriteToCacheJob::NotifyDoneHelper( void ServiceWorkerWriteToCacheJob::NotifyFinishedCaching( net::URLRequestStatus status, const std::string& status_message) { - if (did_notify_finished_) - return; - - // If all the calls to MaybeWriteHeaders/MaybeWriteData succeeded, but the - // incumbent entry wasn't actually replaced because the new entry was - // equivalent, the new version didn't actually install because it already - // exists. - if (status.status() == net::URLRequestStatus::SUCCESS && - !cache_writer_->did_replace()) { - status = - net::URLRequestStatus(net::URLRequestStatus::FAILED, net::ERR_FAILED); - version_->SetStartWorkerStatusCode(SERVICE_WORKER_ERROR_EXISTS); - } - + DCHECK(!did_notify_finished_); int size = -1; if (status.is_success()) - size = cache_writer_->bytes_written(); - + size = writer_ ? writer_->amount_written() : 0; version_->script_cache_map()->NotifyFinishedCaching(url_, size, status, status_message); did_notify_finished_ = true; } -scoped_ptr -ServiceWorkerWriteToCacheJob::CreateCacheResponseReader() { - if (incumbent_response_id_ != kInvalidServiceWorkerResponseId) - return context_->storage()->CreateResponseReader(incumbent_response_id_); - return nullptr; -} - -scoped_ptr -ServiceWorkerWriteToCacheJob::CreateCacheResponseWriter() { - return context_->storage()->CreateResponseWriter(response_id_); -} - } // namespace content diff --git a/content/browser/service_worker/service_worker_write_to_cache_job.h b/content/browser/service_worker/service_worker_write_to_cache_job.h index deee1070fd9ce8..9fcd4b8675b0d3 100644 --- a/content/browser/service_worker/service_worker_write_to_cache_job.h +++ b/content/browser/service_worker/service_worker_write_to_cache_job.h @@ -15,14 +15,13 @@ #include "content/common/service_worker/service_worker_status_code.h" #include "content/common/service_worker/service_worker_types.h" #include "content/public/common/resource_type.h" -#include "net/base/net_errors.h" #include "net/url_request/url_request.h" #include "net/url_request/url_request_job.h" namespace content { -class ServiceWorkerCacheWriter; class ServiceWorkerContextCore; +class ServiceWorkerResponseWriter; class ServiceWorkerVersions; // A URLRequestJob derivative used to cache the main script @@ -59,6 +58,10 @@ class CONTENT_EXPORT ServiceWorkerWriteToCacheJob UpdateAfter24Hours); FRIEND_TEST_ALL_PREFIXES(ServiceWorkerContextRequestHandlerTest, UpdateForceBypassCache); + class NetDataConsumer; + class PassThroughConsumer; + class Comparer; + class Copier; ~ServiceWorkerWriteToCacheJob() override; @@ -83,12 +86,14 @@ class CONTENT_EXPORT ServiceWorkerWriteToCacheJob int buf_size, int* bytes_read); - // Callbacks for writing headers and data via |cache_writer_|. Note that since - // the MaybeWriteHeaders and MaybeWriteData methods on |cache_writer_| are - // guaranteed not to do short writes, these functions only receive a - // net::Error indicating success or failure, not a count of bytes written. - void OnWriteHeadersComplete(net::Error error); - void OnWriteDataComplete(net::Error error); + void CommitHeadersAndNotifyHeadersComplete(); + void WriteHeaders(const base::Closure& callback); + void OnWriteHeadersComplete(const base::Closure& callback, int result); + void WriteData(net::IOBuffer* buf, + int amount_to_write, + const base::Callback& callback); + void OnWriteDataComplete(const base::Callback& callback, + int result); // net::URLRequest::Delegate overrides that observe the net request. void OnReceivedRedirect(net::URLRequest* request, @@ -108,23 +113,20 @@ class CONTENT_EXPORT ServiceWorkerWriteToCacheJob bool CheckPathRestriction(net::URLRequest* request); - // Writes network data back to the script cache if needed, and notifies the - // script cache of fetch completion at EOF. This function might need to do - // asynchronous IO; if so, it signals this through setting the URLRequestJob's - // status to IO_PENDING. After this function returns, if the URLRequestJob - // isn't IO_PENDING, all of the data in |io_buffer_| has been written back to - // the script cache if necessary. + void SetPendingIO(); + void ClearPendingIO(); + void OnPassThroughComplete(); + void OnCompareComplete(int bytes_matched, bool is_equal); + void CopyIncumbent(int bytes_to_copy); + void OnCopyComplete(ServiceWorkerStatusCode status); void HandleNetData(int bytes_read); - void NotifyDoneHelper(const net::URLRequestStatus& status, - const std::string& status_message); + void AsyncNotifyDoneHelper(const net::URLRequestStatus& status, + const std::string& status_message); void NotifyFinishedCaching(net::URLRequestStatus status, const std::string& status_message); - scoped_ptr CreateCacheResponseReader(); - scoped_ptr CreateCacheResponseWriter(); - ResourceType resource_type_; // Differentiate main script and imports scoped_refptr io_buffer_; int io_buffer_bytes_; @@ -136,7 +138,7 @@ class CONTENT_EXPORT ServiceWorkerWriteToCacheJob scoped_ptr http_info_; scoped_ptr writer_; scoped_refptr version_; - scoped_ptr cache_writer_; + scoped_ptr consumer_; bool has_been_killed_; bool did_notify_started_; bool did_notify_finished_; diff --git a/content/content_browser.gypi b/content/content_browser.gypi index 992d6a4bdde4be..f2885143f6eacc 100644 --- a/content/content_browser.gypi +++ b/content/content_browser.gypi @@ -1370,8 +1370,6 @@ 'browser/service_worker/embedded_worker_instance.h', 'browser/service_worker/embedded_worker_registry.cc', 'browser/service_worker/embedded_worker_registry.h', - 'browser/service_worker/service_worker_cache_writer.cc', - 'browser/service_worker/service_worker_cache_writer.h', 'browser/service_worker/service_worker_context_core.cc', 'browser/service_worker/service_worker_context_core.h', 'browser/service_worker/service_worker_context_observer.h', diff --git a/content/content_tests.gypi b/content/content_tests.gypi index a16e9094873675..3dde040f706fa9 100644 --- a/content/content_tests.gypi +++ b/content/content_tests.gypi @@ -555,7 +555,6 @@ '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_writer_unittest.cc', 'browser/service_worker/service_worker_context_request_handler_unittest.cc', 'browser/service_worker/service_worker_context_unittest.cc', 'browser/service_worker/service_worker_controllee_request_handler_unittest.cc',