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