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 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_
;
35 void SetItem(int connection_id
,
36 const base::string16
& key
,
37 const base::string16
& value
,
39 const CompletionCallback
& callback
) override
{
40 pending_callbacks_
.push_back(callback
);
41 observed_set_item_
= true;
42 observed_connection_id_
= connection_id
;
44 observed_value_
= value
;
45 observed_page_url_
= page_url
;
48 void RemoveItem(int connection_id
,
49 const base::string16
& key
,
51 const CompletionCallback
& callback
) override
{
52 pending_callbacks_
.push_back(callback
);
53 observed_remove_item_
= true;
54 observed_connection_id_
= connection_id
;
56 observed_page_url_
= page_url
;
59 void ClearArea(int connection_id
,
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
;
68 // Methods and members for use by test fixtures.
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();
81 void CompleteAllPendingCallbacks() {
82 while (!pending_callbacks_
.empty())
83 CompleteOnePendingCallback(true);
86 void CompleteOnePendingCallback(bool success
) {
87 ASSERT_TRUE(!pending_callbacks_
.empty());
88 pending_callbacks_
.front().Run(success
);
89 pending_callbacks_
.pop_front();
92 typedef std::list
<CompletionCallback
> CallbackList
;
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_
;
106 ~MockProxy() override
{}
111 class DOMStorageCachedAreaTest
: public testing::Test
{
113 DOMStorageCachedAreaTest()
115 kOrigin("http://dom_storage/"),
116 kKey(base::ASCIIToUTF16("key")),
117 kValue(base::ASCIIToUTF16("value")),
118 kPageUrl("http://dom_storage/page") {
121 const int64 kNamespaceId
;
123 const base::string16 kKey
;
124 const base::string16 kValue
;
127 void SetUp() override
{ mock_proxy_
= new MockProxy(); }
129 bool IsPrimed(DOMStorageCachedArea
* cached_area
) {
130 return cached_area
->map_
.get();
133 bool IsIgnoringAllMutations(DOMStorageCachedArea
* cached_area
) {
134 return cached_area
->ignore_all_mutations_
;
137 bool IsIgnoringKeyMutations(DOMStorageCachedArea
* cached_area
,
138 const base::string16
& key
) {
139 return cached_area
->should_ignore_key_mutation(key
);
142 void ResetAll(DOMStorageCachedArea
* cached_area
) {
143 cached_area
->Reset();
144 mock_proxy_
->ResetObservations();
145 mock_proxy_
->pending_callbacks_
.clear();
148 void ResetCacheOnly(DOMStorageCachedArea
* cached_area
) {
149 cached_area
->Reset();
153 scoped_refptr
<MockProxy
> mock_proxy_
;
156 TEST_F(DOMStorageCachedAreaTest
, Basics
) {
157 EXPECT_TRUE(mock_proxy_
->HasOneRef());
158 scoped_refptr
<DOMStorageCachedArea
> cached_area
=
159 new DOMStorageCachedArea(kNamespaceId
, kOrigin
, mock_proxy_
.get());
160 EXPECT_EQ(kNamespaceId
, cached_area
->namespace_id());
161 EXPECT_EQ(kOrigin
, cached_area
->origin());
162 EXPECT_FALSE(mock_proxy_
->HasOneRef());
163 cached_area
->ApplyMutation(base::NullableString16(kKey
, false),
164 base::NullableString16(kValue
, false));
165 EXPECT_FALSE(IsPrimed(cached_area
.get()));
167 ResetAll(cached_area
.get());
168 EXPECT_EQ(kNamespaceId
, cached_area
->namespace_id());
169 EXPECT_EQ(kOrigin
, cached_area
->origin());
171 const int kConnectionId
= 1;
172 EXPECT_EQ(0u, cached_area
->GetLength(kConnectionId
));
173 EXPECT_TRUE(cached_area
->SetItem(kConnectionId
, kKey
, kValue
, kPageUrl
));
174 EXPECT_EQ(1u, cached_area
->GetLength(kConnectionId
));
175 EXPECT_EQ(kKey
, cached_area
->GetKey(kConnectionId
, 0).string());
176 EXPECT_EQ(kValue
, cached_area
->GetItem(kConnectionId
, kKey
).string());
177 cached_area
->RemoveItem(kConnectionId
, kKey
, kPageUrl
);
178 EXPECT_EQ(0u, cached_area
->GetLength(kConnectionId
));
181 TEST_F(DOMStorageCachedAreaTest
, Getters
) {
182 const int kConnectionId
= 7;
183 scoped_refptr
<DOMStorageCachedArea
> cached_area
=
184 new DOMStorageCachedArea(kNamespaceId
, kOrigin
, mock_proxy_
.get());
186 // GetLength, we expect to see one call to load in the proxy.
187 EXPECT_FALSE(IsPrimed(cached_area
.get()));
188 EXPECT_EQ(0u, cached_area
->GetLength(kConnectionId
));
189 EXPECT_TRUE(IsPrimed(cached_area
.get()));
190 EXPECT_TRUE(mock_proxy_
->observed_load_area_
);
191 EXPECT_EQ(kConnectionId
, mock_proxy_
->observed_connection_id_
);
192 EXPECT_EQ(1u, mock_proxy_
->pending_callbacks_
.size());
193 EXPECT_TRUE(IsIgnoringAllMutations(cached_area
.get()));
194 mock_proxy_
->CompleteAllPendingCallbacks();
195 EXPECT_FALSE(IsIgnoringAllMutations(cached_area
.get()));
197 // GetKey, expect the one call to load.
198 ResetAll(cached_area
.get());
199 EXPECT_FALSE(IsPrimed(cached_area
.get()));
200 EXPECT_TRUE(cached_area
->GetKey(kConnectionId
, 2).is_null());
201 EXPECT_TRUE(IsPrimed(cached_area
.get()));
202 EXPECT_TRUE(mock_proxy_
->observed_load_area_
);
203 EXPECT_EQ(kConnectionId
, mock_proxy_
->observed_connection_id_
);
204 EXPECT_EQ(1u, mock_proxy_
->pending_callbacks_
.size());
207 ResetAll(cached_area
.get());
208 EXPECT_FALSE(IsPrimed(cached_area
.get()));
209 EXPECT_TRUE(cached_area
->GetItem(kConnectionId
, kKey
).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());
216 TEST_F(DOMStorageCachedAreaTest
, Setters
) {
217 const int kConnectionId
= 7;
218 scoped_refptr
<DOMStorageCachedArea
> cached_area
=
219 new DOMStorageCachedArea(kNamespaceId
, kOrigin
, mock_proxy_
.get());
221 // SetItem, we expect a call to load followed by a call to set item
223 EXPECT_FALSE(IsPrimed(cached_area
.get()));
224 EXPECT_TRUE(cached_area
->SetItem(kConnectionId
, kKey
, kValue
, kPageUrl
));
225 EXPECT_TRUE(IsPrimed(cached_area
.get()));
226 EXPECT_TRUE(mock_proxy_
->observed_load_area_
);
227 EXPECT_TRUE(mock_proxy_
->observed_set_item_
);
228 EXPECT_EQ(kConnectionId
, mock_proxy_
->observed_connection_id_
);
229 EXPECT_EQ(kPageUrl
, mock_proxy_
->observed_page_url_
);
230 EXPECT_EQ(kKey
, mock_proxy_
->observed_key_
);
231 EXPECT_EQ(kValue
, mock_proxy_
->observed_value_
);
232 EXPECT_EQ(2u, mock_proxy_
->pending_callbacks_
.size());
234 // Clear, we expect a just the one call to clear in the proxy since
235 // there's no need to load the data prior to deleting it.
236 ResetAll(cached_area
.get());
237 EXPECT_FALSE(IsPrimed(cached_area
.get()));
238 cached_area
->Clear(kConnectionId
, kPageUrl
);
239 EXPECT_TRUE(IsPrimed(cached_area
.get()));
240 EXPECT_TRUE(mock_proxy_
->observed_clear_area_
);
241 EXPECT_EQ(kConnectionId
, mock_proxy_
->observed_connection_id_
);
242 EXPECT_EQ(kPageUrl
, mock_proxy_
->observed_page_url_
);
243 EXPECT_EQ(1u, mock_proxy_
->pending_callbacks_
.size());
245 // RemoveItem with nothing to remove, expect just one call to load.
246 ResetAll(cached_area
.get());
247 EXPECT_FALSE(IsPrimed(cached_area
.get()));
248 cached_area
->RemoveItem(kConnectionId
, kKey
, kPageUrl
);
249 EXPECT_TRUE(IsPrimed(cached_area
.get()));
250 EXPECT_TRUE(mock_proxy_
->observed_load_area_
);
251 EXPECT_FALSE(mock_proxy_
->observed_remove_item_
);
252 EXPECT_EQ(kConnectionId
, mock_proxy_
->observed_connection_id_
);
253 EXPECT_EQ(1u, mock_proxy_
->pending_callbacks_
.size());
255 // RemoveItem with something to remove, expect a call to load followed
256 // by a call to remove.
257 ResetAll(cached_area
.get());
258 mock_proxy_
->load_area_return_values_
[kKey
] =
259 base::NullableString16(kValue
, false);
260 EXPECT_FALSE(IsPrimed(cached_area
.get()));
261 cached_area
->RemoveItem(kConnectionId
, kKey
, kPageUrl
);
262 EXPECT_TRUE(IsPrimed(cached_area
.get()));
263 EXPECT_TRUE(mock_proxy_
->observed_load_area_
);
264 EXPECT_TRUE(mock_proxy_
->observed_remove_item_
);
265 EXPECT_EQ(kConnectionId
, mock_proxy_
->observed_connection_id_
);
266 EXPECT_EQ(kPageUrl
, mock_proxy_
->observed_page_url_
);
267 EXPECT_EQ(kKey
, mock_proxy_
->observed_key_
);
268 EXPECT_EQ(2u, mock_proxy_
->pending_callbacks_
.size());
271 TEST_F(DOMStorageCachedAreaTest
, MutationsAreIgnoredUntilLoadCompletion
) {
272 const int kConnectionId
= 7;
273 scoped_refptr
<DOMStorageCachedArea
> cached_area
=
274 new DOMStorageCachedArea(kNamespaceId
, kOrigin
, mock_proxy_
.get());
275 EXPECT_TRUE(cached_area
->GetItem(kConnectionId
, kKey
).is_null());
276 EXPECT_TRUE(IsPrimed(cached_area
.get()));
277 EXPECT_TRUE(IsIgnoringAllMutations(cached_area
.get()));
279 // Before load completion, the mutation should be ignored.
280 cached_area
->ApplyMutation(base::NullableString16(kKey
, false),
281 base::NullableString16(kValue
, false));
282 EXPECT_TRUE(cached_area
->GetItem(kConnectionId
, kKey
).is_null());
284 // Call the load completion callback.
285 mock_proxy_
->CompleteOnePendingCallback(true);
286 EXPECT_FALSE(IsIgnoringAllMutations(cached_area
.get()));
288 // Verify that mutations are now applied.
289 cached_area
->ApplyMutation(base::NullableString16(kKey
, false),
290 base::NullableString16(kValue
, false));
291 EXPECT_EQ(kValue
, cached_area
->GetItem(kConnectionId
, kKey
).string());
294 TEST_F(DOMStorageCachedAreaTest
, MutationsAreIgnoredUntilClearCompletion
) {
295 const int kConnectionId
= 4;
296 scoped_refptr
<DOMStorageCachedArea
> cached_area
=
297 new DOMStorageCachedArea(kNamespaceId
, kOrigin
, mock_proxy_
.get());
298 cached_area
->Clear(kConnectionId
, kPageUrl
);
299 EXPECT_TRUE(IsIgnoringAllMutations(cached_area
.get()));
300 mock_proxy_
->CompleteOnePendingCallback(true);
301 EXPECT_FALSE(IsIgnoringAllMutations(cached_area
.get()));
303 // Verify that calling Clear twice works as expected, the first
304 // completion callback should have been cancelled.
305 ResetCacheOnly(cached_area
.get());
306 cached_area
->Clear(kConnectionId
, kPageUrl
);
307 EXPECT_TRUE(IsIgnoringAllMutations(cached_area
.get()));
308 cached_area
->Clear(kConnectionId
, kPageUrl
);
309 EXPECT_TRUE(IsIgnoringAllMutations(cached_area
.get()));
310 mock_proxy_
->CompleteOnePendingCallback(true);
311 EXPECT_TRUE(IsIgnoringAllMutations(cached_area
.get()));
312 mock_proxy_
->CompleteOnePendingCallback(true);
313 EXPECT_FALSE(IsIgnoringAllMutations(cached_area
.get()));
316 TEST_F(DOMStorageCachedAreaTest
, KeyMutationsAreIgnoredUntilCompletion
) {
317 const int kConnectionId
= 8;
318 scoped_refptr
<DOMStorageCachedArea
> cached_area
=
319 new DOMStorageCachedArea(kNamespaceId
, kOrigin
, mock_proxy_
.get());
322 EXPECT_TRUE(cached_area
->SetItem(kConnectionId
, kKey
, kValue
, kPageUrl
));
323 mock_proxy_
->CompleteOnePendingCallback(true); // load completion
324 EXPECT_FALSE(IsIgnoringAllMutations(cached_area
.get()));
325 EXPECT_TRUE(IsIgnoringKeyMutations(cached_area
.get(), kKey
));
326 cached_area
->ApplyMutation(base::NullableString16(kKey
, false),
327 base::NullableString16());
328 EXPECT_EQ(kValue
, cached_area
->GetItem(kConnectionId
, kKey
).string());
329 mock_proxy_
->CompleteOnePendingCallback(true); // set completion
330 EXPECT_FALSE(IsIgnoringKeyMutations(cached_area
.get(), kKey
));
333 cached_area
->RemoveItem(kConnectionId
, kKey
, kPageUrl
);
334 EXPECT_TRUE(IsIgnoringKeyMutations(cached_area
.get(), kKey
));
335 mock_proxy_
->CompleteOnePendingCallback(true); // remove completion
336 EXPECT_FALSE(IsIgnoringKeyMutations(cached_area
.get(), kKey
));
338 // Multiple mutations to the same key.
339 EXPECT_TRUE(cached_area
->SetItem(kConnectionId
, kKey
, kValue
, kPageUrl
));
340 cached_area
->RemoveItem(kConnectionId
, kKey
, kPageUrl
);
341 EXPECT_TRUE(IsIgnoringKeyMutations(cached_area
.get(), kKey
));
342 mock_proxy_
->CompleteOnePendingCallback(true); // set completion
343 EXPECT_TRUE(IsIgnoringKeyMutations(cached_area
.get(), kKey
));
344 mock_proxy_
->CompleteOnePendingCallback(true); // remove completion
345 EXPECT_FALSE(IsIgnoringKeyMutations(cached_area
.get(), kKey
));
347 // A failed set item operation should Reset the cache.
348 EXPECT_TRUE(cached_area
->SetItem(kConnectionId
, kKey
, kValue
, kPageUrl
));
349 EXPECT_TRUE(IsIgnoringKeyMutations(cached_area
.get(), kKey
));
350 mock_proxy_
->CompleteOnePendingCallback(false);
351 EXPECT_FALSE(IsPrimed(cached_area
.get()));
354 } // namespace content