| // Copyright (c) 2012 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 <list> |
| |
| #include "base/bind.h" |
| #include "base/utf_string_conversions.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "webkit/dom_storage/dom_storage_cached_area.h" |
| #include "webkit/dom_storage/dom_storage_proxy.h" |
| |
| namespace dom_storage { |
| |
| namespace { |
| // A mock implementation of the DomStorageProxy interface. |
| class MockProxy : public DomStorageProxy { |
| public: |
| MockProxy() { |
| ResetObservations(); |
| } |
| |
| // DomStorageProxy interface for use by DomStorageCachedArea. |
| |
| virtual void LoadArea(int connection_id, ValuesMap* values, |
| const CompletionCallback& callback) OVERRIDE { |
| pending_callbacks_.push_back(callback); |
| observed_load_area_ = true; |
| observed_connection_id_ = connection_id; |
| *values = load_area_return_values_; |
| } |
| |
| virtual void SetItem(int connection_id, const string16& key, |
| const string16& value, const GURL& page_url, |
| const CompletionCallback& callback) OVERRIDE { |
| pending_callbacks_.push_back(callback); |
| observed_set_item_ = true; |
| observed_connection_id_ = connection_id; |
| observed_key_ = key; |
| observed_value_ = value; |
| observed_page_url_ = page_url; |
| } |
| |
| virtual void RemoveItem(int connection_id, const string16& key, |
| const GURL& page_url, |
| const CompletionCallback& callback) OVERRIDE { |
| pending_callbacks_.push_back(callback); |
| observed_remove_item_ = true; |
| observed_connection_id_ = connection_id; |
| observed_key_ = key; |
| observed_page_url_ = page_url; |
| } |
| |
| virtual void ClearArea(int connection_id, |
| const GURL& page_url, |
| const CompletionCallback& callback) OVERRIDE { |
| pending_callbacks_.push_back(callback); |
| observed_clear_area_ = true; |
| observed_connection_id_ = connection_id; |
| observed_page_url_ = page_url; |
| } |
| |
| // Methods and members for use by test fixtures. |
| |
| void ResetObservations() { |
| observed_load_area_ = false; |
| observed_set_item_ = false; |
| observed_remove_item_ = false; |
| observed_clear_area_ = false; |
| observed_connection_id_ = 0; |
| observed_key_.clear(); |
| observed_value_.clear(); |
| observed_page_url_ = GURL(); |
| } |
| |
| void CompleteAllPendingCallbacks() { |
| while (!pending_callbacks_.empty()) |
| CompleteOnePendingCallback(true); |
| } |
| |
| void CompleteOnePendingCallback(bool success) { |
| ASSERT_TRUE(!pending_callbacks_.empty()); |
| pending_callbacks_.front().Run(success); |
| pending_callbacks_.pop_front(); |
| } |
| |
| typedef std::list<CompletionCallback> CallbackList; |
| |
| ValuesMap load_area_return_values_; |
| CallbackList pending_callbacks_; |
| bool observed_load_area_; |
| bool observed_set_item_; |
| bool observed_remove_item_; |
| bool observed_clear_area_; |
| int observed_connection_id_; |
| string16 observed_key_; |
| string16 observed_value_; |
| GURL observed_page_url_; |
| |
| private: |
| virtual ~MockProxy() {} |
| }; |
| } // namespace |
| |
| class DomStorageCachedAreaTest : public testing::Test { |
| public: |
| DomStorageCachedAreaTest() |
| : kNamespaceId(10), |
| kOrigin("http://dom_storage/"), |
| kKey(ASCIIToUTF16("key")), |
| kValue(ASCIIToUTF16("value")), |
| kPageUrl("http://dom_storage/page") { |
| } |
| |
| const int64 kNamespaceId; |
| const GURL kOrigin; |
| const string16 kKey; |
| const string16 kValue; |
| const GURL kPageUrl; |
| |
| virtual void SetUp() { |
| mock_proxy_ = new MockProxy(); |
| } |
| |
| bool IsPrimed(DomStorageCachedArea* cached_area) { |
| return cached_area->map_.get(); |
| } |
| |
| bool IsIgnoringAllMutations(DomStorageCachedArea* cached_area) { |
| return cached_area->ignore_all_mutations_; |
| } |
| |
| bool IsIgnoringKeyMutations(DomStorageCachedArea* cached_area, |
| const string16& key) { |
| return cached_area->should_ignore_key_mutation(key); |
| } |
| |
| void ResetAll(DomStorageCachedArea* cached_area) { |
| cached_area->Reset(); |
| mock_proxy_->ResetObservations(); |
| mock_proxy_->pending_callbacks_.clear(); |
| } |
| |
| void ResetCacheOnly(DomStorageCachedArea* cached_area) { |
| cached_area->Reset(); |
| } |
| |
| protected: |
| scoped_refptr<MockProxy> mock_proxy_; |
| }; |
| |
| TEST_F(DomStorageCachedAreaTest, Basics) { |
| EXPECT_TRUE(mock_proxy_->HasOneRef()); |
| scoped_refptr<DomStorageCachedArea> cached_area = |
| new DomStorageCachedArea(kNamespaceId, kOrigin, mock_proxy_); |
| EXPECT_EQ(kNamespaceId, cached_area->namespace_id()); |
| EXPECT_EQ(kOrigin, cached_area->origin()); |
| EXPECT_FALSE(mock_proxy_->HasOneRef()); |
| cached_area->ApplyMutation(NullableString16(kKey, false), |
| NullableString16(kValue, false)); |
| EXPECT_FALSE(IsPrimed(cached_area)); |
| |
| ResetAll(cached_area); |
| EXPECT_EQ(kNamespaceId, cached_area->namespace_id()); |
| EXPECT_EQ(kOrigin, cached_area->origin()); |
| |
| const int kConnectionId = 1; |
| EXPECT_EQ(0u, cached_area->GetLength(kConnectionId)); |
| EXPECT_TRUE(cached_area->SetItem(kConnectionId, kKey, kValue, kPageUrl)); |
| EXPECT_EQ(1u, cached_area->GetLength(kConnectionId)); |
| EXPECT_EQ(kKey, cached_area->GetKey(kConnectionId, 0).string()); |
| EXPECT_EQ(kValue, cached_area->GetItem(kConnectionId, kKey).string()); |
| cached_area->RemoveItem(kConnectionId, kKey, kPageUrl); |
| EXPECT_EQ(0u, cached_area->GetLength(kConnectionId)); |
| } |
| |
| TEST_F(DomStorageCachedAreaTest, Getters) { |
| const int kConnectionId = 7; |
| scoped_refptr<DomStorageCachedArea> cached_area = |
| new DomStorageCachedArea(kNamespaceId, kOrigin, mock_proxy_); |
| |
| // GetLength, we expect to see one call to load in the proxy. |
| EXPECT_FALSE(IsPrimed(cached_area)); |
| EXPECT_EQ(0u, cached_area->GetLength(kConnectionId)); |
| EXPECT_TRUE(IsPrimed(cached_area)); |
| EXPECT_TRUE(mock_proxy_->observed_load_area_); |
| EXPECT_EQ(kConnectionId, mock_proxy_->observed_connection_id_); |
| EXPECT_EQ(1u, mock_proxy_->pending_callbacks_.size()); |
| EXPECT_TRUE(IsIgnoringAllMutations(cached_area)); |
| mock_proxy_->CompleteAllPendingCallbacks(); |
| EXPECT_FALSE(IsIgnoringAllMutations(cached_area)); |
| |
| // GetKey, expect the one call to load. |
| ResetAll(cached_area); |
| EXPECT_FALSE(IsPrimed(cached_area)); |
| EXPECT_TRUE(cached_area->GetKey(kConnectionId, 2).is_null()); |
| EXPECT_TRUE(IsPrimed(cached_area)); |
| EXPECT_TRUE(mock_proxy_->observed_load_area_); |
| EXPECT_EQ(kConnectionId, mock_proxy_->observed_connection_id_); |
| EXPECT_EQ(1u, mock_proxy_->pending_callbacks_.size()); |
| |
| // GetItem, ditto. |
| ResetAll(cached_area); |
| EXPECT_FALSE(IsPrimed(cached_area)); |
| EXPECT_TRUE(cached_area->GetItem(kConnectionId, kKey).is_null()); |
| EXPECT_TRUE(IsPrimed(cached_area)); |
| EXPECT_TRUE(mock_proxy_->observed_load_area_); |
| EXPECT_EQ(kConnectionId, mock_proxy_->observed_connection_id_); |
| EXPECT_EQ(1u, mock_proxy_->pending_callbacks_.size()); |
| } |
| |
| TEST_F(DomStorageCachedAreaTest, Setters) { |
| const int kConnectionId = 7; |
| scoped_refptr<DomStorageCachedArea> cached_area = |
| new DomStorageCachedArea(kNamespaceId, kOrigin, mock_proxy_); |
| |
| // SetItem, we expect a call to load followed by a call to set item |
| // in the proxy. |
| EXPECT_FALSE(IsPrimed(cached_area)); |
| EXPECT_TRUE(cached_area->SetItem(kConnectionId, kKey, kValue, kPageUrl)); |
| EXPECT_TRUE(IsPrimed(cached_area)); |
| EXPECT_TRUE(mock_proxy_->observed_load_area_); |
| EXPECT_TRUE(mock_proxy_->observed_set_item_); |
| EXPECT_EQ(kConnectionId, mock_proxy_->observed_connection_id_); |
| EXPECT_EQ(kPageUrl, mock_proxy_->observed_page_url_); |
| EXPECT_EQ(kKey, mock_proxy_->observed_key_); |
| EXPECT_EQ(kValue, mock_proxy_->observed_value_); |
| EXPECT_EQ(2u, mock_proxy_->pending_callbacks_.size()); |
| |
| // Clear, we expect a just the one call to clear in the proxy since |
| // there's no need to load the data prior to deleting it. |
| ResetAll(cached_area); |
| EXPECT_FALSE(IsPrimed(cached_area)); |
| cached_area->Clear(kConnectionId, kPageUrl); |
| EXPECT_TRUE(IsPrimed(cached_area)); |
| EXPECT_TRUE(mock_proxy_->observed_clear_area_); |
| EXPECT_EQ(kConnectionId, mock_proxy_->observed_connection_id_); |
| EXPECT_EQ(kPageUrl, mock_proxy_->observed_page_url_); |
| EXPECT_EQ(1u, mock_proxy_->pending_callbacks_.size()); |
| |
| // RemoveItem with nothing to remove, expect just one call to load. |
| ResetAll(cached_area); |
| EXPECT_FALSE(IsPrimed(cached_area)); |
| cached_area->RemoveItem(kConnectionId, kKey, kPageUrl); |
| EXPECT_TRUE(IsPrimed(cached_area)); |
| EXPECT_TRUE(mock_proxy_->observed_load_area_); |
| EXPECT_FALSE(mock_proxy_->observed_remove_item_); |
| EXPECT_EQ(kConnectionId, mock_proxy_->observed_connection_id_); |
| EXPECT_EQ(1u, mock_proxy_->pending_callbacks_.size()); |
| |
| // RemoveItem with something to remove, expect a call to load followed |
| // by a call to remove. |
| ResetAll(cached_area); |
| mock_proxy_->load_area_return_values_[kKey] = NullableString16(kValue, false); |
| EXPECT_FALSE(IsPrimed(cached_area)); |
| cached_area->RemoveItem(kConnectionId, kKey, kPageUrl); |
| EXPECT_TRUE(IsPrimed(cached_area)); |
| EXPECT_TRUE(mock_proxy_->observed_load_area_); |
| EXPECT_TRUE(mock_proxy_->observed_remove_item_); |
| EXPECT_EQ(kConnectionId, mock_proxy_->observed_connection_id_); |
| EXPECT_EQ(kPageUrl, mock_proxy_->observed_page_url_); |
| EXPECT_EQ(kKey, mock_proxy_->observed_key_); |
| EXPECT_EQ(2u, mock_proxy_->pending_callbacks_.size()); |
| } |
| |
| TEST_F(DomStorageCachedAreaTest, MutationsAreIgnoredUntilLoadCompletion) { |
| const int kConnectionId = 7; |
| scoped_refptr<DomStorageCachedArea> cached_area = |
| new DomStorageCachedArea(kNamespaceId, kOrigin, mock_proxy_); |
| EXPECT_TRUE(cached_area->GetItem(kConnectionId, kKey).is_null()); |
| EXPECT_TRUE(IsPrimed(cached_area)); |
| EXPECT_TRUE(IsIgnoringAllMutations(cached_area)); |
| |
| // Before load completion, the mutation should be ignored. |
| cached_area->ApplyMutation(NullableString16(kKey, false), |
| NullableString16(kValue, false)); |
| EXPECT_TRUE(cached_area->GetItem(kConnectionId, kKey).is_null()); |
| |
| // Call the load completion callback. |
| mock_proxy_->CompleteOnePendingCallback(true); |
| EXPECT_FALSE(IsIgnoringAllMutations(cached_area)); |
| |
| // Verify that mutations are now applied. |
| cached_area->ApplyMutation(NullableString16(kKey, false), |
| NullableString16(kValue, false)); |
| EXPECT_EQ(kValue, cached_area->GetItem(kConnectionId, kKey).string()); |
| } |
| |
| TEST_F(DomStorageCachedAreaTest, MutationsAreIgnoredUntilClearCompletion) { |
| const int kConnectionId = 4; |
| scoped_refptr<DomStorageCachedArea> cached_area = |
| new DomStorageCachedArea(kNamespaceId, kOrigin, mock_proxy_); |
| cached_area->Clear(kConnectionId, kPageUrl); |
| EXPECT_TRUE(IsIgnoringAllMutations(cached_area)); |
| mock_proxy_->CompleteOnePendingCallback(true); |
| EXPECT_FALSE(IsIgnoringAllMutations(cached_area)); |
| |
| // Verify that calling Clear twice works as expected, the first |
| // completion callback should have been cancelled. |
| ResetCacheOnly(cached_area); |
| cached_area->Clear(kConnectionId, kPageUrl); |
| EXPECT_TRUE(IsIgnoringAllMutations(cached_area)); |
| cached_area->Clear(kConnectionId, kPageUrl); |
| EXPECT_TRUE(IsIgnoringAllMutations(cached_area)); |
| mock_proxy_->CompleteOnePendingCallback(true); |
| EXPECT_TRUE(IsIgnoringAllMutations(cached_area)); |
| mock_proxy_->CompleteOnePendingCallback(true); |
| EXPECT_FALSE(IsIgnoringAllMutations(cached_area)); |
| } |
| |
| TEST_F(DomStorageCachedAreaTest, KeyMutationsAreIgnoredUntilCompletion) { |
| const int kConnectionId = 8; |
| scoped_refptr<DomStorageCachedArea> cached_area = |
| new DomStorageCachedArea(kNamespaceId, kOrigin, mock_proxy_); |
| |
| // SetItem |
| EXPECT_TRUE(cached_area->SetItem(kConnectionId, kKey, kValue, kPageUrl)); |
| mock_proxy_->CompleteOnePendingCallback(true); // load completion |
| EXPECT_FALSE(IsIgnoringAllMutations(cached_area)); |
| EXPECT_TRUE(IsIgnoringKeyMutations(cached_area, kKey)); |
| cached_area->ApplyMutation(NullableString16(kKey, false), |
| NullableString16(true)); |
| EXPECT_EQ(kValue, cached_area->GetItem(kConnectionId, kKey).string()); |
| mock_proxy_->CompleteOnePendingCallback(true); // set completion |
| EXPECT_FALSE(IsIgnoringKeyMutations(cached_area, kKey)); |
| |
| // RemoveItem |
| cached_area->RemoveItem(kConnectionId, kKey, kPageUrl); |
| EXPECT_TRUE(IsIgnoringKeyMutations(cached_area, kKey)); |
| mock_proxy_->CompleteOnePendingCallback(true); // remove completion |
| EXPECT_FALSE(IsIgnoringKeyMutations(cached_area, kKey)); |
| |
| // Multiple mutations to the same key. |
| EXPECT_TRUE(cached_area->SetItem(kConnectionId, kKey, kValue, kPageUrl)); |
| cached_area->RemoveItem(kConnectionId, kKey, kPageUrl); |
| EXPECT_TRUE(IsIgnoringKeyMutations(cached_area, kKey)); |
| mock_proxy_->CompleteOnePendingCallback(true); // set completion |
| EXPECT_TRUE(IsIgnoringKeyMutations(cached_area, kKey)); |
| mock_proxy_->CompleteOnePendingCallback(true); // remove completion |
| EXPECT_FALSE(IsIgnoringKeyMutations(cached_area, kKey)); |
| |
| // A failed set item operation should Reset the cache. |
| EXPECT_TRUE(cached_area->SetItem(kConnectionId, kKey, kValue, kPageUrl)); |
| EXPECT_TRUE(IsIgnoringKeyMutations(cached_area, kKey)); |
| mock_proxy_->CompleteOnePendingCallback(false); |
| EXPECT_FALSE(IsPrimed(cached_area)); |
| } |
| |
| } // namespace dom_storage |