diff options
author | michaeln@google.com <michaeln@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-10-21 18:09:33 +0000 |
---|---|---|
committer | michaeln@google.com <michaeln@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-10-21 18:09:33 +0000 |
commit | 073aabe9e48306216d979f03d6c2fc37d25d42e9 (patch) | |
tree | a9088a3c8be87fa57208074c7de65e998b865f94 /webkit | |
parent | c54d269e0b8a057533c1a1455a2acea0659629b9 (diff) | |
download | chromium_src-073aabe9e48306216d979f03d6c2fc37d25d42e9.zip chromium_src-073aabe9e48306216d979f03d6c2fc37d25d42e9.tar.gz chromium_src-073aabe9e48306216d979f03d6c2fc37d25d42e9.tar.bz2 |
AppCacheResponse storage implementation
* classes AppCacheResponseReader and AppCacheResponseWriter
* also implements AppCacheStorage.LoadResponseInfo()
* using a memory-backed disk_cache for now, so not really on disk yet
TEST=appcache_response_unittest.cc
BUG=none
Review URL: http://codereview.chromium.org/269062
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@29670 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'webkit')
-rw-r--r-- | webkit/appcache/DEPS | 3 | ||||
-rw-r--r-- | webkit/appcache/appcache_backend_impl.h | 1 | ||||
-rw-r--r-- | webkit/appcache/appcache_response.cc | 262 | ||||
-rw-r--r-- | webkit/appcache/appcache_response.h | 174 | ||||
-rw-r--r-- | webkit/appcache/appcache_response_unittest.cc | 716 | ||||
-rw-r--r-- | webkit/appcache/appcache_service.h | 1 | ||||
-rw-r--r-- | webkit/appcache/appcache_storage.cc | 38 | ||||
-rw-r--r-- | webkit/appcache/appcache_storage.h | 170 | ||||
-rw-r--r-- | webkit/appcache/appcache_working_set.cc | 2 | ||||
-rw-r--r-- | webkit/appcache/mock_appcache_storage.cc | 82 | ||||
-rw-r--r-- | webkit/appcache/mock_appcache_storage.h | 41 | ||||
-rw-r--r-- | webkit/tools/test_shell/test_shell.gyp | 1 | ||||
-rw-r--r-- | webkit/webkit.gyp | 2 |
13 files changed, 1378 insertions, 115 deletions
diff --git a/webkit/appcache/DEPS b/webkit/appcache/DEPS new file mode 100644 index 0000000..8608d5f --- /dev/null +++ b/webkit/appcache/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+net/disk_cache", +] diff --git a/webkit/appcache/appcache_backend_impl.h b/webkit/appcache/appcache_backend_impl.h index ff3dfe5..00385e5e 100644 --- a/webkit/appcache/appcache_backend_impl.h +++ b/webkit/appcache/appcache_backend_impl.h @@ -5,7 +5,6 @@ #ifndef WEBKIT_APPCACHE_APPCACHE_BACKEND_IMPL_H_ #define WEBKIT_APPCACHE_APPCACHE_BACKEND_IMPL_H_ -#include "base/logging.h" #include "base/hash_tables.h" #include "webkit/appcache/appcache_host.h" diff --git a/webkit/appcache/appcache_response.cc b/webkit/appcache/appcache_response.cc new file mode 100644 index 0000000..3952d59 --- /dev/null +++ b/webkit/appcache/appcache_response.cc @@ -0,0 +1,262 @@ +// Copyright (c) 2009 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 "webkit/appcache/appcache_response.h" + +#include "base/logging.h" +#include "base/message_loop.h" +#include "base/pickle.h" +#include "base/string_util.h" +#include "net/base/net_errors.h" +#include "net/base/io_buffer.h" +#include "net/disk_cache/disk_cache.h" +#include "webkit/appcache/appcache_service.h" + +using disk_cache::Entry; + +namespace appcache { + +namespace { + +// Disk cache entry data indices. +enum { + kResponseInfoIndex, + kResponseContentIndex +}; + +// Disk cache entry keys. +std::string response_key(int64 response_id) { + return Int64ToString(response_id); +} + +// An IOBuffer that wraps a pickle's data. Ownership of the +// pickle is transfered to the WrappedPickleIOBuffer object. +class WrappedPickleIOBuffer : public net::WrappedIOBuffer { + public: + explicit WrappedPickleIOBuffer(const Pickle* pickle) : + net::WrappedIOBuffer(reinterpret_cast<const char*>(pickle->data())), + pickle_(pickle) { + DCHECK(pickle->data()); + } + + private: + scoped_ptr<const Pickle> pickle_; +}; + +} // anon namespace + + +// AppCacheResponseInfo ---------------------------------------------- + +AppCacheResponseInfo::AppCacheResponseInfo( + AppCacheService* service, int64 response_id, + net::HttpResponseInfo* http_info) + : response_id_(response_id), http_response_info_(http_info), + service_(service) { + DCHECK(http_info); + DCHECK(response_id != kNoResponseId); + service_->storage()->working_set()->AddResponseInfo(this); +} + +AppCacheResponseInfo::~AppCacheResponseInfo() { + service_->storage()->working_set()->RemoveResponseInfo(this); +} + + +// AppCacheResponseIO ---------------------------------------------- + +AppCacheResponseIO::AppCacheResponseIO( + int64 response_id, disk_cache::Backend* disk_cache) + : response_id_(response_id), disk_cache_(disk_cache), + entry_(NULL), user_callback_(NULL), + ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)), + ALLOW_THIS_IN_INITIALIZER_LIST(raw_callback_( + new net::CancelableCompletionCallback<AppCacheResponseIO>( + this, &AppCacheResponseIO::OnRawIOComplete))) { +} + +AppCacheResponseIO::~AppCacheResponseIO() { + raw_callback_->Cancel(); + if (entry_) + entry_->Close(); +} + +void AppCacheResponseIO::ScheduleIOCompletionCallback(int result) { + MessageLoop::current()->PostTask(FROM_HERE, + method_factory_.NewRunnableMethod( + &AppCacheResponseIO::OnIOComplete, result)); +} + +void AppCacheResponseIO::InvokeUserCompletionCallback(int result) { + // Clear the user callback and buffers prior to invoking the callback + // so the caller can schedule additional operations in the callback. + buffer_ = NULL; + info_buffer_ = NULL; + net::CompletionCallback* temp_user_callback = user_callback_; + user_callback_ = NULL; + temp_user_callback->Run(result); +} + +void AppCacheResponseIO::ReadRaw(int index, int offset, + net::IOBuffer* buf, int buf_len) { + DCHECK(entry_); + raw_callback_->AddRef(); // Balanced in OnRawIOComplete. + int rv = entry_->ReadData(index, offset, buf, buf_len, raw_callback_); + if (rv != net::ERR_IO_PENDING) { + raw_callback_->Release(); + ScheduleIOCompletionCallback(rv); + } +} + +void AppCacheResponseIO::WriteRaw(int index, int offset, + net::IOBuffer* buf, int buf_len) { + DCHECK(entry_); + const bool kTruncate = true; + raw_callback_->AddRef(); // Balanced in OnRawIOComplete. + int rv = entry_->WriteData(index, offset, buf, buf_len, raw_callback_, + kTruncate); + if (rv != net::ERR_IO_PENDING) { + raw_callback_->Release(); + ScheduleIOCompletionCallback(rv); + } +} + +void AppCacheResponseIO::OnRawIOComplete(int result) { + raw_callback_->Release(); // Balance the AddRefs + OnIOComplete(result); +} + + +// AppCacheResponseReader ---------------------------------------------- + +void AppCacheResponseReader::ReadInfo(HttpResponseInfoIOBuffer* info_buf, + net::CompletionCallback* callback) { + DCHECK(callback && !IsReadPending()); + DCHECK(info_buf && !info_buf->http_info.get()); + DCHECK(!buffer_.get() && !info_buffer_.get()); + + user_callback_ = callback; // cleared on completion + + if (!OpenEntryIfNeeded()) { + ScheduleIOCompletionCallback(net::ERR_CACHE_MISS); + return; + } + + int size = entry_->GetDataSize(kResponseInfoIndex); + info_buffer_ = info_buf; + buffer_ = new net::IOBuffer(size); + ReadRaw(kResponseInfoIndex, 0, buffer_.get(), size); +} + +void AppCacheResponseReader::ReadData(net::IOBuffer* buf, int buf_len, + net::CompletionCallback* callback) { + DCHECK(callback && !IsReadPending()); + DCHECK(buf && (buf_len >= 0)); + DCHECK(!buffer_.get() && !info_buffer_.get()); + + user_callback_ = callback; // cleared on completion + + if (!OpenEntryIfNeeded()) { + ScheduleIOCompletionCallback(net::ERR_CACHE_MISS); + return; + } + + buffer_ = buf; + if (read_position_ + buf_len > range_length_) { + // TODO(michaeln): What about integer overflows? + DCHECK(range_length_ >= read_position_); + buf_len = range_length_ - read_position_; + } + ReadRaw(kResponseContentIndex, range_offset_ + read_position_, + buf, buf_len); +} + +void AppCacheResponseReader::SetReadRange(int offset, int length) { + DCHECK(!IsReadPending() && !read_position_); + range_offset_ = offset; + range_length_ = length; +} + +void AppCacheResponseReader::OnIOComplete(int result) { + if (result >= 0) { + if (info_buffer_.get()) { + // Allocate and deserialize the http info structure. + Pickle pickle(buffer_->data(), result); + bool response_truncated = false; + info_buffer_->http_info.reset(new net::HttpResponseInfo); + info_buffer_->http_info->InitFromPickle(pickle, &response_truncated); + DCHECK(!response_truncated); + } else { + read_position_ += result; + } + } + InvokeUserCompletionCallback(result); +} + +bool AppCacheResponseReader::OpenEntryIfNeeded() { + if (!entry_) + disk_cache_->OpenEntry(response_key(response_id_), &entry_); + return entry_ ? true : false; +} + + +// AppCacheResponseWriter ---------------------------------------------- + +void AppCacheResponseWriter::WriteInfo(HttpResponseInfoIOBuffer* info_buf, + net::CompletionCallback* callback) { + DCHECK(callback && !IsWritePending()); + DCHECK(info_buf && info_buf->http_info.get()); + DCHECK(!buffer_.get() && !info_buffer_.get()); + + user_callback_ = callback; // cleared on completion + + if (!CreateEntryIfNeeded()) { + ScheduleIOCompletionCallback(net::ERR_FAILED); + return; + } + + const bool kSkipTransientHeaders = true; + const bool kTruncated = false; + Pickle* pickle = new Pickle; + info_buf->http_info->Persist(pickle, kSkipTransientHeaders, kTruncated); + write_amount_ = static_cast<int>(pickle->size()); + buffer_ = new WrappedPickleIOBuffer(pickle); // takes ownership of pickle + WriteRaw(kResponseInfoIndex, 0, buffer_, write_amount_); +} + +void AppCacheResponseWriter::WriteData(net::IOBuffer* buf, int buf_len, + net::CompletionCallback* callback) { + DCHECK(callback && !IsWritePending()); + DCHECK(buf && (buf_len >= 0)); + DCHECK(!buffer_.get() && !info_buffer_.get()); + + user_callback_ = callback; // cleared on completion + + if (!CreateEntryIfNeeded()) { + ScheduleIOCompletionCallback(net::ERR_FAILED); + return; + } + + buffer_ = buf; + write_amount_ = buf_len; + WriteRaw(kResponseContentIndex, write_position_, buf, buf_len); +} + +void AppCacheResponseWriter::OnIOComplete(int result) { + if (result >= 0) { + DCHECK(write_amount_ == result); + if (!info_buffer_.get()) + write_position_ += result; + } + InvokeUserCompletionCallback(result); +} + +bool AppCacheResponseWriter::CreateEntryIfNeeded() { + if (!entry_) + disk_cache_->CreateEntry(response_key(response_id_), &entry_); + return entry_ ? true : false; +} + +} // namespace appcache + diff --git a/webkit/appcache/appcache_response.h b/webkit/appcache/appcache_response.h index c3a08e1..e82521a 100644 --- a/webkit/appcache/appcache_response.h +++ b/webkit/appcache/appcache_response.h @@ -5,23 +5,25 @@ #ifndef WEBKIT_APPCACHE_APPCACHE_RESPONSE_H_ #define WEBKIT_APPCACHE_APPCACHE_RESPONSE_H_ -#include "base/logging.h" +#include "base/compiler_specific.h" #include "base/ref_counted.h" +#include "base/scoped_ptr.h" #include "net/base/completion_callback.h" #include "net/http/http_response_info.h" #include "webkit/appcache/appcache_interfaces.h" -#include "webkit/appcache/appcache_service.h" -#include "webkit/appcache/appcache_storage.h" namespace net { class IOBuffer; } namespace disk_cache { +class Entry; class Backend; }; namespace appcache { +class AppCacheService; + // Response info for a particular response id. Instances are tracked in // the working set. class AppCacheResponseInfo @@ -29,17 +31,9 @@ class AppCacheResponseInfo public: // AppCacheResponseInfo takes ownership of the http_info. AppCacheResponseInfo(AppCacheService* service, int64 response_id, - net::HttpResponseInfo* http_info) - : response_id_(response_id), http_response_info_(http_info), - service_(service) { - DCHECK(http_info); - DCHECK(response_id != kNoResponseId); - service_->storage()->working_set()->AddResponseInfo(this); - } - - ~AppCacheResponseInfo() { - service_->storage()->working_set()->RemoveResponseInfo(this); - } + net::HttpResponseInfo* http_info); + ~AppCacheResponseInfo(); + // TODO(michaeln): should the ctor/dtor be hidden from public view? int64 response_id() const { return response_id_; } @@ -53,24 +47,48 @@ class AppCacheResponseInfo const AppCacheService* service_; }; +// A refcounted wrapper for HttpResponseInfo so we can apply the +// refcounting semantics used with IOBuffer with these structures too. +struct HttpResponseInfoIOBuffer + : public base::RefCountedThreadSafe<HttpResponseInfoIOBuffer> { + scoped_ptr<net::HttpResponseInfo> http_info; + + HttpResponseInfoIOBuffer() {} + HttpResponseInfoIOBuffer(net::HttpResponseInfo* info) : http_info(info) {} +}; + // Common base class for response reader and writer. class AppCacheResponseIO { public: - virtual ~AppCacheResponseIO() {} + virtual ~AppCacheResponseIO(); int64 response_id() const { return response_id_; } + protected: - explicit AppCacheResponseIO( - int64 response_id, disk_cache::Backend* disk_cache) - : response_id_(response_id), disk_cache_(disk_cache) {} + friend class ScopedRunnableMethodFactory<AppCacheResponseIO>; + + AppCacheResponseIO(int64 response_id, disk_cache::Backend* disk_cache); + + virtual void OnIOComplete(int result) = 0; + + bool IsIOPending() { return user_callback_ ? true : false; } + void ScheduleIOCompletionCallback(int result); + void InvokeUserCompletionCallback(int result); + void ReadRaw(int index, int offset, net::IOBuffer* buf, int buf_len); + void WriteRaw(int index, int offset, net::IOBuffer* buf, int buf_len); + const int64 response_id_; disk_cache::Backend* disk_cache_; -}; + disk_cache::Entry* entry_; + scoped_refptr<HttpResponseInfoIOBuffer> info_buffer_; + scoped_refptr<net::IOBuffer> buffer_; + net::CompletionCallback* user_callback_; -// A refcounted wrapper for HttpResponseInfo so we can apply the -// refcounting semantics used with IOBuffer with these structures too. -struct HttpResponseInfoIOBuffer - : public base::RefCountedThreadSafe<HttpResponseInfoIOBuffer> { - scoped_ptr<net::HttpResponseInfo> http_info; + private: + void OnRawIOComplete(int result); + + ScopedRunnableMethodFactory<AppCacheResponseIO> method_factory_; + scoped_refptr<net::CancelableCompletionCallback<AppCacheResponseIO> > + raw_callback_; }; // Reads existing response data from storage. If the object is deleted @@ -79,55 +97,52 @@ struct HttpResponseInfoIOBuffer // operation. In other words, instances are safe to delete at will. class AppCacheResponseReader : public AppCacheResponseIO { public: - // Reads http info from storage. Returns the number of bytes read + // Reads http info from storage. Always returns the result of the read + // asynchronously through the 'callback'. Returns the number of bytes read // or a net:: error code. Guaranteed to not perform partial reads of - // the info data. ERR_IO_PENDING is returned if the - // operation could not be completed synchronously, in which case the reader - // acquires a reference to the provided 'info_buf' until completion at which - // time the callback is invoked with either a negative error code or the - // number of bytes written. The 'info_buf' argument should contain a NULL - // http_info when ReadInfo is called. The 'callback' is a required parameter. + // the info data. The reader acquires a reference to the 'info_buf' until + // completion at which time the callback is invoked with either a negative + // error code or the number of bytes read. The 'info_buf' argument should + // contain a NULL http_info when ReadInfo is called. The 'callback' is a + // required parameter. // Should only be called where there is no Read operation in progress. - int ReadInfo(HttpResponseInfoIOBuffer* info_buf, - net::CompletionCallback* callback) { - DCHECK(info_buf && !info_buf->http_info.get()); - return -2; - } + void ReadInfo(HttpResponseInfoIOBuffer* info_buf, + net::CompletionCallback* callback); - // Reads data from storage. Returns the number of bytes read + // Reads data from storage. Always returns the result of the read + // asynchronously through the 'callback'. Returns the number of bytes read // or a net:: error code. EOF is indicated with a return value of zero. - // ERR_IO_PENDING is returned if the operation could not be completed - // synchronously, in which case the reader acquires a reference to the - // provided 'buf' until completion at which time the callback is invoked - // with either a negative error code or the number of bytes read. The - // 'callback' is a required parameter. + // The reader acquires a reference to the provided 'buf' until completion + // at which time the callback is invoked with either a negative error code + // or the number of bytes read. The 'callback' is a required parameter. // Should only be called where there is no Read operation in progress. - int ReadData(net::IOBuffer* buf, int buf_len, - net::CompletionCallback* callback) { return -2; } + void ReadData(net::IOBuffer* buf, int buf_len, + net::CompletionCallback* callback); // Returns true if there is a read operation, for data or info, pending. - bool IsReadPending() { return false; } + bool IsReadPending() { return IsIOPending(); } // Used to support range requests. If not called, the reader will // read the entire response body. If called, this must be called prior // to the first call to the ReadData method. - void SetReadRange(int64 offset, int64 length) { - range_offset_ = offset; - range_length_ = length; - } + void SetReadRange(int offset, int length); private: friend class AppCacheStorageImpl; friend class MockAppCacheStorage; // Should only be constructed by the storage class. - explicit AppCacheResponseReader( - int64 response_id, disk_cache::Backend* disk_cache) + AppCacheResponseReader(int64 response_id, disk_cache::Backend* disk_cache) : AppCacheResponseIO(response_id, disk_cache), - range_offset_(0), range_length_(kint64max) {} + range_offset_(0), range_length_(kint32max), + read_position_(0) {} + + virtual void OnIOComplete(int result); + bool OpenEntryIfNeeded(); - int64 range_offset_; - int64 range_length_; + int range_offset_; + int range_length_; + int read_position_; }; // Writes new response data to storage. If the object is deleted @@ -136,43 +151,44 @@ class AppCacheResponseReader : public AppCacheResponseIO { // operation. In other words, instances are safe to delete at will. class AppCacheResponseWriter : public AppCacheResponseIO { public: - // Writes the http info to storage. Returns the number of bytes written - // or a net:: error code. ERR_IO_PENDING is returned if the - // operation could not be completed synchronously, in which case the writer - // acquires a reference to the provided 'info_buf' until completion at which - // time the callback is invoked with either a negative error code or the - // number of bytes written. The 'callback' is a required parameter. The - // contents of 'info_buf' are not modified. + // Writes the http info to storage. Always returns the result of the write + // asynchronously through the 'callback'. Returns the number of bytes written + // or a net:: error code. The writer acquires a reference to the 'info_buf' + // until completion at which time the callback is invoked with either a + // 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. - int WriteInfo(HttpResponseInfoIOBuffer* info_buf, - net::CompletionCallback* callback) { - DCHECK(info_buf && info_buf->http_info.get()); - return -2; - } + void WriteInfo(HttpResponseInfoIOBuffer* info_buf, + net::CompletionCallback* callback); - // Writes data to storage. Returns the number of bytes written + // Writes data to storage. Always returns the result of the write + // asynchronously through the 'callback'. Returns the number of bytes written // or a net:: error code. Guaranteed to not perform partial writes. - // ERR_IO_PENDING is returned if the operation could not be completed - // synchronously, in which case the writer acquires a reference to the - // provided 'buf' until completion at which time the callback is invoked - // with either a negative error code or the number of bytes written. The - // 'callback' is a required parameter. The contents of 'buf' are not - // modified. + // The writer acquires a reference to the provided 'buf' until completion at + // which time the callback is invoked with either a negative error code or + // 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. - int WriteData(net::IOBuffer* buf, int buf_len, - net::CompletionCallback* callback) { return -2; } + void WriteData(net::IOBuffer* buf, int buf_len, + net::CompletionCallback* callback); // Returns true if there is a write pending. - bool IsWritePending() { return false; } + bool IsWritePending() { return IsIOPending(); } private: friend class AppCacheStorageImpl; friend class MockAppCacheStorage; // Should only be constructed by the storage class. - explicit AppCacheResponseWriter( - int64 response_id, disk_cache::Backend* disk_cache) - : AppCacheResponseIO(response_id, disk_cache) {} + AppCacheResponseWriter(int64 response_id, disk_cache::Backend* disk_cache) + : AppCacheResponseIO(response_id, disk_cache), + write_position_(0), write_amount_(0) {} + + virtual void OnIOComplete(int result); + bool CreateEntryIfNeeded(); + + int write_position_; + int write_amount_; }; } // namespace appcache diff --git a/webkit/appcache/appcache_response_unittest.cc b/webkit/appcache/appcache_response_unittest.cc new file mode 100644 index 0000000..abc01a9 --- /dev/null +++ b/webkit/appcache/appcache_response_unittest.cc @@ -0,0 +1,716 @@ +// Copyright (c) 2009 The Chromium Authos. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/compiler_specific.h" +#include "base/pickle.h" +#include "base/thread.h" +#include "base/waitable_event.h" +#include "net/base/io_buffer.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/appcache/appcache_response.h" +#include "webkit/appcache/mock_appcache_service.h" + +using net::IOBuffer; + +namespace appcache { + +class AppCacheResponseTest : public testing::Test { + public: + + // Test Harness ------------------------------------------------------------- + + // Helper class used to verify test results + class MockStorageDelegate : public AppCacheStorage::Delegate { + public: + explicit MockStorageDelegate(AppCacheResponseTest* test) + : loaded_info_id_(0), test_(test) { + } + + virtual void OnResponseInfoLoaded(AppCacheResponseInfo* info, + int64 response_id) { + loaded_info_ = info; + loaded_info_id_ = response_id; + test_->ScheduleNextTask(); + } + + scoped_refptr<AppCacheResponseInfo> loaded_info_; + int64 loaded_info_id_; + AppCacheResponseTest* test_; + }; + + // Helper class run a test on our io_thread. The io_thread + // is spun up once and reused for all tests. + template <class Method> + class WrapperTask : public Task { + public: + WrapperTask(AppCacheResponseTest* test, Method method) + : test_(test), method_(method) { + } + + virtual void Run() { + test_->SetUpTest(); + (test_->*method_)(); + } + + private: + AppCacheResponseTest* test_; + Method method_; + }; + + static void SetUpTestCase() { + io_thread_.reset(new base::Thread("AppCacheResponseTest Thread")); + base::Thread::Options options(MessageLoop::TYPE_IO, 0); + io_thread_->StartWithOptions(options); + } + + static void TearDownTestCase() { + io_thread_.reset(NULL); + } + + AppCacheResponseTest() + : ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)), + ALLOW_THIS_IN_INITIALIZER_LIST(read_callback_( + this, &AppCacheResponseTest::OnReadComplete)), + ALLOW_THIS_IN_INITIALIZER_LIST(read_info_callback_( + this, &AppCacheResponseTest::OnReadInfoComplete)), + ALLOW_THIS_IN_INITIALIZER_LIST(write_callback_( + this, &AppCacheResponseTest::OnWriteComplete)), + ALLOW_THIS_IN_INITIALIZER_LIST(write_info_callback_( + this, &AppCacheResponseTest::OnWriteInfoComplete)) { + } + + template <class Method> + void RunTestOnIOThread(Method method) { + test_finished_event_ .reset(new base::WaitableEvent(false, false)); + io_thread_->message_loop()->PostTask( + FROM_HERE, new WrapperTask<Method>(this, method)); + test_finished_event_->Wait(); + } + + void SetUpTest() { + DCHECK(MessageLoop::current() == io_thread_->message_loop()); + DCHECK(task_stack_.empty()); + storage_delegate_.reset(new MockStorageDelegate(this)); + service_.reset(new MockAppCacheService()); + expected_read_result_ = 0; + expected_write_result_ = 0; + written_response_id_ = 0; + should_delete_reader_in_completion_callback_ = false; + should_delete_writer_in_completion_callback_ = false; + reader_deletion_count_down_ = 0; + writer_deletion_count_down_ = 0; + read_callback_was_called_ = false; + write_callback_was_called_ = false; + } + + void TearDownTest() { + DCHECK(MessageLoop::current() == io_thread_->message_loop()); + while (!task_stack_.empty()) { + delete task_stack_.top().first; + task_stack_.pop(); + } + reader_.reset(); + read_buffer_ = NULL; + read_info_buffer_ = NULL; + writer_.reset(); + write_buffer_ = NULL; + write_info_buffer_ = NULL; + storage_delegate_.reset(); + service_.reset(); + } + + void TestFinished() { + // We unwind the stack prior to finishing up to let stack + // based objects get deleted. + DCHECK(MessageLoop::current() == io_thread_->message_loop()); + MessageLoop::current()->PostTask(FROM_HERE, + method_factory_.NewRunnableMethod( + &AppCacheResponseTest::TestFinishedUnwound)); + } + + void TestFinishedUnwound() { + TearDownTest(); + test_finished_event_->Signal(); + } + + void PushNextTask(Task* task) { + task_stack_.push(std::pair<Task*, bool>(task, false)); + } + + void PushNextTaskAsImmediate(Task* task) { + task_stack_.push(std::pair<Task*, bool>(task, true)); + } + + void ScheduleNextTask() { + DCHECK(MessageLoop::current() == io_thread_->message_loop()); + if (task_stack_.empty()) { + TestFinished(); + return; + } + scoped_ptr<Task> task(task_stack_.top().first); + bool immediate = task_stack_.top().second; + task_stack_.pop(); + if (immediate) + task->Run(); + else + MessageLoop::current()->PostTask(FROM_HERE, task.release()); + } + + // Wrappers to call AppCacheResponseReader/Writer Read and Write methods + + void WriteBasicResponse() { + static const char* kRawHttpHeaders = + "HTTP/1.0 200 OK\r\nContent-Length: 5\r\n\r\n"; + static const char* kRawHttpBody = "Hello"; + WriteResponse(MakeHttpResponseInfo(kRawHttpHeaders), kRawHttpBody); + } + + void WriteResponse(net::HttpResponseInfo* head, const char* body) { + PushNextTask(method_factory_.NewRunnableMethod( + &AppCacheResponseTest::WriteResponseBody, + new net::WrappedIOBuffer(body), strlen(body))); + WriteResponseHead(head); + } + + void WriteResponseHead(net::HttpResponseInfo* head) { + EXPECT_FALSE(writer_->IsWritePending()); + expected_write_result_ = GetHttpResponseInfoSize(head); + write_info_buffer_ = new HttpResponseInfoIOBuffer(head); + writer_->WriteInfo(write_info_buffer_, &write_info_callback_); + } + + void WriteResponseBody(scoped_refptr<IOBuffer> io_buffer, int buf_len) { + EXPECT_FALSE(writer_->IsWritePending()); + write_buffer_ = io_buffer; + expected_write_result_ = buf_len; + writer_->WriteData( + write_buffer_, buf_len, &write_callback_); + } + + void ReadResponseBody(scoped_refptr<IOBuffer> io_buffer, int buf_len) { + EXPECT_FALSE(reader_->IsReadPending()); + read_buffer_ = io_buffer; + expected_read_result_ = buf_len; + reader_->ReadData( + read_buffer_, buf_len, &read_callback_); + } + + // AppCacheResponseReader / Writer completion callbacks + + void OnWriteInfoComplete(int result) { + EXPECT_FALSE(writer_->IsWritePending()); + EXPECT_EQ(expected_write_result_, result); + ScheduleNextTask(); + } + + void OnWriteComplete(int result) { + EXPECT_FALSE(writer_->IsWritePending()); + write_callback_was_called_ = true; + EXPECT_EQ(expected_write_result_, result); + if (should_delete_writer_in_completion_callback_ && + --writer_deletion_count_down_ == 0) { + writer_.reset(); + } + ScheduleNextTask(); + } + + void OnReadInfoComplete(int result) { + EXPECT_FALSE(reader_->IsReadPending()); + EXPECT_EQ(expected_read_result_, result); + ScheduleNextTask(); + } + + void OnReadComplete(int result) { + EXPECT_FALSE(reader_->IsReadPending()); + read_callback_was_called_ = true; + EXPECT_EQ(expected_read_result_, result); + if (should_delete_reader_in_completion_callback_ && + --reader_deletion_count_down_ == 0) { + reader_.reset(); + } + ScheduleNextTask(); + } + + // Helpers to work with HttpResponseInfo objects + + net::HttpResponseInfo* MakeHttpResponseInfo(const char* raw_headers) { + net::HttpResponseInfo* info = new net::HttpResponseInfo; + info->request_time = base::Time::Now(); + info->response_time = base::Time::Now(); + info->was_cached = false; + info->headers = new net::HttpResponseHeaders(raw_headers); + return info; + } + + int GetHttpResponseInfoSize(const net::HttpResponseInfo* info) { + Pickle pickle; + return PickleHttpResonseInfo(&pickle, info); + } + + bool CompareHttpResponseInfos(const net::HttpResponseInfo* info1, + const net::HttpResponseInfo* info2) { + Pickle pickle1; + Pickle pickle2; + PickleHttpResonseInfo(&pickle1, info1); + PickleHttpResonseInfo(&pickle2, info2); + return (pickle1.size() == pickle2.size()) && + (0 == memcmp(pickle1.data(), pickle2.data(), pickle1.size())); + } + + int PickleHttpResonseInfo(Pickle* pickle, const net::HttpResponseInfo* info) { + const bool kSkipTransientHeaders = true; + const bool kTruncated = false; + info->Persist(pickle, kSkipTransientHeaders, kTruncated); + return pickle->size(); + } + + // Helpers to fill and verify blocks of memory with a value + + void FillData(char value, char* data, int data_len) { + memset(data, value, data_len); + } + + bool CheckData(char value, const char* data, int data_len) { + for (int i = 0; i < data_len; ++i, ++data) { + if (*data != value) + return false; + } + return true; + } + + // Individual Tests --------------------------------------------------------- + // Most of the individual tests involve multiple async steps. Each test + // is delineated with a section header. + + // DelegateReferences ------------------------------------------------------- + // TODO(michaeln): maybe this one belongs in appcache_storage_unittest.cc + void DelegateReferences() { + typedef scoped_refptr<AppCacheStorage::DelegateReference> + ScopedDelegateReference; + MockStorageDelegate delegate(this); + ScopedDelegateReference delegate_reference1; + ScopedDelegateReference delegate_reference2; + + EXPECT_FALSE(service_->storage()->GetDelegateReference(&delegate)); + + delegate_reference1 = + service_->storage()->GetOrCreateDelegateReference(&delegate); + EXPECT_TRUE(delegate_reference1.get()); + EXPECT_TRUE(delegate_reference1->HasOneRef()); + EXPECT_TRUE(service_->storage()->GetDelegateReference(&delegate)); + EXPECT_EQ(&delegate, + service_->storage()->GetDelegateReference(&delegate)->delegate); + EXPECT_EQ(service_->storage()->GetDelegateReference(&delegate), + service_->storage()->GetOrCreateDelegateReference(&delegate)); + delegate_reference1 = NULL; + EXPECT_FALSE(service_->storage()->GetDelegateReference(&delegate)); + + delegate_reference1 = + service_->storage()->GetOrCreateDelegateReference(&delegate); + service_->storage()->CancelDelegateCallbacks(&delegate); + EXPECT_TRUE(delegate_reference1.get()); + EXPECT_TRUE(delegate_reference1->HasOneRef()); + EXPECT_FALSE(delegate_reference1->delegate); + EXPECT_FALSE(service_->storage()->GetDelegateReference(&delegate)); + + delegate_reference2 = + service_->storage()->GetOrCreateDelegateReference(&delegate); + EXPECT_TRUE(delegate_reference2.get()); + EXPECT_TRUE(delegate_reference2->HasOneRef()); + EXPECT_EQ(&delegate, delegate_reference2->delegate); + EXPECT_NE(delegate_reference1.get(), delegate_reference2.get()); + + TestFinished(); + } + + // ReadNonExistentResponse ------------------------------------------- + static const int64 kNoSuchResponseId = 123; + + void ReadNonExistentResponse() { + // 1. Attempt to ReadInfo + // 2. Attempt to ReadData + + reader_.reset(service_->storage()->CreateResponseReader( + GURL(), kNoSuchResponseId)); + + // Push tasks in reverse order + PushNextTask(method_factory_.NewRunnableMethod( + &AppCacheResponseTest::ReadNonExistentData)); + PushNextTask(method_factory_.NewRunnableMethod( + &AppCacheResponseTest::ReadNonExistentInfo)); + ScheduleNextTask(); + } + + void ReadNonExistentInfo() { + EXPECT_FALSE(reader_->IsReadPending()); + read_info_buffer_ = new HttpResponseInfoIOBuffer(); + reader_->ReadInfo(read_info_buffer_, &read_info_callback_); + EXPECT_TRUE(reader_->IsReadPending()); + expected_read_result_ = net::ERR_CACHE_MISS; + } + + void ReadNonExistentData() { + EXPECT_FALSE(reader_->IsReadPending()); + read_buffer_ = new IOBuffer(kBlockSize); + reader_->ReadData(read_buffer_, kBlockSize, &read_callback_); + EXPECT_TRUE(reader_->IsReadPending()); + expected_read_result_ = net::ERR_CACHE_MISS; + } + + // LoadResponseInfo_Miss ---------------------------------------------------- + void LoadResponseInfo_Miss() { + PushNextTask(method_factory_.NewRunnableMethod( + &AppCacheResponseTest::LoadResponseInfo_Miss_Verify)); + service_->storage()->LoadResponseInfo(GURL(), kNoSuchResponseId, + storage_delegate_.get()); + } + + void LoadResponseInfo_Miss_Verify() { + EXPECT_EQ(kNoSuchResponseId, storage_delegate_->loaded_info_id_); + EXPECT_TRUE(!storage_delegate_->loaded_info_.get()); + TestFinished(); + } + + // LoadResponseInfo_Hit ---------------------------------------------------- + void LoadResponseInfo_Hit() { + // This tests involves multiple async steps. + // 1. Write a response headers and body to storage + // a. headers + // b. body + // 2. Use LoadResponseInfo to read the response headers back out + PushNextTask(method_factory_.NewRunnableMethod( + &AppCacheResponseTest::LoadResponseInfo_Hit_Step2)); + writer_.reset(service_->storage()->CreateResponseWriter(GURL())); + written_response_id_ = writer_->response_id(); + WriteBasicResponse(); + } + + void LoadResponseInfo_Hit_Step2() { + writer_.reset(); + PushNextTask(method_factory_.NewRunnableMethod( + &AppCacheResponseTest::LoadResponseInfo_Hit_Verify)); + service_->storage()->LoadResponseInfo(GURL(), written_response_id_, + storage_delegate_.get()); + } + + void LoadResponseInfo_Hit_Verify() { + EXPECT_EQ(written_response_id_, storage_delegate_->loaded_info_id_); + EXPECT_TRUE(storage_delegate_->loaded_info_.get()); + EXPECT_TRUE(CompareHttpResponseInfos( + write_info_buffer_->http_info.get(), + storage_delegate_->loaded_info_->http_response_info())); + TestFinished(); + } + + // WriteThenVariouslyReadResponse ------------------------------------------- + static const int kNumBlocks = 4; + static const int kBlockSize = 1024; + + void WriteThenVariouslyReadResponse() { + // This tests involves multiple async steps. + // 1. First, write a large body using multiple writes, we don't bother + // with a response head for this test. + // 2. Read the entire body, using multiple reads + // 3. Read the entire body, using one read. + // 4. Attempt to read beyond the EOF. + // 5. Read just a range. + // 6. Attempt to read beyond EOF of a range. + + // Push tasks in reverse order + PushNextTask(method_factory_.NewRunnableMethod( + &AppCacheResponseTest::ReadRangeFullyBeyondEOF)); + PushNextTask(method_factory_.NewRunnableMethod( + &AppCacheResponseTest::ReadRangePartiallyBeyondEOF)); + PushNextTask(method_factory_.NewRunnableMethod( + &AppCacheResponseTest::ReadPastEOF)); + PushNextTask(method_factory_.NewRunnableMethod( + &AppCacheResponseTest::ReadRange)); + PushNextTask(method_factory_.NewRunnableMethod( + &AppCacheResponseTest::ReadPastEOF)); + PushNextTask(method_factory_.NewRunnableMethod( + &AppCacheResponseTest::ReadAllAtOnce)); + PushNextTask(method_factory_.NewRunnableMethod( + &AppCacheResponseTest::ReadInBlocks)); + PushNextTask(method_factory_.NewRunnableMethod( + &AppCacheResponseTest::WriteOutBlocks)); + + // Get them going. + ScheduleNextTask(); + } + + void WriteOutBlocks() { + writer_.reset(service_->storage()->CreateResponseWriter(GURL())); + written_response_id_ = writer_->response_id(); + for (int i = 0; i < kNumBlocks; ++i) { + PushNextTask(method_factory_.NewRunnableMethod( + &AppCacheResponseTest::WriteOneBlock, kNumBlocks - i)); + } + ScheduleNextTask(); + } + + void WriteOneBlock(int block_number) { + scoped_refptr<IOBuffer> io_buffer = + new IOBuffer(kBlockSize); + FillData(block_number, io_buffer->data(), kBlockSize); + WriteResponseBody(io_buffer, kBlockSize); + } + + void ReadInBlocks() { + writer_.reset(); + reader_.reset(service_->storage()->CreateResponseReader( + GURL(), written_response_id_)); + for (int i = 0; i < kNumBlocks; ++i) { + PushNextTask(method_factory_.NewRunnableMethod( + &AppCacheResponseTest::ReadOneBlock, kNumBlocks - i)); + } + ScheduleNextTask(); + } + + void ReadOneBlock(int block_number) { + PushNextTask(method_factory_.NewRunnableMethod( + &AppCacheResponseTest::VerifyOneBlock, block_number)); + ReadResponseBody(new IOBuffer(kBlockSize), kBlockSize); + } + + void VerifyOneBlock(int block_number) { + EXPECT_TRUE(CheckData(block_number, read_buffer_->data(), kBlockSize)); + ScheduleNextTask(); + } + + void ReadAllAtOnce() { + PushNextTask(method_factory_.NewRunnableMethod( + &AppCacheResponseTest::VerifyAllAtOnce)); + reader_.reset(service_->storage()->CreateResponseReader( + GURL(), written_response_id_)); + int big_size = kNumBlocks * kBlockSize; + ReadResponseBody(new IOBuffer(big_size), big_size); + } + + void VerifyAllAtOnce() { + char* p = read_buffer_->data(); + for (int i = 0; i < kNumBlocks; ++i, p += kBlockSize) + EXPECT_TRUE(CheckData(i + 1, p, kBlockSize)); + ScheduleNextTask(); + } + + void ReadPastEOF() { + EXPECT_FALSE(reader_->IsReadPending()); + read_buffer_ = new IOBuffer(kBlockSize); + expected_read_result_ = 0; + reader_->ReadData( + read_buffer_, kBlockSize, &read_callback_); + } + + void ReadRange() { + PushNextTask(method_factory_.NewRunnableMethod( + &AppCacheResponseTest::VerifyRange)); + reader_.reset(service_->storage()->CreateResponseReader( + GURL(), written_response_id_)); + reader_->SetReadRange(kBlockSize, kBlockSize); + ReadResponseBody(new IOBuffer(kBlockSize), kBlockSize); + } + + void VerifyRange() { + EXPECT_TRUE(CheckData(2, read_buffer_->data(), kBlockSize)); + ScheduleNextTask(); // ReadPastEOF is scheduled next + } + + void ReadRangePartiallyBeyondEOF() { + PushNextTask(method_factory_.NewRunnableMethod( + &AppCacheResponseTest::VerifyRangeBeyondEOF)); + reader_.reset(service_->storage()->CreateResponseReader( + GURL(), written_response_id_)); + reader_->SetReadRange(kBlockSize, kNumBlocks * kBlockSize); + ReadResponseBody(new IOBuffer(kNumBlocks * kBlockSize), + kNumBlocks * kBlockSize); + expected_read_result_ = (kNumBlocks - 1) * kBlockSize; + } + + void VerifyRangeBeyondEOF() { + // Just verify the first 1k + VerifyRange(); + } + + void ReadRangeFullyBeyondEOF() { + reader_.reset(service_->storage()->CreateResponseReader( + GURL(), written_response_id_)); + reader_->SetReadRange((kNumBlocks * kBlockSize) + 1, kBlockSize); + ReadResponseBody(new IOBuffer(kBlockSize), kBlockSize); + expected_read_result_ = 0; + } + + // IOChaining ------------------------------------------- + void IOChaining() { + // 1. Write several blocks out initiating the subsequent write + // from within the completion callback of the previous write. + // 2. Read and verify several blocks in similarly chaining reads. + + // Push tasks in reverse order + PushNextTaskAsImmediate(method_factory_.NewRunnableMethod( + &AppCacheResponseTest::ReadInBlocksImmediately)); + PushNextTaskAsImmediate(method_factory_.NewRunnableMethod( + &AppCacheResponseTest::WriteOutBlocksImmediately)); + + // Get them going. + ScheduleNextTask(); + } + + void WriteOutBlocksImmediately() { + writer_.reset(service_->storage()->CreateResponseWriter(GURL())); + written_response_id_ = writer_->response_id(); + for (int i = 0; i < kNumBlocks; ++i) { + PushNextTaskAsImmediate(method_factory_.NewRunnableMethod( + &AppCacheResponseTest::WriteOneBlock, kNumBlocks - i)); + } + ScheduleNextTask(); + } + + void ReadInBlocksImmediately() { + writer_.reset(); + reader_.reset(service_->storage()->CreateResponseReader( + GURL(), written_response_id_)); + for (int i = 0; i < kNumBlocks; ++i) { + PushNextTaskAsImmediate(method_factory_.NewRunnableMethod( + &AppCacheResponseTest::ReadOneBlockImmediately, kNumBlocks - i)); + } + ScheduleNextTask(); + } + + void ReadOneBlockImmediately(int block_number) { + PushNextTaskAsImmediate(method_factory_.NewRunnableMethod( + &AppCacheResponseTest::VerifyOneBlock, block_number)); + ReadResponseBody(new IOBuffer(kBlockSize), kBlockSize); + } + + // DeleteWithinCallbacks ------------------------------------------- + void DeleteWithinCallbacks() { + // 1. Write out a few blocks normally, and upon + // completion of the last write, delete the writer. + // 2. Read in a few blocks normally, and upon completion + // of the last read, delete the reader. + + should_delete_reader_in_completion_callback_ = true; + reader_deletion_count_down_ = kNumBlocks; + should_delete_writer_in_completion_callback_ = true; + writer_deletion_count_down_ = kNumBlocks; + + PushNextTask(method_factory_.NewRunnableMethod( + &AppCacheResponseTest::ReadInBlocks)); + PushNextTask(method_factory_.NewRunnableMethod( + &AppCacheResponseTest::WriteOutBlocks)); + ScheduleNextTask(); + } + + // DeleteWithIOPending ------------------------------------------- + void DeleteWithIOPending() { + // 1. Write a few blocks normally. + // 2. Start a write, delete with it pending. + // 3. Start a read, delete with it pending. + PushNextTask(method_factory_.NewRunnableMethod( + &AppCacheResponseTest::ReadThenDelete)); + PushNextTask(method_factory_.NewRunnableMethod( + &AppCacheResponseTest::WriteThenDelete)); + PushNextTask(method_factory_.NewRunnableMethod( + &AppCacheResponseTest::WriteOutBlocks)); + ScheduleNextTask(); + } + + void WriteThenDelete() { + write_callback_was_called_ = false; + WriteOneBlock(5); + EXPECT_TRUE(writer_->IsWritePending()); + writer_.reset(); + ScheduleNextTask(); + } + + void ReadThenDelete() { + read_callback_was_called_ = false; + reader_.reset(service_->storage()->CreateResponseReader( + GURL(), written_response_id_)); + ReadResponseBody(new IOBuffer(kBlockSize), kBlockSize); + EXPECT_TRUE(reader_->IsReadPending()); + reader_.reset(); + + // Wait a moment to verify no callbacks. + MessageLoop::current()->PostDelayedTask(FROM_HERE, + method_factory_.NewRunnableMethod( + &AppCacheResponseTest::VerifyNoCallbacks), + 10); + } + + void VerifyNoCallbacks() { + EXPECT_TRUE(!write_callback_was_called_); + EXPECT_TRUE(!read_callback_was_called_); + TestFinished(); + } + + // Data members + + ScopedRunnableMethodFactory<AppCacheResponseTest> method_factory_; + scoped_ptr<base::WaitableEvent> test_finished_event_; + scoped_ptr<MockStorageDelegate> storage_delegate_; + scoped_ptr<MockAppCacheService> service_; + std::stack<std::pair<Task*, bool> > task_stack_; + + scoped_ptr<AppCacheResponseReader> reader_; + scoped_refptr<HttpResponseInfoIOBuffer> read_info_buffer_; + scoped_refptr<IOBuffer> read_buffer_; + int expected_read_result_; + net::CompletionCallbackImpl<AppCacheResponseTest> read_callback_; + net::CompletionCallbackImpl<AppCacheResponseTest> read_info_callback_; + bool should_delete_reader_in_completion_callback_; + int reader_deletion_count_down_; + bool read_callback_was_called_; + + int64 written_response_id_; + scoped_ptr<AppCacheResponseWriter> writer_; + scoped_refptr<HttpResponseInfoIOBuffer> write_info_buffer_; + scoped_refptr<IOBuffer> write_buffer_; + int expected_write_result_; + net::CompletionCallbackImpl<AppCacheResponseTest> write_callback_; + net::CompletionCallbackImpl<AppCacheResponseTest> write_info_callback_; + bool should_delete_writer_in_completion_callback_; + int writer_deletion_count_down_; + bool write_callback_was_called_; + + static scoped_ptr<base::Thread> io_thread_; +}; + +// static +scoped_ptr<base::Thread> AppCacheResponseTest::io_thread_; + +TEST_F(AppCacheResponseTest, DelegateReferences) { + RunTestOnIOThread(&AppCacheResponseTest::DelegateReferences); +} + +TEST_F(AppCacheResponseTest, ReadNonExistentResponse) { + RunTestOnIOThread(&AppCacheResponseTest::ReadNonExistentResponse); +} + +TEST_F(AppCacheResponseTest, LoadResponseInfo_Miss) { + RunTestOnIOThread(&AppCacheResponseTest::LoadResponseInfo_Miss); +} + +TEST_F(AppCacheResponseTest, LoadResponseInfo_Hit) { + RunTestOnIOThread(&AppCacheResponseTest::LoadResponseInfo_Hit); +} + +TEST_F(AppCacheResponseTest, WriteThenVariouslyReadResponse) { + RunTestOnIOThread(&AppCacheResponseTest::WriteThenVariouslyReadResponse); +} + +TEST_F(AppCacheResponseTest, IOChaining) { + RunTestOnIOThread(&AppCacheResponseTest::IOChaining); +} + +TEST_F(AppCacheResponseTest, DeleteWithinCallbacks) { + RunTestOnIOThread(&AppCacheResponseTest::DeleteWithinCallbacks); +} + +TEST_F(AppCacheResponseTest, DeleteWithIOPending) { + RunTestOnIOThread(&AppCacheResponseTest::DeleteWithIOPending); +} + +} // namespace appcache + diff --git a/webkit/appcache/appcache_service.h b/webkit/appcache/appcache_service.h index 5cdaed4..05e9329 100644 --- a/webkit/appcache/appcache_service.h +++ b/webkit/appcache/appcache_service.h @@ -6,7 +6,6 @@ #define WEBKIT_APPCACHE_APPCACHE_SERVICE_H_ #include "base/file_path.h" -#include "base/logging.h" #include "base/scoped_ptr.h" #include "testing/gtest/include/gtest/gtest_prod.h" #include "webkit/appcache/appcache_storage.h" diff --git a/webkit/appcache/appcache_storage.cc b/webkit/appcache/appcache_storage.cc new file mode 100644 index 0000000..b1fc5c4 --- /dev/null +++ b/webkit/appcache/appcache_storage.cc @@ -0,0 +1,38 @@ +// Copyright (c) 2009 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 "webkit/appcache/appcache_storage.h" + +#include "base/stl_util-inl.h" + +namespace appcache { + +AppCacheStorage::AppCacheStorage(AppCacheService* service) + : last_cache_id_(kUnitializedId), last_group_id_(kUnitializedId), + last_entry_id_(kUnitializedId), last_response_id_(kUnitializedId), + service_(service) { +} + +AppCacheStorage::~AppCacheStorage() { + STLDeleteValues(&pending_info_loads_); + DCHECK(delegate_references_.empty()); +} + +void AppCacheStorage::LoadResponseInfo( + const GURL& manifest_url, int64 id, Delegate* delegate) { + AppCacheResponseInfo* info = working_set_.GetResponseInfo(id); + if (info) { + delegate->OnResponseInfoLoaded(info, id); + return; + } + ResponseInfoLoadTask* info_load = + GetOrCreateResponseInfoLoadTask(manifest_url, id); + DCHECK(manifest_url == info_load->manifest_url()); + DCHECK(id == info_load->response_id()); + info_load->AddDelegate(GetOrCreateDelegateReference(delegate)); + info_load->StartIfNeeded(); +} + +} // namespace appcache + diff --git a/webkit/appcache/appcache_storage.h b/webkit/appcache/appcache_storage.h index 47d1af8..912bdcb 100644 --- a/webkit/appcache/appcache_storage.h +++ b/webkit/appcache/appcache_storage.h @@ -5,10 +5,14 @@ #ifndef WEBKIT_APPCACHE_APPCACHE_STORAGE_H_ #define WEBKIT_APPCACHE_APPCACHE_STORAGE_H_ +#include <map> #include <vector> +#include "base/compiler_specific.h" #include "base/basictypes.h" -#include "base/logging.h" +#include "base/ref_counted.h" +#include "net/base/net_errors.h" +#include "webkit/appcache/appcache_response.h" #include "webkit/appcache/appcache_working_set.h" class GURL; @@ -17,9 +21,6 @@ namespace appcache { class AppCache; class AppCacheGroup; -class AppCacheResponseInfo; -class AppCacheResponseReader; -class AppCacheResponseWriter; class AppCacheService; class AppCacheStorage { @@ -55,11 +56,8 @@ class AppCacheStorage { int64 cache_id, const GURL& mainfest_url) {} }; - explicit AppCacheStorage(AppCacheService* service) - : last_cache_id_(kUnitializedId), last_group_id_(kUnitializedId), - last_entry_id_(kUnitializedId), last_response_id_(kUnitializedId), - service_(service) {} - virtual ~AppCacheStorage() {} + explicit AppCacheStorage(AppCacheService* service); + virtual ~AppCacheStorage(); // Schedules a cache to be loaded from storage. Upon load completion // the delegate will be called back. If the cache already resides in @@ -82,7 +80,7 @@ class AppCacheStorage { // immediately without returning to the message loop. If the load fails, // the delegate will be called back with a NULL pointer. virtual void LoadResponseInfo( - const GURL& manifest_url, int64 response_id, Delegate* delegate) = 0; + const GURL& manifest_url, int64 response_id, Delegate* delegate); // Schedules a group and its newest complete cache to be initially stored or // incrementally updated with new changes. Upon completion the delegate @@ -115,7 +113,11 @@ class AppCacheStorage { // will not be invoked after, however any scheduled operations will still // take place. The callbacks for subsequently scheduled operations are // unaffected. - virtual void CancelDelegateCallbacks(Delegate* delegate) = 0; + void CancelDelegateCallbacks(Delegate* delegate) { + DelegateReference* delegate_reference = GetDelegateReference(delegate); + if (delegate_reference) + delegate_reference->CancelReference(); + } // Creates a reader to read a response from storage. virtual AppCacheResponseReader* CreateResponseReader( @@ -132,15 +134,12 @@ class AppCacheStorage { // Generates unique storage ids for different object types. int64 NewCacheId() { - DCHECK(last_cache_id_ != kUnitializedId); return ++last_cache_id_; } int64 NewGroupId() { - DCHECK(last_group_id_ != kUnitializedId); return ++last_group_id_; } int64 NewEntryId() { - DCHECK(last_entry_id_ != kUnitializedId); return ++last_entry_id_; } @@ -151,9 +150,128 @@ class AppCacheStorage { AppCacheService* service() { return service_; } protected: + friend class AppCacheResponseTest; + + // Helper to call a collection of delegates. + #define FOR_EACH_DELEGATE(delegates, func_and_args) \ + do { \ + for (DelegateReferenceVector::iterator it = delegates.begin(); \ + it != delegates.end(); ++it) { \ + if (it->get()->delegate) \ + it->get()->delegate->func_and_args; \ + } \ + } while (0) + + // Helper used to manage multiple references to a 'delegate' and to + // allow all pending callbacks to that delegate to be easily cancelled. + struct DelegateReference : public base::RefCounted<DelegateReference> { + Delegate* delegate; + AppCacheStorage* storage; + + DelegateReference(Delegate* delegate, AppCacheStorage* storage) + : delegate(delegate), storage(storage) { + storage->delegate_references_.insert( + DelegateReferenceMap::value_type(delegate, this)); + } + + ~DelegateReference() { + if (delegate) + storage->delegate_references_.erase(delegate); + } + + void CancelReference() { + storage->delegate_references_.erase(delegate); + storage = NULL; + delegate = NULL; + } + }; + typedef std::map<Delegate*, DelegateReference*> DelegateReferenceMap; + typedef std::vector<scoped_refptr<DelegateReference> > + DelegateReferenceVector; + + // Helper used to manage an async LoadResponseInfo calls on behalf of + // multiple callers. + // TODO(michaeln): this may be generalizable for other load/store 'tasks' + class ResponseInfoLoadTask { + public: + ResponseInfoLoadTask(const GURL& manifest_url, int64 response_id, + AppCacheStorage* storage) : + storage_(storage), + manifest_url_(manifest_url), + response_id_(response_id), + info_buffer_(new HttpResponseInfoIOBuffer), + ALLOW_THIS_IN_INITIALIZER_LIST(read_callback_( + this, &ResponseInfoLoadTask::OnReadComplete)) { + ALLOW_THIS_IN_INITIALIZER_LIST(storage_->pending_info_loads_.insert( + PendingResponseInfoLoads::value_type(response_id, this))); + } + + int64 response_id() const { return response_id_; } + const GURL& manifest_url() const { return manifest_url_; } + + void AddDelegate(DelegateReference* delegate_reference) { + delegates_.push_back(delegate_reference); + } + + void StartIfNeeded() { + if (reader_.get()) + return; + reader_.reset( + storage_->CreateResponseReader(manifest_url_, response_id_)); + reader_->ReadInfo(info_buffer_, &read_callback_); + } + + private: + void OnReadComplete(int result) { + storage_->pending_info_loads_.erase(response_id_); + scoped_refptr<AppCacheResponseInfo> info; + if (result >= 0) { + info = new AppCacheResponseInfo( + storage_->service(), reader_->response_id(), + info_buffer_->http_info.release()); + } + FOR_EACH_DELEGATE( + delegates_, OnResponseInfoLoaded(info.get(), response_id_)); + delete this; + } + + AppCacheStorage* storage_; + GURL manifest_url_; + int64 response_id_; + scoped_ptr<AppCacheResponseReader> reader_; + DelegateReferenceVector delegates_; + scoped_refptr<HttpResponseInfoIOBuffer> info_buffer_; + net::CompletionCallbackImpl<ResponseInfoLoadTask> read_callback_; + }; + + typedef std::map<int64, ResponseInfoLoadTask*> PendingResponseInfoLoads; + + DelegateReference* GetDelegateReference(Delegate* delegate) { + DelegateReferenceMap::iterator iter = + delegate_references_.find(delegate); + if (iter != delegate_references_.end()) + return iter->second; + return NULL; + } + + DelegateReference* GetOrCreateDelegateReference(Delegate* delegate) { + DelegateReference* reference = GetDelegateReference(delegate); + if (reference) + return reference; + return new DelegateReference(delegate, this); + } + + ResponseInfoLoadTask* GetOrCreateResponseInfoLoadTask( + const GURL& manifest_url, int64 response_id) { + PendingResponseInfoLoads::iterator iter = + pending_info_loads_.find(response_id); + if (iter != pending_info_loads_.end()) + return iter->second; + return new ResponseInfoLoadTask(manifest_url, response_id, this); + } + // Should only be called when creating a new response writer. int64 NewResponseId() { - DCHECK(last_response_id_ != kUnitializedId); return ++last_response_id_; } @@ -165,6 +283,8 @@ class AppCacheStorage { AppCacheWorkingSet working_set_; AppCacheService* service_; + DelegateReferenceMap delegate_references_; + PendingResponseInfoLoads pending_info_loads_; // The set of last ids must be retrieved from storage prior to being used. static const int64 kUnitializedId = -1; @@ -172,6 +292,26 @@ class AppCacheStorage { DISALLOW_COPY_AND_ASSIGN(AppCacheStorage); }; +// TODO(michaeln): Maybe? +class AppCacheStoredItem { + public: + bool is_doomed() const { return is_doomed_; } + bool is_stored() const { return is_stored_; } + + protected: + AppCacheStoredItem() : is_doomed_(false), is_stored_(false) {} + + private: + friend class AppCacheStorage; + friend class MockAppCacheStorage; + + void set_is_doomed(bool b) { is_doomed_ = b; } + void set_is_stored(bool b) { is_stored_ = b; } + + bool is_doomed_; + bool is_stored_; +}; + } // namespace appcache #endif // WEBKIT_APPCACHE_APPCACHE_STORAGE_H_ diff --git a/webkit/appcache/appcache_working_set.cc b/webkit/appcache/appcache_working_set.cc index 9b67942..2b61ea3 100644 --- a/webkit/appcache/appcache_working_set.cc +++ b/webkit/appcache/appcache_working_set.cc @@ -17,6 +17,7 @@ AppCacheWorkingSet::~AppCacheWorkingSet() { } void AppCacheWorkingSet::AddCache(AppCache* cache) { + DCHECK(cache->cache_id() != kNoCacheId); int64 cache_id = cache->cache_id(); DCHECK(caches_.find(cache_id) == caches_.end()); caches_.insert(CacheMap::value_type(cache_id, cache)); @@ -37,6 +38,7 @@ void AppCacheWorkingSet::RemoveGroup(AppCacheGroup* group) { } void AppCacheWorkingSet::AddResponseInfo(AppCacheResponseInfo* info) { + DCHECK(info->response_id() != kNoResponseId); int64 response_id = info->response_id(); DCHECK(response_infos_.find(response_id) == response_infos_.end()); response_infos_.insert(ResponseInfoMap::value_type(response_id, info)); diff --git a/webkit/appcache/mock_appcache_storage.cc b/webkit/appcache/mock_appcache_storage.cc index 4dfb8c9..3144179 100644 --- a/webkit/appcache/mock_appcache_storage.cc +++ b/webkit/appcache/mock_appcache_storage.cc @@ -7,7 +7,6 @@ #include "base/logging.h" #include "base/ref_counted.h" #include "webkit/appcache/appcache.h" -#include "webkit/appcache/appcache_backend_impl.h" #include "webkit/appcache/appcache_entry.h" #include "webkit/appcache/appcache_group.h" #include "webkit/appcache/appcache_response.h" @@ -24,6 +23,9 @@ MockAppCacheStorage::MockAppCacheStorage(AppCacheService* service) void MockAppCacheStorage::LoadCache(int64 id, Delegate* delegate) { AppCache* cache = working_set_.GetCache(id); + if (cache->HasOneRef()) { + // TODO(michaeln): make the load look async + } delegate->OnCacheLoaded(cache, id); } @@ -34,23 +36,48 @@ void MockAppCacheStorage::LoadOrCreateGroup( group = new AppCacheGroup(service_, manifest_url); DCHECK(working_set_.GetGroup(manifest_url)); } + // TODO(michaeln): make the load look async if all of the groups + // caches only have one ref delegate->OnGroupLoaded(group.get(), manifest_url); } -void MockAppCacheStorage::LoadResponseInfo( - const GURL& manifest_url, int64 id, Delegate* delegate) { - delegate->OnResponseInfoLoaded(working_set_.GetResponseInfo(id), id); -} - void MockAppCacheStorage::StoreGroupAndNewestCache( AppCacheGroup* group, Delegate* delegate) { + DCHECK(group->newest_complete_cache()); + // TODO(michaeln): write me + // 'store' the group + newest + // 'unstore' the old caches + // figure out which responses can be doomed and doom them + // OldRepsonses - NewestResponse == ToBeDoomed + + // TODO(michaeln): Make this appear async + //AddStoredGroup(group); + //AddStoredCache(group->newest_complete_cache()); + // + //foreach(group->old_caches()) + // RemoveStoredCache(old_cache); + //std::set<int64> doomed_responses_ = responses from old caches + //std::set<int64> needed_responses_ = responses from newest cache + //foreach(needed_responses_) + // doomed_responses_.remove(needed_response_); + //DoomResponses(group->manifest_url(), doomed_responses_); + delegate->OnGroupAndNewestCacheStored(group, false); } void MockAppCacheStorage::FindResponseForMainRequest( const GURL& url, Delegate* delegate) { // TODO(michaeln): write me + // + //foreach(stored_group) { + // if (group->manifest_url()->origin() != url.GetOrigin()) + // continue; + // + // look for an entry + // look for a fallback namespace + // look for a online namespace + //} delegate->OnMainResponseFound( url, kNoResponseId, false, kNoCacheId, GURL::EmptyGURL()); } @@ -65,34 +92,57 @@ void MockAppCacheStorage::MarkEntryAsForeign( if (entry) entry->add_types(AppCacheEntry::FOREIGN); } - // TODO(michaeln): actually update in storage, and if this cache is + // TODO(michaeln): in real storage update in storage, and if this cache is // being loaded be sure to update the memory cache upon load completion. } void MockAppCacheStorage::MarkGroupAsObsolete( AppCacheGroup* group, Delegate* delegate) { // TODO(michaeln): write me -} - -void MockAppCacheStorage::CancelDelegateCallbacks(Delegate* delegate) { - // TODO(michaeln): remove delegate from callback list + // remove from working_set + // remove from storage + // doom things + // group->set_obsolete(true); + // TODO(michaeln): Make this appear async } AppCacheResponseReader* MockAppCacheStorage::CreateResponseReader( const GURL& origin, int64 response_id) { - return new AppCacheResponseReader(response_id, NULL); - // TODO(michaeln): use a disk_cache + return new AppCacheResponseReader(response_id, disk_cache()); } AppCacheResponseWriter* MockAppCacheStorage::CreateResponseWriter( const GURL& manifest_url) { - return new AppCacheResponseWriter(NewResponseId(), NULL); - // TODO(michaeln): use a disk_cache + return new AppCacheResponseWriter(NewResponseId(), disk_cache()); } void MockAppCacheStorage::DoomResponses( const GURL& manifest_url, const std::vector<int64>& response_ids) { - // TODO(michaeln): write me + // We don't bother with deleting responses from mock storage. +} + +void MockAppCacheStorage::AddStoredCache(AppCache* cache) { +// cache->set_is_stored(true); + int64 cache_id = cache->cache_id(); + if (stored_caches_.find(cache_id) == stored_caches_.end()) + stored_caches_.insert(StoredCacheMap::value_type(cache_id, cache)); +} + +void MockAppCacheStorage::RemoveStoredCache(AppCache* cache) { +// cache->set_is_stored(false); + stored_caches_.erase(cache->cache_id()); +} + +void MockAppCacheStorage::AddStoredGroup(AppCacheGroup* group) { +// group->set_is_stored(true); + const GURL& url = group->manifest_url(); + if (stored_groups_.find(url) == stored_groups_.end()) + stored_groups_.insert(StoredGroupMap::value_type(url, group)); +} + +void MockAppCacheStorage::RemoveStoredGroup(AppCacheGroup* group) { +// group->set_is_stored(false); + stored_groups_.erase(group->manifest_url()); } } // namespace appcache diff --git a/webkit/appcache/mock_appcache_storage.h b/webkit/appcache/mock_appcache_storage.h index 95cc082..8efb7d9 100644 --- a/webkit/appcache/mock_appcache_storage.h +++ b/webkit/appcache/mock_appcache_storage.h @@ -5,6 +5,13 @@ #ifndef WEBKIT_APPCACHE_MOCK_APPCACHE_STORAGE_H_ #define WEBKIT_APPCACHE_MOCK_APPCACHE_STORAGE_H_ +#include <map> + +#include "base/hash_tables.h" +#include "base/scoped_ptr.h" +#include "net/disk_cache/disk_cache.h" +#include "webkit/appcache/appcache.h" +#include "webkit/appcache/appcache_group.h" #include "webkit/appcache/appcache_storage.h" namespace appcache { @@ -18,12 +25,9 @@ class MockAppCacheStorage : public AppCacheStorage { explicit MockAppCacheStorage(AppCacheService* service); virtual void LoadCache(int64 id, Delegate* delegate); virtual void LoadOrCreateGroup(const GURL& manifest_url, Delegate* delegate); - virtual void LoadResponseInfo( - const GURL& manifest_url, int64 response_id, Delegate* delegate); virtual void StoreGroupAndNewestCache( AppCacheGroup* group, Delegate* delegate); virtual void FindResponseForMainRequest(const GURL& url, Delegate* delegate); - virtual void CancelDelegateCallbacks(Delegate* delegate); virtual void MarkEntryAsForeign(const GURL& entry_url, int64 cache_id); virtual void MarkGroupAsObsolete(AppCacheGroup* group, Delegate* delegate); virtual AppCacheResponseReader* CreateResponseReader( @@ -31,6 +35,37 @@ class MockAppCacheStorage : public AppCacheStorage { virtual AppCacheResponseWriter* CreateResponseWriter(const GURL& origin); virtual void DoomResponses( const GURL& manifest_url, const std::vector<int64>& response_ids); + + private: + typedef base::hash_map<int64, scoped_refptr<AppCache> > StoredCacheMap; + typedef std::map<GURL, scoped_refptr<AppCacheGroup> > StoredGroupMap; + + void AddStoredCache(AppCache* cache); + void RemoveStoredCache(AppCache* cache); + AppCache* GetStoredCache(int64 id) { + StoredCacheMap::iterator it = stored_caches_.find(id); + return (it != stored_caches_.end()) ? it->second : NULL; + } + + void AddStoredGroup(AppCacheGroup* group); + void RemoveStoredGroup(AppCacheGroup* group); + AppCacheGroup* GetStoredGroup(const GURL& manifest_url) { + StoredGroupMap::iterator it = stored_groups_.find(manifest_url); + return (it != stored_groups_.end()) ? it->second : NULL; + } + + // Lazily constructed in-memory disk cache. + disk_cache::Backend* disk_cache() { + if (!disk_cache_.get()) { + const int kMaxCacheSize = 10 * 1024 * 1024; + disk_cache_.reset(disk_cache::CreateInMemoryCacheBackend(kMaxCacheSize)); + } + return disk_cache_.get(); + } + + StoredCacheMap stored_caches_; + StoredGroupMap stored_groups_; + scoped_ptr<disk_cache::Backend> disk_cache_; }; } // namespace appcache diff --git a/webkit/tools/test_shell/test_shell.gyp b/webkit/tools/test_shell/test_shell.gyp index 06b1ed4..5c6e100 100644 --- a/webkit/tools/test_shell/test_shell.gyp +++ b/webkit/tools/test_shell/test_shell.gyp @@ -368,6 +368,7 @@ '../../appcache/appcache_unittest.cc', '../../appcache/appcache_group_unittest.cc', '../../appcache/appcache_host_unittest.cc', + '../../appcache/appcache_response_unittest.cc', '../../appcache/appcache_storage_unittest.cc', '../../appcache/appcache_update_job_unittest.cc', '../../appcache/mock_appcache_service.h', diff --git a/webkit/webkit.gyp b/webkit/webkit.gyp index 927463a..ce249c2 100644 --- a/webkit/webkit.gyp +++ b/webkit/webkit.gyp @@ -422,9 +422,11 @@ 'appcache/appcache_interfaces.h', 'appcache/appcache_request_handler.cc', 'appcache/appcache_request_handler.h', + 'appcache/appcache_response.cc', 'appcache/appcache_response.h', 'appcache/appcache_service.cc', 'appcache/appcache_service.h', + 'appcache/appcache_storage.cc', 'appcache/appcache_storage.h', 'appcache/appcache_storage_impl.cc', 'appcache/appcache_storage_impl.h', |