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