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 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;
37 virtual 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 virtual void LogGetItem(int connection_id
,
51 const base::string16
& key
,
52 const base::NullableString16
& value
) override
{
55 virtual void RemoveItem(int connection_id
,
56 const base::string16
& key
,
58 const CompletionCallback
& callback
) override
{
59 pending_callbacks_
.push_back(callback
);
60 observed_remove_item_
= true;
61 observed_connection_id_
= connection_id
;
63 observed_page_url_
= page_url
;
66 virtual void ClearArea(int connection_id
,
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
;
75 // Methods and members for use by test fixtures.
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();
88 void CompleteAllPendingCallbacks() {
89 while (!pending_callbacks_
.empty())
90 CompleteOnePendingCallback(true);
93 void CompleteOnePendingCallback(bool success
) {
94 ASSERT_TRUE(!pending_callbacks_
.empty());
95 pending_callbacks_
.front().Run(success
);
96 pending_callbacks_
.pop_front();
99 typedef std::list
<CompletionCallback
> CallbackList
;
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_
;
113 virtual ~MockProxy() {}
118 class DOMStorageCachedAreaTest
: public testing::Test
{
120 DOMStorageCachedAreaTest()
122 kOrigin("http://dom_storage/"),
123 kKey(base::ASCIIToUTF16("key")),
124 kValue(base::ASCIIToUTF16("value")),
125 kPageUrl("http://dom_storage/page") {
128 const int64 kNamespaceId
;
130 const base::string16 kKey
;
131 const base::string16 kValue
;
134 virtual void SetUp() {
135 mock_proxy_
= new MockProxy();
138 bool IsPrimed(DOMStorageCachedArea
* cached_area
) {
139 return cached_area
->map_
.get();
142 bool IsIgnoringAllMutations(DOMStorageCachedArea
* cached_area
) {
143 return cached_area
->ignore_all_mutations_
;
146 bool IsIgnoringKeyMutations(DOMStorageCachedArea
* cached_area
,
147 const base::string16
& key
) {
148 return cached_area
->should_ignore_key_mutation(key
);
151 void ResetAll(DOMStorageCachedArea
* cached_area
) {
152 cached_area
->Reset();
153 mock_proxy_
->ResetObservations();
154 mock_proxy_
->pending_callbacks_
.clear();
157 void ResetCacheOnly(DOMStorageCachedArea
* cached_area
) {
158 cached_area
->Reset();
162 scoped_refptr
<MockProxy
> mock_proxy_
;
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()));
176 ResetAll(cached_area
.get());
177 EXPECT_EQ(kNamespaceId
, cached_area
->namespace_id());
178 EXPECT_EQ(kOrigin
, cached_area
->origin());
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
));
190 TEST_F(DOMStorageCachedAreaTest
, Getters
) {
191 const int kConnectionId
= 7;
192 scoped_refptr
<DOMStorageCachedArea
> cached_area
=
193 new DOMStorageCachedArea(kNamespaceId
, kOrigin
, mock_proxy_
.get());
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()));
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());
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());
225 TEST_F(DOMStorageCachedAreaTest
, Setters
) {
226 const int kConnectionId
= 7;
227 scoped_refptr
<DOMStorageCachedArea
> cached_area
=
228 new DOMStorageCachedArea(kNamespaceId
, kOrigin
, mock_proxy_
.get());
230 // SetItem, we expect a call to load followed by a call to set item
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());
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());
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());
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());
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()));
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());
293 // Call the load completion callback.
294 mock_proxy_
->CompleteOnePendingCallback(true);
295 EXPECT_FALSE(IsIgnoringAllMutations(cached_area
.get()));
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());
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()));
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()));
325 TEST_F(DOMStorageCachedAreaTest
, KeyMutationsAreIgnoredUntilCompletion
) {
326 const int kConnectionId
= 8;
327 scoped_refptr
<DOMStorageCachedArea
> cached_area
=
328 new DOMStorageCachedArea(kNamespaceId
, kOrigin
, mock_proxy_
.get());
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
));
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
));
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
));
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()));
363 } // namespace content