| // Copyright 2013 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 "base/memory/scoped_ptr.h" |
| #include "base/message_loop/message_loop_proxy.h" |
| #include "base/values.h" |
| #include "content/child/indexed_db/indexed_db_dispatcher.h" |
| #include "content/child/indexed_db/webidbcursor_impl.h" |
| #include "content/child/thread_safe_sender.h" |
| #include "content/common/indexed_db/indexed_db_key.h" |
| #include "content/common/indexed_db/indexed_db_key_range.h" |
| #include "content/common/indexed_db/indexed_db_messages.h" |
| #include "ipc/ipc_sync_message_filter.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/WebKit/public/platform/WebBlobInfo.h" |
| #include "third_party/WebKit/public/platform/WebData.h" |
| #include "third_party/WebKit/public/platform/WebIDBCallbacks.h" |
| |
| using blink::WebBlobInfo; |
| using blink::WebData; |
| using blink::WebIDBCallbacks; |
| using blink::WebIDBCursor; |
| using blink::WebIDBDatabase; |
| using blink::WebIDBDatabaseError; |
| using blink::WebIDBKey; |
| using blink::WebVector; |
| |
| namespace content { |
| namespace { |
| |
| class MockCallbacks : public WebIDBCallbacks { |
| public: |
| MockCallbacks() : error_seen_(false) {} |
| |
| virtual void onError(const WebIDBDatabaseError&) { error_seen_ = true; } |
| |
| bool error_seen() const { return error_seen_; } |
| |
| private: |
| bool error_seen_; |
| }; |
| |
| class MockDispatcher : public IndexedDBDispatcher { |
| public: |
| MockDispatcher(ThreadSafeSender* sender) : IndexedDBDispatcher(sender) {} |
| |
| virtual bool Send(IPC::Message* msg) OVERRIDE { |
| delete msg; |
| return true; |
| } |
| }; |
| |
| } // namespace |
| |
| class IndexedDBDispatcherTest : public testing::Test { |
| public: |
| IndexedDBDispatcherTest() |
| : message_loop_proxy_(base::MessageLoopProxy::current()), |
| sync_message_filter_(new IPC::SyncMessageFilter(NULL)), |
| thread_safe_sender_(new ThreadSafeSender(message_loop_proxy_.get(), |
| sync_message_filter_.get())) {} |
| |
| protected: |
| scoped_refptr<base::MessageLoopProxy> message_loop_proxy_; |
| scoped_refptr<IPC::SyncMessageFilter> sync_message_filter_; |
| scoped_refptr<ThreadSafeSender> thread_safe_sender_; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(IndexedDBDispatcherTest); |
| }; |
| |
| TEST_F(IndexedDBDispatcherTest, ValueSizeTest) { |
| const std::vector<char> data(kMaxIDBValueSizeInBytes + 1); |
| const WebData value(&data.front(), data.size()); |
| const WebVector<WebBlobInfo> web_blob_info; |
| const int32 ipc_dummy_id = -1; |
| const int64 transaction_id = 1; |
| const int64 object_store_id = 2; |
| |
| MockCallbacks callbacks; |
| IndexedDBDispatcher dispatcher(thread_safe_sender_.get()); |
| IndexedDBKey key(0, blink::WebIDBKeyTypeNumber); |
| dispatcher.RequestIDBDatabasePut(ipc_dummy_id, |
| transaction_id, |
| object_store_id, |
| value, |
| web_blob_info, |
| key, |
| WebIDBDatabase::AddOrUpdate, |
| &callbacks, |
| WebVector<long long>(), |
| WebVector<WebVector<WebIDBKey> >()); |
| |
| EXPECT_TRUE(callbacks.error_seen()); |
| } |
| |
| TEST_F(IndexedDBDispatcherTest, KeyAndValueSizeTest) { |
| const size_t kKeySize = 1024 * 1024; |
| |
| const std::vector<char> data(kMaxIDBValueSizeInBytes - kKeySize); |
| const WebData value(&data.front(), data.size()); |
| const WebVector<WebBlobInfo> web_blob_info; |
| const IndexedDBKey key( |
| base::string16(kKeySize / sizeof(base::string16::value_type), 'x')); |
| |
| const int32 ipc_dummy_id = -1; |
| const int64 transaction_id = 1; |
| const int64 object_store_id = 2; |
| |
| MockCallbacks callbacks; |
| IndexedDBDispatcher dispatcher(thread_safe_sender_.get()); |
| dispatcher.RequestIDBDatabasePut(ipc_dummy_id, |
| transaction_id, |
| object_store_id, |
| value, |
| web_blob_info, |
| key, |
| WebIDBDatabase::AddOrUpdate, |
| &callbacks, |
| WebVector<long long>(), |
| WebVector<WebVector<WebIDBKey> >()); |
| |
| EXPECT_TRUE(callbacks.error_seen()); |
| } |
| |
| namespace { |
| |
| class CursorCallbacks : public WebIDBCallbacks { |
| public: |
| CursorCallbacks(scoped_ptr<WebIDBCursor>* cursor) : cursor_(cursor) {} |
| |
| virtual void onSuccess(const WebData&, |
| const WebVector<WebBlobInfo>&) OVERRIDE {} |
| virtual void onSuccess(WebIDBCursor* cursor, |
| const WebIDBKey& key, |
| const WebIDBKey& primaryKey, |
| const WebData& value, |
| const WebVector<WebBlobInfo>&) OVERRIDE { |
| cursor_->reset(cursor); |
| } |
| |
| private: |
| scoped_ptr<WebIDBCursor>* cursor_; |
| }; |
| |
| } // namespace |
| |
| TEST_F(IndexedDBDispatcherTest, CursorTransactionId) { |
| const int32 ipc_database_id = -1; |
| const int64 transaction_id = 1234; |
| const int64 object_store_id = 2; |
| const int32 index_id = 3; |
| const WebIDBCursor::Direction direction = WebIDBCursor::Next; |
| const bool key_only = false; |
| |
| MockDispatcher dispatcher(thread_safe_sender_.get()); |
| |
| // First case: successful cursor open. |
| { |
| scoped_ptr<WebIDBCursor> cursor; |
| EXPECT_EQ(0UL, dispatcher.cursor_transaction_ids_.size()); |
| |
| // Make a cursor request. This should record the transaction id. |
| dispatcher.RequestIDBDatabaseOpenCursor(ipc_database_id, |
| transaction_id, |
| object_store_id, |
| index_id, |
| IndexedDBKeyRange(), |
| direction, |
| key_only, |
| blink::WebIDBDatabase::NormalTask, |
| new CursorCallbacks(&cursor)); |
| |
| // Verify that the transaction id was captured. |
| EXPECT_EQ(1UL, dispatcher.cursor_transaction_ids_.size()); |
| EXPECT_FALSE(cursor.get()); |
| |
| int32 ipc_callbacks_id = dispatcher.cursor_transaction_ids_.begin()->first; |
| |
| IndexedDBMsg_CallbacksSuccessIDBCursor_Params params; |
| params.ipc_thread_id = dispatcher.CurrentWorkerId(); |
| params.ipc_callbacks_id = ipc_callbacks_id; |
| |
| // Now simululate the cursor response. |
| params.ipc_cursor_id = WebIDBCursorImpl::kInvalidCursorId; |
| dispatcher.OnSuccessOpenCursor(params); |
| |
| EXPECT_EQ(0UL, dispatcher.cursor_transaction_ids_.size()); |
| |
| EXPECT_TRUE(cursor.get()); |
| |
| WebIDBCursorImpl* impl = static_cast<WebIDBCursorImpl*>(cursor.get()); |
| |
| // This is the primary expectation of this test: the transaction id was |
| // applied to the cursor. |
| EXPECT_EQ(transaction_id, impl->transaction_id()); |
| } |
| |
| // Second case: null cursor (no data in range) |
| { |
| scoped_ptr<WebIDBCursor> cursor; |
| EXPECT_EQ(0UL, dispatcher.cursor_transaction_ids_.size()); |
| |
| // Make a cursor request. This should record the transaction id. |
| dispatcher.RequestIDBDatabaseOpenCursor(ipc_database_id, |
| transaction_id, |
| object_store_id, |
| index_id, |
| IndexedDBKeyRange(), |
| direction, |
| key_only, |
| blink::WebIDBDatabase::NormalTask, |
| new CursorCallbacks(&cursor)); |
| |
| // Verify that the transaction id was captured. |
| EXPECT_EQ(1UL, dispatcher.cursor_transaction_ids_.size()); |
| EXPECT_FALSE(cursor.get()); |
| |
| int32 ipc_callbacks_id = dispatcher.cursor_transaction_ids_.begin()->first; |
| |
| // Now simululate a "null cursor" response. |
| IndexedDBMsg_CallbacksSuccessValue_Params params; |
| params.ipc_thread_id = dispatcher.CurrentWorkerId(); |
| params.ipc_callbacks_id = ipc_callbacks_id; |
| dispatcher.OnSuccessValue(params); |
| |
| // Ensure the map result was deleted. |
| EXPECT_EQ(0UL, dispatcher.cursor_transaction_ids_.size()); |
| EXPECT_FALSE(cursor.get()); |
| } |
| } |
| |
| namespace { |
| |
| class MockCursor : public WebIDBCursorImpl { |
| public: |
| MockCursor(int32 ipc_cursor_id, |
| int64 transaction_id, |
| ThreadSafeSender* thread_safe_sender) |
| : WebIDBCursorImpl(ipc_cursor_id, transaction_id, thread_safe_sender), |
| reset_count_(0) {} |
| |
| // This method is virtual so it can be overridden in unit tests. |
| virtual void ResetPrefetchCache() OVERRIDE { ++reset_count_; } |
| |
| int reset_count() const { return reset_count_; } |
| |
| private: |
| int reset_count_; |
| }; |
| |
| } // namespace |
| |
| TEST_F(IndexedDBDispatcherTest, CursorReset) { |
| scoped_ptr<WebIDBCursor> cursor; |
| MockDispatcher dispatcher(thread_safe_sender_.get()); |
| |
| const int32 ipc_database_id = 0; |
| const int32 object_store_id = 0; |
| const int32 index_id = 0; |
| const bool key_only = false; |
| const int cursor1_ipc_id = 1; |
| const int cursor2_ipc_id = 2; |
| const int other_cursor_ipc_id = 2; |
| const int cursor1_transaction_id = 1; |
| const int cursor2_transaction_id = 2; |
| const int other_transaction_id = 3; |
| |
| scoped_ptr<MockCursor> cursor1( |
| new MockCursor(WebIDBCursorImpl::kInvalidCursorId, |
| cursor1_transaction_id, |
| thread_safe_sender_.get())); |
| |
| scoped_ptr<MockCursor> cursor2( |
| new MockCursor(WebIDBCursorImpl::kInvalidCursorId, |
| cursor2_transaction_id, |
| thread_safe_sender_.get())); |
| |
| dispatcher.cursors_[cursor1_ipc_id] = cursor1.get(); |
| dispatcher.cursors_[cursor2_ipc_id] = cursor2.get(); |
| |
| EXPECT_EQ(0, cursor1->reset_count()); |
| EXPECT_EQ(0, cursor2->reset_count()); |
| |
| // Other transaction: |
| dispatcher.RequestIDBDatabaseGet(ipc_database_id, |
| other_transaction_id, |
| object_store_id, |
| index_id, |
| IndexedDBKeyRange(), |
| key_only, |
| new MockCallbacks()); |
| |
| EXPECT_EQ(0, cursor1->reset_count()); |
| EXPECT_EQ(0, cursor2->reset_count()); |
| |
| // Same transaction: |
| dispatcher.RequestIDBDatabaseGet(ipc_database_id, |
| cursor1_transaction_id, |
| object_store_id, |
| index_id, |
| IndexedDBKeyRange(), |
| key_only, |
| new MockCallbacks()); |
| |
| EXPECT_EQ(1, cursor1->reset_count()); |
| EXPECT_EQ(0, cursor2->reset_count()); |
| |
| // Same transaction and same cursor: |
| dispatcher.RequestIDBCursorContinue(IndexedDBKey(), |
| IndexedDBKey(), |
| new MockCallbacks(), |
| cursor1_ipc_id, |
| cursor1_transaction_id); |
| |
| EXPECT_EQ(1, cursor1->reset_count()); |
| EXPECT_EQ(0, cursor2->reset_count()); |
| |
| // Same transaction and different cursor: |
| dispatcher.RequestIDBCursorContinue(IndexedDBKey(), |
| IndexedDBKey(), |
| new MockCallbacks(), |
| other_cursor_ipc_id, |
| cursor1_transaction_id); |
| |
| EXPECT_EQ(2, cursor1->reset_count()); |
| EXPECT_EQ(0, cursor2->reset_count()); |
| |
| cursor1.reset(); |
| cursor2.reset(); |
| } |
| |
| } // namespace content |