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