summaryrefslogtreecommitdiffstats
path: root/webkit
diff options
context:
space:
mode:
authormichaeln@google.com <michaeln@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2009-10-21 18:09:33 +0000
committermichaeln@google.com <michaeln@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2009-10-21 18:09:33 +0000
commit073aabe9e48306216d979f03d6c2fc37d25d42e9 (patch)
treea9088a3c8be87fa57208074c7de65e998b865f94 /webkit
parentc54d269e0b8a057533c1a1455a2acea0659629b9 (diff)
downloadchromium_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/DEPS3
-rw-r--r--webkit/appcache/appcache_backend_impl.h1
-rw-r--r--webkit/appcache/appcache_response.cc262
-rw-r--r--webkit/appcache/appcache_response.h174
-rw-r--r--webkit/appcache/appcache_response_unittest.cc716
-rw-r--r--webkit/appcache/appcache_service.h1
-rw-r--r--webkit/appcache/appcache_storage.cc38
-rw-r--r--webkit/appcache/appcache_storage.h170
-rw-r--r--webkit/appcache/appcache_working_set.cc2
-rw-r--r--webkit/appcache/mock_appcache_storage.cc82
-rw-r--r--webkit/appcache/mock_appcache_storage.h41
-rw-r--r--webkit/tools/test_shell/test_shell.gyp1
-rw-r--r--webkit/webkit.gyp2
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',