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