1// Copyright 2013 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 "base/bind.h"
6#include "base/file_util.h"
7#include "base/files/scoped_temp_dir.h"
8#include "base/message_loop/message_loop.h"
9#include "base/message_loop/message_loop_proxy.h"
10#include "base/strings/utf_string_conversions.h"
11#include "base/threading/sequenced_worker_pool.h"
12#include "base/time/time.h"
13#include "content/browser/dom_storage/dom_storage_area.h"
14#include "content/browser/dom_storage/dom_storage_context_impl.h"
15#include "content/browser/dom_storage/dom_storage_namespace.h"
16#include "content/browser/dom_storage/dom_storage_task_runner.h"
17#include "content/public/browser/local_storage_usage_info.h"
18#include "content/public/browser/session_storage_namespace.h"
19#include "content/public/browser/session_storage_usage_info.h"
20#include "testing/gtest/include/gtest/gtest.h"
21#include "webkit/browser/quota/mock_special_storage_policy.h"
22
23namespace content {
24
25class DOMStorageContextImplTest : public testing::Test {
26 public:
27  DOMStorageContextImplTest()
28    : kOrigin(GURL("http://dom_storage/")),
29      kKey(ASCIIToUTF16("key")),
30      kValue(ASCIIToUTF16("value")),
31      kDontIncludeFileInfo(false),
32      kDoIncludeFileInfo(true) {
33  }
34
35  const GURL kOrigin;
36  const base::string16 kKey;
37  const base::string16 kValue;
38  const bool kDontIncludeFileInfo;
39  const bool kDoIncludeFileInfo;
40
41  virtual void SetUp() {
42    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
43    storage_policy_ = new quota::MockSpecialStoragePolicy;
44    task_runner_ =
45        new MockDOMStorageTaskRunner(base::MessageLoopProxy::current().get());
46    context_ = new DOMStorageContextImpl(temp_dir_.path(),
47                                         base::FilePath(),
48                                         storage_policy_.get(),
49                                         task_runner_.get());
50  }
51
52  virtual void TearDown() {
53    base::MessageLoop::current()->RunUntilIdle();
54  }
55
56  void VerifySingleOriginRemains(const GURL& origin) {
57    // Use a new instance to examine the contexts of temp_dir_.
58    scoped_refptr<DOMStorageContextImpl> context =
59        new DOMStorageContextImpl(temp_dir_.path(), base::FilePath(),
60                                  NULL, NULL);
61    std::vector<LocalStorageUsageInfo> infos;
62    context->GetLocalStorageUsage(&infos, kDontIncludeFileInfo);
63    ASSERT_EQ(1u, infos.size());
64    EXPECT_EQ(origin, infos[0].origin);
65  }
66
67 protected:
68  base::MessageLoop message_loop_;
69  base::ScopedTempDir temp_dir_;
70  scoped_refptr<quota::MockSpecialStoragePolicy> storage_policy_;
71  scoped_refptr<MockDOMStorageTaskRunner> task_runner_;
72  scoped_refptr<DOMStorageContextImpl> context_;
73  DISALLOW_COPY_AND_ASSIGN(DOMStorageContextImplTest);
74};
75
76TEST_F(DOMStorageContextImplTest, Basics) {
77  // This test doesn't do much, checks that the constructor
78  // initializes members properly and that invoking methods
79  // on a newly created object w/o any data on disk do no harm.
80  EXPECT_EQ(temp_dir_.path(), context_->localstorage_directory());
81  EXPECT_EQ(base::FilePath(), context_->sessionstorage_directory());
82  EXPECT_EQ(storage_policy_.get(), context_->special_storage_policy_.get());
83  context_->PurgeMemory();
84  context_->DeleteLocalStorage(GURL("http://chromium.org/"));
85  const int kFirstSessionStorageNamespaceId = 1;
86  EXPECT_TRUE(context_->GetStorageNamespace(kLocalStorageNamespaceId));
87  EXPECT_FALSE(context_->GetStorageNamespace(kFirstSessionStorageNamespaceId));
88  EXPECT_EQ(kFirstSessionStorageNamespaceId, context_->AllocateSessionId());
89  std::vector<LocalStorageUsageInfo> infos;
90  context_->GetLocalStorageUsage(&infos, kDontIncludeFileInfo);
91  EXPECT_TRUE(infos.empty());
92  context_->Shutdown();
93}
94
95TEST_F(DOMStorageContextImplTest, UsageInfo) {
96  // Should be empty initially
97  std::vector<LocalStorageUsageInfo> infos;
98  context_->GetLocalStorageUsage(&infos, kDontIncludeFileInfo);
99  EXPECT_TRUE(infos.empty());
100  context_->GetLocalStorageUsage(&infos, kDoIncludeFileInfo);
101  EXPECT_TRUE(infos.empty());
102
103  // Put some data into local storage and shutdown the context
104  // to ensure data is written to disk.
105  base::NullableString16 old_value;
106  EXPECT_TRUE(context_->GetStorageNamespace(kLocalStorageNamespaceId)->
107      OpenStorageArea(kOrigin)->SetItem(kKey, kValue, &old_value));
108  context_->Shutdown();
109  context_ = NULL;
110  base::MessageLoop::current()->RunUntilIdle();
111
112  // Create a new context that points to the same directory, see that
113  // it knows about the origin that we stored data for.
114  context_ = new DOMStorageContextImpl(temp_dir_.path(), base::FilePath(),
115                                       NULL, NULL);
116  context_->GetLocalStorageUsage(&infos, kDontIncludeFileInfo);
117  EXPECT_EQ(1u, infos.size());
118  EXPECT_EQ(kOrigin, infos[0].origin);
119  EXPECT_EQ(0u, infos[0].data_size);
120  EXPECT_EQ(base::Time(), infos[0].last_modified);
121  infos.clear();
122  context_->GetLocalStorageUsage(&infos, kDoIncludeFileInfo);
123  EXPECT_EQ(1u, infos.size());
124  EXPECT_EQ(kOrigin, infos[0].origin);
125  EXPECT_NE(0u, infos[0].data_size);
126  EXPECT_NE(base::Time(), infos[0].last_modified);
127}
128
129TEST_F(DOMStorageContextImplTest, SessionOnly) {
130  const GURL kSessionOnlyOrigin("http://www.sessiononly.com/");
131  storage_policy_->AddSessionOnly(kSessionOnlyOrigin);
132
133  // Store data for a normal and a session-only origin and then
134  // invoke Shutdown() which should delete data for session-only
135  // origins.
136  base::NullableString16 old_value;
137  EXPECT_TRUE(context_->GetStorageNamespace(kLocalStorageNamespaceId)->
138      OpenStorageArea(kOrigin)->SetItem(kKey, kValue, &old_value));
139  EXPECT_TRUE(context_->GetStorageNamespace(kLocalStorageNamespaceId)->
140      OpenStorageArea(kSessionOnlyOrigin)->SetItem(kKey, kValue, &old_value));
141  context_->Shutdown();
142  context_ = NULL;
143  base::MessageLoop::current()->RunUntilIdle();
144
145  // Verify that the session-only origin data is gone.
146  VerifySingleOriginRemains(kOrigin);
147}
148
149TEST_F(DOMStorageContextImplTest, SetForceKeepSessionState) {
150  const GURL kSessionOnlyOrigin("http://www.sessiononly.com/");
151  storage_policy_->AddSessionOnly(kSessionOnlyOrigin);
152
153  // Store data for a session-only origin, setup to save session data, then
154  // shutdown.
155  base::NullableString16 old_value;
156  EXPECT_TRUE(context_->GetStorageNamespace(kLocalStorageNamespaceId)->
157      OpenStorageArea(kSessionOnlyOrigin)->SetItem(kKey, kValue, &old_value));
158  context_->SetForceKeepSessionState();  // Should override clear behavior.
159  context_->Shutdown();
160  context_ = NULL;
161  base::MessageLoop::current()->RunUntilIdle();
162
163  VerifySingleOriginRemains(kSessionOnlyOrigin);
164}
165
166TEST_F(DOMStorageContextImplTest, PersistentIds) {
167  const int kFirstSessionStorageNamespaceId = 1;
168  const std::string kPersistentId = "persistent";
169  context_->CreateSessionNamespace(kFirstSessionStorageNamespaceId,
170                                   kPersistentId);
171  DOMStorageNamespace* dom_namespace =
172      context_->GetStorageNamespace(kFirstSessionStorageNamespaceId);
173  ASSERT_TRUE(dom_namespace);
174  EXPECT_EQ(kPersistentId, dom_namespace->persistent_namespace_id());
175  // Verify that the areas inherit the persistent ID.
176  DOMStorageArea* area = dom_namespace->OpenStorageArea(kOrigin);
177  EXPECT_EQ(kPersistentId, area->persistent_namespace_id_);
178
179  // Verify that the persistent IDs are handled correctly when cloning.
180  const int kClonedSessionStorageNamespaceId = 2;
181  const std::string kClonedPersistentId = "cloned";
182  context_->CloneSessionNamespace(kFirstSessionStorageNamespaceId,
183                                  kClonedSessionStorageNamespaceId,
184                                  kClonedPersistentId);
185  DOMStorageNamespace* cloned_dom_namespace =
186      context_->GetStorageNamespace(kClonedSessionStorageNamespaceId);
187  ASSERT_TRUE(dom_namespace);
188  EXPECT_EQ(kClonedPersistentId,
189            cloned_dom_namespace->persistent_namespace_id());
190  // Verify that the areas inherit the persistent ID.
191  DOMStorageArea* cloned_area = cloned_dom_namespace->OpenStorageArea(kOrigin);
192  EXPECT_EQ(kClonedPersistentId, cloned_area->persistent_namespace_id_);
193}
194
195TEST_F(DOMStorageContextImplTest, DeleteSessionStorage) {
196  // Create a DOMStorageContextImpl which will save sessionStorage on disk.
197  context_ = new DOMStorageContextImpl(temp_dir_.path(),
198                                       temp_dir_.path(),
199                                       storage_policy_.get(),
200                                       task_runner_.get());
201  context_->SetSaveSessionStorageOnDisk();
202  ASSERT_EQ(temp_dir_.path(), context_->sessionstorage_directory());
203
204  // Write data.
205  const int kSessionStorageNamespaceId = 1;
206  const std::string kPersistentId = "persistent";
207  context_->CreateSessionNamespace(kSessionStorageNamespaceId,
208                                   kPersistentId);
209  DOMStorageNamespace* dom_namespace =
210      context_->GetStorageNamespace(kSessionStorageNamespaceId);
211  DOMStorageArea* area = dom_namespace->OpenStorageArea(kOrigin);
212  const base::string16 kKey(ASCIIToUTF16("foo"));
213  const base::string16 kValue(ASCIIToUTF16("bar"));
214  base::NullableString16 old_nullable_value;
215  area->SetItem(kKey, kValue, &old_nullable_value);
216  dom_namespace->CloseStorageArea(area);
217
218  // Destroy and recreate the DOMStorageContextImpl.
219  context_->Shutdown();
220  context_ = NULL;
221  base::MessageLoop::current()->RunUntilIdle();
222  context_ = new DOMStorageContextImpl(
223      temp_dir_.path(), temp_dir_.path(),
224      storage_policy_.get(), task_runner_.get());
225  context_->SetSaveSessionStorageOnDisk();
226
227  // Read the data back.
228  context_->CreateSessionNamespace(kSessionStorageNamespaceId,
229                                   kPersistentId);
230  dom_namespace = context_->GetStorageNamespace(kSessionStorageNamespaceId);
231  area = dom_namespace->OpenStorageArea(kOrigin);
232  base::NullableString16 read_value;
233  read_value = area->GetItem(kKey);
234  EXPECT_EQ(kValue, read_value.string());
235  dom_namespace->CloseStorageArea(area);
236
237  SessionStorageUsageInfo info;
238  info.origin = kOrigin;
239  info.persistent_namespace_id = kPersistentId;
240  context_->DeleteSessionStorage(info);
241
242  // Destroy and recreate again.
243  context_->Shutdown();
244  context_ = NULL;
245  base::MessageLoop::current()->RunUntilIdle();
246  context_ = new DOMStorageContextImpl(
247      temp_dir_.path(), temp_dir_.path(),
248      storage_policy_.get(), task_runner_.get());
249  context_->SetSaveSessionStorageOnDisk();
250
251  // Now there should be no data.
252  context_->CreateSessionNamespace(kSessionStorageNamespaceId,
253                                   kPersistentId);
254  dom_namespace = context_->GetStorageNamespace(kSessionStorageNamespaceId);
255  area = dom_namespace->OpenStorageArea(kOrigin);
256  read_value = area->GetItem(kKey);
257  EXPECT_TRUE(read_value.is_null());
258  dom_namespace->CloseStorageArea(area);
259  context_->Shutdown();
260  context_ = NULL;
261  base::MessageLoop::current()->RunUntilIdle();
262}
263
264TEST_F(DOMStorageContextImplTest, SessionStorageAlias) {
265  const int kFirstSessionStorageNamespaceId = 1;
266  const std::string kPersistentId = "persistent";
267  context_->CreateSessionNamespace(kFirstSessionStorageNamespaceId,
268                                   kPersistentId);
269  DOMStorageNamespace* dom_namespace1 =
270      context_->GetStorageNamespace(kFirstSessionStorageNamespaceId);
271  ASSERT_TRUE(dom_namespace1);
272  DOMStorageArea* area1 = dom_namespace1->OpenStorageArea(kOrigin);
273  base::NullableString16 old_value;
274  area1->SetItem(kKey, kValue, &old_value);
275  EXPECT_TRUE(old_value.is_null());
276  base::NullableString16 read_value = area1->GetItem(kKey);
277  EXPECT_TRUE(!read_value.is_null() && read_value.string() == kValue);
278
279  // Create an alias.
280  const int kAliasSessionStorageNamespaceId = 2;
281  context_->CreateAliasSessionNamespace(kFirstSessionStorageNamespaceId,
282                                        kAliasSessionStorageNamespaceId,
283                                        kPersistentId);
284  DOMStorageNamespace* dom_namespace2 =
285      context_->GetStorageNamespace(kAliasSessionStorageNamespaceId);
286  ASSERT_TRUE(dom_namespace2);
287  ASSERT_TRUE(dom_namespace2->alias_master_namespace() == dom_namespace1);
288
289  // Verify that read values are identical.
290  DOMStorageArea* area2 = dom_namespace2->OpenStorageArea(kOrigin);
291  read_value = area2->GetItem(kKey);
292  EXPECT_TRUE(!read_value.is_null() && read_value.string() == kValue);
293
294  // Verify that writes are reflected in both namespaces.
295  const base::string16 kValue2(ASCIIToUTF16("value2"));
296  area2->SetItem(kKey, kValue2, &old_value);
297  read_value = area1->GetItem(kKey);
298  EXPECT_TRUE(!read_value.is_null() && read_value.string() == kValue2);
299  dom_namespace1->CloseStorageArea(area1);
300  dom_namespace2->CloseStorageArea(area2);
301
302  // When creating an alias of an alias, ensure that the master relationship
303  // is collapsed.
304  const int kAlias2SessionStorageNamespaceId = 3;
305  context_->CreateAliasSessionNamespace(kAliasSessionStorageNamespaceId,
306                                        kAlias2SessionStorageNamespaceId,
307                                        kPersistentId);
308  DOMStorageNamespace* dom_namespace3 =
309      context_->GetStorageNamespace(kAlias2SessionStorageNamespaceId);
310  ASSERT_TRUE(dom_namespace3);
311  ASSERT_TRUE(dom_namespace3->alias_master_namespace() == dom_namespace1);
312}
313
314TEST_F(DOMStorageContextImplTest, SessionStorageMerge) {
315  // Create a target namespace that we will merge into.
316  const int kTargetSessionStorageNamespaceId = 1;
317  const std::string kTargetPersistentId = "persistent";
318  context_->CreateSessionNamespace(kTargetSessionStorageNamespaceId,
319                                   kTargetPersistentId);
320  DOMStorageNamespace* target_ns =
321      context_->GetStorageNamespace(kTargetSessionStorageNamespaceId);
322  ASSERT_TRUE(target_ns);
323  DOMStorageArea* target_ns_area = target_ns->OpenStorageArea(kOrigin);
324  base::NullableString16 old_value;
325  const base::string16 kKey2(ASCIIToUTF16("key2"));
326  const base::string16 kKey2Value(ASCIIToUTF16("key2value"));
327  target_ns_area->SetItem(kKey, kValue, &old_value);
328  target_ns_area->SetItem(kKey2, kKey2Value, &old_value);
329
330  // Create a source namespace & its alias.
331  const int kSourceSessionStorageNamespaceId = 2;
332  const int kAliasSessionStorageNamespaceId = 3;
333  const std::string kSourcePersistentId = "persistent_source";
334  context_->CreateSessionNamespace(kSourceSessionStorageNamespaceId,
335                                   kSourcePersistentId);
336  context_->CreateAliasSessionNamespace(kSourceSessionStorageNamespaceId,
337                                        kAliasSessionStorageNamespaceId,
338                                        kSourcePersistentId);
339  DOMStorageNamespace* alias_ns =
340      context_->GetStorageNamespace(kAliasSessionStorageNamespaceId);
341  ASSERT_TRUE(alias_ns);
342
343  // Create a transaction log that can't be merged.
344  const int kPid1 = 10;
345  ASSERT_FALSE(alias_ns->IsLoggingRenderer(kPid1));
346  alias_ns->AddTransactionLogProcessId(kPid1);
347  ASSERT_TRUE(alias_ns->IsLoggingRenderer(kPid1));
348  const base::string16 kValue2(ASCIIToUTF16("value2"));
349  DOMStorageNamespace::TransactionRecord txn;
350  txn.origin = kOrigin;
351  txn.key = kKey;
352  txn.value = base::NullableString16(kValue2, false);
353  txn.transaction_type = DOMStorageNamespace::TRANSACTION_READ;
354  alias_ns->AddTransaction(kPid1, txn);
355  ASSERT_TRUE(alias_ns->Merge(false, kPid1, target_ns, NULL) ==
356              SessionStorageNamespace::MERGE_RESULT_NOT_MERGEABLE);
357
358  // Create a transaction log that can be merged.
359  const int kPid2 = 20;
360  alias_ns->AddTransactionLogProcessId(kPid2);
361  txn.transaction_type = DOMStorageNamespace::TRANSACTION_WRITE;
362  alias_ns->AddTransaction(kPid2, txn);
363  ASSERT_TRUE(alias_ns->Merge(true, kPid2, target_ns, NULL) ==
364              SessionStorageNamespace::MERGE_RESULT_MERGEABLE);
365
366  // Verify that the merge was successful.
367  ASSERT_TRUE(alias_ns->alias_master_namespace() == target_ns);
368  base::NullableString16 read_value = target_ns_area->GetItem(kKey);
369  EXPECT_TRUE(!read_value.is_null() && read_value.string() == kValue2);
370  DOMStorageArea* alias_ns_area = alias_ns->OpenStorageArea(kOrigin);
371  read_value = alias_ns_area->GetItem(kKey2);
372  EXPECT_TRUE(!read_value.is_null() && read_value.string() == kKey2Value);
373}
374
375}  // namespace content
376