dom_storage_cached_area_unittest.cc revision 4e180b6a0b4720a9b8e9e959a882386f690f08ff
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
14namespace content {
15
16namespace {
17// A mock implementation of the DOMStorageProxy interface.
18class 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
118class DOMStorageCachedAreaTest : public testing::Test {
119 public:
120  DOMStorageCachedAreaTest()
121    : kNamespaceId(10),
122      kOrigin("http://dom_storage/"),
123      kKey(ASCIIToUTF16("key")),
124      kValue(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
165TEST_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
190TEST_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
225TEST_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
280TEST_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
303TEST_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
325TEST_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