Home | History | Annotate | Download | only in dom_storage
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "content/renderer/dom_storage/dom_storage_cached_area.h"
      6 
      7 #include <list>
      8 
      9 #include "base/bind.h"
     10 #include "base/strings/utf_string_conversions.h"
     11 #include "content/renderer/dom_storage/dom_storage_proxy.h"
     12 #include "testing/gtest/include/gtest/gtest.h"
     13 
     14 namespace content {
     15 
     16 namespace {
     17 // A mock implementation of the DOMStorageProxy interface.
     18 class MockProxy : public DOMStorageProxy {
     19  public:
     20   MockProxy() {
     21     ResetObservations();
     22   }
     23 
     24   // DOMStorageProxy interface for use by DOMStorageCachedArea.
     25 
     26   virtual void LoadArea(int connection_id,
     27                         DOMStorageValuesMap* values,
     28                         bool* send_log_get_messages,
     29                         const CompletionCallback& callback) OVERRIDE {
     30     pending_callbacks_.push_back(callback);
     31     observed_load_area_ = true;
     32     observed_connection_id_ = connection_id;
     33     *values = load_area_return_values_;
     34     *send_log_get_messages = false;
     35   }
     36 
     37   virtual void SetItem(int connection_id,
     38                        const base::string16& key,
     39                        const base::string16& value,
     40                        const GURL& page_url,
     41                        const CompletionCallback& callback) OVERRIDE {
     42     pending_callbacks_.push_back(callback);
     43     observed_set_item_ = true;
     44     observed_connection_id_ = connection_id;
     45     observed_key_ = key;
     46     observed_value_ = value;
     47     observed_page_url_ = page_url;
     48   }
     49 
     50   virtual void LogGetItem(int connection_id,
     51                           const base::string16& key,
     52                           const base::NullableString16& value) OVERRIDE {
     53   }
     54 
     55   virtual void RemoveItem(int connection_id,
     56                           const base::string16& key,
     57                           const GURL& page_url,
     58                           const CompletionCallback& callback) OVERRIDE {
     59     pending_callbacks_.push_back(callback);
     60     observed_remove_item_ = true;
     61     observed_connection_id_ = connection_id;
     62     observed_key_ = key;
     63     observed_page_url_ = page_url;
     64   }
     65 
     66   virtual void ClearArea(int connection_id,
     67                          const GURL& page_url,
     68                          const CompletionCallback& callback) OVERRIDE {
     69     pending_callbacks_.push_back(callback);
     70     observed_clear_area_ = true;
     71     observed_connection_id_ = connection_id;
     72     observed_page_url_ = page_url;
     73   }
     74 
     75   // Methods and members for use by test fixtures.
     76 
     77   void ResetObservations() {
     78     observed_load_area_ = false;
     79     observed_set_item_ = false;
     80     observed_remove_item_ = false;
     81     observed_clear_area_ = false;
     82     observed_connection_id_ = 0;
     83     observed_key_.clear();
     84     observed_value_.clear();
     85     observed_page_url_ = GURL();
     86   }
     87 
     88   void CompleteAllPendingCallbacks() {
     89     while (!pending_callbacks_.empty())
     90       CompleteOnePendingCallback(true);
     91   }
     92 
     93   void CompleteOnePendingCallback(bool success) {
     94     ASSERT_TRUE(!pending_callbacks_.empty());
     95     pending_callbacks_.front().Run(success);
     96     pending_callbacks_.pop_front();
     97   }
     98 
     99   typedef std::list<CompletionCallback> CallbackList;
    100 
    101   DOMStorageValuesMap load_area_return_values_;
    102   CallbackList pending_callbacks_;
    103   bool observed_load_area_;
    104   bool observed_set_item_;
    105   bool observed_remove_item_;
    106   bool observed_clear_area_;
    107   int observed_connection_id_;
    108   base::string16 observed_key_;
    109   base::string16 observed_value_;
    110   GURL observed_page_url_;
    111 
    112  private:
    113   virtual ~MockProxy() {}
    114 };
    115 
    116 }  // namespace
    117 
    118 class DOMStorageCachedAreaTest : public testing::Test {
    119  public:
    120   DOMStorageCachedAreaTest()
    121     : kNamespaceId(10),
    122       kOrigin("http://dom_storage/"),
    123       kKey(base::ASCIIToUTF16("key")),
    124       kValue(base::ASCIIToUTF16("value")),
    125       kPageUrl("http://dom_storage/page") {
    126   }
    127 
    128   const int64 kNamespaceId;
    129   const GURL kOrigin;
    130   const base::string16 kKey;
    131   const base::string16 kValue;
    132   const GURL kPageUrl;
    133 
    134   virtual void SetUp() {
    135     mock_proxy_ = new MockProxy();
    136   }
    137 
    138   bool IsPrimed(DOMStorageCachedArea* cached_area) {
    139     return cached_area->map_.get();
    140   }
    141 
    142   bool IsIgnoringAllMutations(DOMStorageCachedArea* cached_area) {
    143     return cached_area->ignore_all_mutations_;
    144   }
    145 
    146   bool IsIgnoringKeyMutations(DOMStorageCachedArea* cached_area,
    147                               const base::string16& key) {
    148     return cached_area->should_ignore_key_mutation(key);
    149   }
    150 
    151   void ResetAll(DOMStorageCachedArea* cached_area) {
    152     cached_area->Reset();
    153     mock_proxy_->ResetObservations();
    154     mock_proxy_->pending_callbacks_.clear();
    155   }
    156 
    157   void ResetCacheOnly(DOMStorageCachedArea* cached_area) {
    158     cached_area->Reset();
    159   }
    160 
    161  protected:
    162   scoped_refptr<MockProxy> mock_proxy_;
    163 };
    164 
    165 TEST_F(DOMStorageCachedAreaTest, Basics) {
    166   EXPECT_TRUE(mock_proxy_->HasOneRef());
    167   scoped_refptr<DOMStorageCachedArea> cached_area =
    168       new DOMStorageCachedArea(kNamespaceId, kOrigin, mock_proxy_.get());
    169   EXPECT_EQ(kNamespaceId, cached_area->namespace_id());
    170   EXPECT_EQ(kOrigin, cached_area->origin());
    171   EXPECT_FALSE(mock_proxy_->HasOneRef());
    172   cached_area->ApplyMutation(base::NullableString16(kKey, false),
    173                              base::NullableString16(kValue, false));
    174   EXPECT_FALSE(IsPrimed(cached_area.get()));
    175 
    176   ResetAll(cached_area.get());
    177   EXPECT_EQ(kNamespaceId, cached_area->namespace_id());
    178   EXPECT_EQ(kOrigin, cached_area->origin());
    179 
    180   const int kConnectionId = 1;
    181   EXPECT_EQ(0u, cached_area->GetLength(kConnectionId));
    182   EXPECT_TRUE(cached_area->SetItem(kConnectionId, kKey, kValue, kPageUrl));
    183   EXPECT_EQ(1u, cached_area->GetLength(kConnectionId));
    184   EXPECT_EQ(kKey, cached_area->GetKey(kConnectionId, 0).string());
    185   EXPECT_EQ(kValue, cached_area->GetItem(kConnectionId, kKey).string());
    186   cached_area->RemoveItem(kConnectionId, kKey, kPageUrl);
    187   EXPECT_EQ(0u, cached_area->GetLength(kConnectionId));
    188 }
    189 
    190 TEST_F(DOMStorageCachedAreaTest, Getters) {
    191   const int kConnectionId = 7;
    192   scoped_refptr<DOMStorageCachedArea> cached_area =
    193       new DOMStorageCachedArea(kNamespaceId, kOrigin, mock_proxy_.get());
    194 
    195   // GetLength, we expect to see one call to load in the proxy.
    196   EXPECT_FALSE(IsPrimed(cached_area.get()));
    197   EXPECT_EQ(0u, cached_area->GetLength(kConnectionId));
    198   EXPECT_TRUE(IsPrimed(cached_area.get()));
    199   EXPECT_TRUE(mock_proxy_->observed_load_area_);
    200   EXPECT_EQ(kConnectionId, mock_proxy_->observed_connection_id_);
    201   EXPECT_EQ(1u, mock_proxy_->pending_callbacks_.size());
    202   EXPECT_TRUE(IsIgnoringAllMutations(cached_area.get()));
    203   mock_proxy_->CompleteAllPendingCallbacks();
    204   EXPECT_FALSE(IsIgnoringAllMutations(cached_area.get()));
    205 
    206   // GetKey, expect the one call to load.
    207   ResetAll(cached_area.get());
    208   EXPECT_FALSE(IsPrimed(cached_area.get()));
    209   EXPECT_TRUE(cached_area->GetKey(kConnectionId, 2).is_null());
    210   EXPECT_TRUE(IsPrimed(cached_area.get()));
    211   EXPECT_TRUE(mock_proxy_->observed_load_area_);
    212   EXPECT_EQ(kConnectionId, mock_proxy_->observed_connection_id_);
    213   EXPECT_EQ(1u, mock_proxy_->pending_callbacks_.size());
    214 
    215   // GetItem, ditto.
    216   ResetAll(cached_area.get());
    217   EXPECT_FALSE(IsPrimed(cached_area.get()));
    218   EXPECT_TRUE(cached_area->GetItem(kConnectionId, kKey).is_null());
    219   EXPECT_TRUE(IsPrimed(cached_area.get()));
    220   EXPECT_TRUE(mock_proxy_->observed_load_area_);
    221   EXPECT_EQ(kConnectionId, mock_proxy_->observed_connection_id_);
    222   EXPECT_EQ(1u, mock_proxy_->pending_callbacks_.size());
    223 }
    224 
    225 TEST_F(DOMStorageCachedAreaTest, Setters) {
    226   const int kConnectionId = 7;
    227   scoped_refptr<DOMStorageCachedArea> cached_area =
    228       new DOMStorageCachedArea(kNamespaceId, kOrigin, mock_proxy_.get());
    229 
    230   // SetItem, we expect a call to load followed by a call to set item
    231   // in the proxy.
    232   EXPECT_FALSE(IsPrimed(cached_area.get()));
    233   EXPECT_TRUE(cached_area->SetItem(kConnectionId, kKey, kValue, kPageUrl));
    234   EXPECT_TRUE(IsPrimed(cached_area.get()));
    235   EXPECT_TRUE(mock_proxy_->observed_load_area_);
    236   EXPECT_TRUE(mock_proxy_->observed_set_item_);
    237   EXPECT_EQ(kConnectionId, mock_proxy_->observed_connection_id_);
    238   EXPECT_EQ(kPageUrl, mock_proxy_->observed_page_url_);
    239   EXPECT_EQ(kKey, mock_proxy_->observed_key_);
    240   EXPECT_EQ(kValue, mock_proxy_->observed_value_);
    241   EXPECT_EQ(2u, mock_proxy_->pending_callbacks_.size());
    242 
    243   // Clear, we expect a just the one call to clear in the proxy since
    244   // there's no need to load the data prior to deleting it.
    245   ResetAll(cached_area.get());
    246   EXPECT_FALSE(IsPrimed(cached_area.get()));
    247   cached_area->Clear(kConnectionId, kPageUrl);
    248   EXPECT_TRUE(IsPrimed(cached_area.get()));
    249   EXPECT_TRUE(mock_proxy_->observed_clear_area_);
    250   EXPECT_EQ(kConnectionId, mock_proxy_->observed_connection_id_);
    251   EXPECT_EQ(kPageUrl, mock_proxy_->observed_page_url_);
    252   EXPECT_EQ(1u, mock_proxy_->pending_callbacks_.size());
    253 
    254   // RemoveItem with nothing to remove, expect just one call to load.
    255   ResetAll(cached_area.get());
    256   EXPECT_FALSE(IsPrimed(cached_area.get()));
    257   cached_area->RemoveItem(kConnectionId, kKey, kPageUrl);
    258   EXPECT_TRUE(IsPrimed(cached_area.get()));
    259   EXPECT_TRUE(mock_proxy_->observed_load_area_);
    260   EXPECT_FALSE(mock_proxy_->observed_remove_item_);
    261   EXPECT_EQ(kConnectionId, mock_proxy_->observed_connection_id_);
    262   EXPECT_EQ(1u, mock_proxy_->pending_callbacks_.size());
    263 
    264   // RemoveItem with something to remove, expect a call to load followed
    265   // by a call to remove.
    266   ResetAll(cached_area.get());
    267   mock_proxy_->load_area_return_values_[kKey] =
    268       base::NullableString16(kValue, false);
    269   EXPECT_FALSE(IsPrimed(cached_area.get()));
    270   cached_area->RemoveItem(kConnectionId, kKey, kPageUrl);
    271   EXPECT_TRUE(IsPrimed(cached_area.get()));
    272   EXPECT_TRUE(mock_proxy_->observed_load_area_);
    273   EXPECT_TRUE(mock_proxy_->observed_remove_item_);
    274   EXPECT_EQ(kConnectionId, mock_proxy_->observed_connection_id_);
    275   EXPECT_EQ(kPageUrl, mock_proxy_->observed_page_url_);
    276   EXPECT_EQ(kKey, mock_proxy_->observed_key_);
    277   EXPECT_EQ(2u, mock_proxy_->pending_callbacks_.size());
    278 }
    279 
    280 TEST_F(DOMStorageCachedAreaTest, MutationsAreIgnoredUntilLoadCompletion) {
    281   const int kConnectionId = 7;
    282   scoped_refptr<DOMStorageCachedArea> cached_area =
    283       new DOMStorageCachedArea(kNamespaceId, kOrigin, mock_proxy_.get());
    284   EXPECT_TRUE(cached_area->GetItem(kConnectionId, kKey).is_null());
    285   EXPECT_TRUE(IsPrimed(cached_area.get()));
    286   EXPECT_TRUE(IsIgnoringAllMutations(cached_area.get()));
    287 
    288   // Before load completion, the mutation should be ignored.
    289   cached_area->ApplyMutation(base::NullableString16(kKey, false),
    290                              base::NullableString16(kValue, false));
    291   EXPECT_TRUE(cached_area->GetItem(kConnectionId, kKey).is_null());
    292 
    293   // Call the load completion callback.
    294   mock_proxy_->CompleteOnePendingCallback(true);
    295   EXPECT_FALSE(IsIgnoringAllMutations(cached_area.get()));
    296 
    297   // Verify that mutations are now applied.
    298   cached_area->ApplyMutation(base::NullableString16(kKey, false),
    299                              base::NullableString16(kValue, false));
    300   EXPECT_EQ(kValue, cached_area->GetItem(kConnectionId, kKey).string());
    301 }
    302 
    303 TEST_F(DOMStorageCachedAreaTest, MutationsAreIgnoredUntilClearCompletion) {
    304   const int kConnectionId = 4;
    305   scoped_refptr<DOMStorageCachedArea> cached_area =
    306       new DOMStorageCachedArea(kNamespaceId, kOrigin, mock_proxy_.get());
    307   cached_area->Clear(kConnectionId, kPageUrl);
    308   EXPECT_TRUE(IsIgnoringAllMutations(cached_area.get()));
    309   mock_proxy_->CompleteOnePendingCallback(true);
    310   EXPECT_FALSE(IsIgnoringAllMutations(cached_area.get()));
    311 
    312   // Verify that calling Clear twice works as expected, the first
    313   // completion callback should have been cancelled.
    314   ResetCacheOnly(cached_area.get());
    315   cached_area->Clear(kConnectionId, kPageUrl);
    316   EXPECT_TRUE(IsIgnoringAllMutations(cached_area.get()));
    317   cached_area->Clear(kConnectionId, kPageUrl);
    318   EXPECT_TRUE(IsIgnoringAllMutations(cached_area.get()));
    319   mock_proxy_->CompleteOnePendingCallback(true);
    320   EXPECT_TRUE(IsIgnoringAllMutations(cached_area.get()));
    321   mock_proxy_->CompleteOnePendingCallback(true);
    322   EXPECT_FALSE(IsIgnoringAllMutations(cached_area.get()));
    323 }
    324 
    325 TEST_F(DOMStorageCachedAreaTest, KeyMutationsAreIgnoredUntilCompletion) {
    326   const int kConnectionId = 8;
    327   scoped_refptr<DOMStorageCachedArea> cached_area =
    328       new DOMStorageCachedArea(kNamespaceId, kOrigin, mock_proxy_.get());
    329 
    330   // SetItem
    331   EXPECT_TRUE(cached_area->SetItem(kConnectionId, kKey, kValue, kPageUrl));
    332   mock_proxy_->CompleteOnePendingCallback(true);  // load completion
    333   EXPECT_FALSE(IsIgnoringAllMutations(cached_area.get()));
    334   EXPECT_TRUE(IsIgnoringKeyMutations(cached_area.get(), kKey));
    335   cached_area->ApplyMutation(base::NullableString16(kKey, false),
    336                              base::NullableString16());
    337   EXPECT_EQ(kValue, cached_area->GetItem(kConnectionId, kKey).string());
    338   mock_proxy_->CompleteOnePendingCallback(true);  // set completion
    339   EXPECT_FALSE(IsIgnoringKeyMutations(cached_area.get(), kKey));
    340 
    341   // RemoveItem
    342   cached_area->RemoveItem(kConnectionId, kKey, kPageUrl);
    343   EXPECT_TRUE(IsIgnoringKeyMutations(cached_area.get(), kKey));
    344   mock_proxy_->CompleteOnePendingCallback(true);  // remove completion
    345   EXPECT_FALSE(IsIgnoringKeyMutations(cached_area.get(), kKey));
    346 
    347   // Multiple mutations to the same key.
    348   EXPECT_TRUE(cached_area->SetItem(kConnectionId, kKey, kValue, kPageUrl));
    349   cached_area->RemoveItem(kConnectionId, kKey, kPageUrl);
    350   EXPECT_TRUE(IsIgnoringKeyMutations(cached_area.get(), kKey));
    351   mock_proxy_->CompleteOnePendingCallback(true);  // set completion
    352   EXPECT_TRUE(IsIgnoringKeyMutations(cached_area.get(), kKey));
    353   mock_proxy_->CompleteOnePendingCallback(true);  // remove completion
    354   EXPECT_FALSE(IsIgnoringKeyMutations(cached_area.get(), kKey));
    355 
    356   // A failed set item operation should Reset the cache.
    357   EXPECT_TRUE(cached_area->SetItem(kConnectionId, kKey, kValue, kPageUrl));
    358   EXPECT_TRUE(IsIgnoringKeyMutations(cached_area.get(), kKey));
    359   mock_proxy_->CompleteOnePendingCallback(false);
    360   EXPECT_FALSE(IsPrimed(cached_area.get()));
    361 }
    362 
    363 }  // namespace content
    364