blob: ada36d72b2062c60d959582857dae7799a286a3c [file] [log] [blame]
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +01001// 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
14namespace content {
15
16namespace {
Ben Murdochbb1529c2013-08-08 10:24:53 +010017// A mock implementation of the DOMStorageProxy interface.
18class MockProxy : public DOMStorageProxy {
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +010019 public:
20 MockProxy() {
21 ResetObservations();
22 }
23
Ben Murdochbb1529c2013-08-08 10:24:53 +010024 // DOMStorageProxy interface for use by DOMStorageCachedArea.
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +010025
26 virtual void LoadArea(int connection_id,
Ben Murdochbb1529c2013-08-08 10:24:53 +010027 DOMStorageValuesMap* values,
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +010028 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
Ben Murdochbb1529c2013-08-08 10:24:53 +010094 DOMStorageValuesMap load_area_return_values_;
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +010095 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
Ben Murdochbb1529c2013-08-08 10:24:53 +0100111class DOMStorageCachedAreaTest : public testing::Test {
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100112 public:
Ben Murdochbb1529c2013-08-08 10:24:53 +0100113 DOMStorageCachedAreaTest()
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100114 : 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
Ben Murdochbb1529c2013-08-08 10:24:53 +0100131 bool IsPrimed(DOMStorageCachedArea* cached_area) {
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100132 return cached_area->map_.get();
133 }
134
Ben Murdochbb1529c2013-08-08 10:24:53 +0100135 bool IsIgnoringAllMutations(DOMStorageCachedArea* cached_area) {
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100136 return cached_area->ignore_all_mutations_;
137 }
138
Ben Murdochbb1529c2013-08-08 10:24:53 +0100139 bool IsIgnoringKeyMutations(DOMStorageCachedArea* cached_area,
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100140 const base::string16& key) {
141 return cached_area->should_ignore_key_mutation(key);
142 }
143
Ben Murdochbb1529c2013-08-08 10:24:53 +0100144 void ResetAll(DOMStorageCachedArea* cached_area) {
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100145 cached_area->Reset();
146 mock_proxy_->ResetObservations();
147 mock_proxy_->pending_callbacks_.clear();
148 }
149
Ben Murdochbb1529c2013-08-08 10:24:53 +0100150 void ResetCacheOnly(DOMStorageCachedArea* cached_area) {
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100151 cached_area->Reset();
152 }
153
154 protected:
155 scoped_refptr<MockProxy> mock_proxy_;
156};
157
Ben Murdochbb1529c2013-08-08 10:24:53 +0100158TEST_F(DOMStorageCachedAreaTest, Basics) {
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100159 EXPECT_TRUE(mock_proxy_->HasOneRef());
Ben Murdochbb1529c2013-08-08 10:24:53 +0100160 scoped_refptr<DOMStorageCachedArea> cached_area =
161 new DOMStorageCachedArea(kNamespaceId, kOrigin, mock_proxy_.get());
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100162 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
Ben Murdochbb1529c2013-08-08 10:24:53 +0100183TEST_F(DOMStorageCachedAreaTest, Getters) {
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100184 const int kConnectionId = 7;
Ben Murdochbb1529c2013-08-08 10:24:53 +0100185 scoped_refptr<DOMStorageCachedArea> cached_area =
186 new DOMStorageCachedArea(kNamespaceId, kOrigin, mock_proxy_.get());
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100187
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
Ben Murdochbb1529c2013-08-08 10:24:53 +0100218TEST_F(DOMStorageCachedAreaTest, Setters) {
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100219 const int kConnectionId = 7;
Ben Murdochbb1529c2013-08-08 10:24:53 +0100220 scoped_refptr<DOMStorageCachedArea> cached_area =
221 new DOMStorageCachedArea(kNamespaceId, kOrigin, mock_proxy_.get());
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100222
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
Ben Murdochbb1529c2013-08-08 10:24:53 +0100273TEST_F(DOMStorageCachedAreaTest, MutationsAreIgnoredUntilLoadCompletion) {
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100274 const int kConnectionId = 7;
Ben Murdochbb1529c2013-08-08 10:24:53 +0100275 scoped_refptr<DOMStorageCachedArea> cached_area =
276 new DOMStorageCachedArea(kNamespaceId, kOrigin, mock_proxy_.get());
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100277 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
Ben Murdochbb1529c2013-08-08 10:24:53 +0100296TEST_F(DOMStorageCachedAreaTest, MutationsAreIgnoredUntilClearCompletion) {
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100297 const int kConnectionId = 4;
Ben Murdochbb1529c2013-08-08 10:24:53 +0100298 scoped_refptr<DOMStorageCachedArea> cached_area =
299 new DOMStorageCachedArea(kNamespaceId, kOrigin, mock_proxy_.get());
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100300 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
Ben Murdochbb1529c2013-08-08 10:24:53 +0100318TEST_F(DOMStorageCachedAreaTest, KeyMutationsAreIgnoredUntilCompletion) {
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100319 const int kConnectionId = 8;
Ben Murdochbb1529c2013-08-08 10:24:53 +0100320 scoped_refptr<DOMStorageCachedArea> cached_area =
321 new DOMStorageCachedArea(kNamespaceId, kOrigin, mock_proxy_.get());
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100322
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
Ben Murdochbb1529c2013-08-08 10:24:53 +0100356} // namespace content