// 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 message_loop_proxy_; scoped_refptr sync_message_filter_; scoped_refptr thread_safe_sender_; private: DISALLOW_COPY_AND_ASSIGN(IndexedDBDispatcherTest); }; TEST_F(IndexedDBDispatcherTest, ValueSizeTest) { const std::vector data(kMaxIDBValueSizeInBytes + 1); const WebData value(&data.front(), data.size()); const WebVector 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(), WebVector >()); EXPECT_TRUE(callbacks.error_seen()); } TEST_F(IndexedDBDispatcherTest, KeyAndValueSizeTest) { const size_t kKeySize = 1024 * 1024; const std::vector data(kMaxIDBValueSizeInBytes - kKeySize); const WebData value(&data.front(), data.size()); const WebVector 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(), WebVector >()); EXPECT_TRUE(callbacks.error_seen()); } namespace { class CursorCallbacks : public WebIDBCallbacks { public: CursorCallbacks(scoped_ptr* cursor) : cursor_(cursor) {} virtual void onSuccess(const WebData&, const WebVector&) OVERRIDE {} virtual void onSuccess(WebIDBCursor* cursor, const WebIDBKey& key, const WebIDBKey& primaryKey, const WebData& value, const WebVector&) OVERRIDE { cursor_->reset(cursor); } private: scoped_ptr* 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 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(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 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 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 cursor1( new MockCursor(WebIDBCursorImpl::kInvalidCursorId, cursor1_transaction_id, thread_safe_sender_.get())); scoped_ptr 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