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_database.h"
15#include "content/browser/dom_storage/dom_storage_database_adapter.h"
16#include "content/browser/dom_storage/dom_storage_task_runner.h"
17#include "content/browser/dom_storage/local_storage_database_adapter.h"
18#include "content/common/dom_storage/dom_storage_types.h"
19#include "testing/gtest/include/gtest/gtest.h"
20
21namespace content {
22
23
24class DOMStorageAreaTest : public testing::Test {
25 public:
26  DOMStorageAreaTest()
27    : kOrigin(GURL("http://dom_storage/")),
28      kKey(ASCIIToUTF16("key")),
29      kValue(ASCIIToUTF16("value")),
30      kKey2(ASCIIToUTF16("key2")),
31      kValue2(ASCIIToUTF16("value2")) {
32  }
33
34  const GURL kOrigin;
35  const base::string16 kKey;
36  const base::string16 kValue;
37  const base::string16 kKey2;
38  const base::string16 kValue2;
39
40  // Method used in the CommitTasks test case.
41  void InjectedCommitSequencingTask(DOMStorageArea* area) {
42    // At this point the OnCommitTimer has run.
43    // Verify that it put a commit in flight.
44    EXPECT_EQ(1, area->commit_batches_in_flight_);
45    EXPECT_FALSE(area->commit_batch_.get());
46    EXPECT_TRUE(area->HasUncommittedChanges());
47    // Make additional change and verify that a new commit batch
48    // is created for that change.
49    base::NullableString16 old_value;
50    EXPECT_TRUE(area->SetItem(kKey2, kValue2, &old_value));
51    EXPECT_TRUE(area->commit_batch_.get());
52    EXPECT_EQ(1, area->commit_batches_in_flight_);
53    EXPECT_TRUE(area->HasUncommittedChanges());
54  }
55
56  // Class used in the CommitChangesAtShutdown test case.
57  class VerifyChangesCommittedDatabase : public DOMStorageDatabase {
58   public:
59    VerifyChangesCommittedDatabase() {}
60    virtual ~VerifyChangesCommittedDatabase() {
61      const base::string16 kKey(ASCIIToUTF16("key"));
62      const base::string16 kValue(ASCIIToUTF16("value"));
63      DOMStorageValuesMap values;
64      ReadAllValues(&values);
65      EXPECT_EQ(1u, values.size());
66      EXPECT_EQ(kValue, values[kKey].string());
67    }
68  };
69
70 private:
71  base::MessageLoop message_loop_;
72};
73
74TEST_F(DOMStorageAreaTest, DOMStorageAreaBasics) {
75  scoped_refptr<DOMStorageArea> area(
76      new DOMStorageArea(1, std::string(), kOrigin, NULL, NULL));
77  base::string16 old_value;
78  base::NullableString16 old_nullable_value;
79  scoped_refptr<DOMStorageArea> copy;
80
81  // We don't focus on the underlying DOMStorageMap functionality
82  // since that's covered by seperate unit tests.
83  EXPECT_EQ(kOrigin, area->origin());
84  EXPECT_EQ(1, area->namespace_id());
85  EXPECT_EQ(0u, area->Length());
86  EXPECT_TRUE(area->SetItem(kKey, kValue, &old_nullable_value));
87  EXPECT_TRUE(area->SetItem(kKey2, kValue2, &old_nullable_value));
88  EXPECT_FALSE(area->HasUncommittedChanges());
89
90  // Verify that a copy shares the same map.
91  copy = area->ShallowCopy(2, std::string());
92  EXPECT_EQ(kOrigin, copy->origin());
93  EXPECT_EQ(2, copy->namespace_id());
94  EXPECT_EQ(area->Length(), copy->Length());
95  EXPECT_EQ(area->GetItem(kKey).string(), copy->GetItem(kKey).string());
96  EXPECT_EQ(area->Key(0).string(), copy->Key(0).string());
97  EXPECT_EQ(copy->map_.get(), area->map_.get());
98
99  // But will deep copy-on-write as needed.
100  EXPECT_TRUE(area->RemoveItem(kKey, &old_value));
101  EXPECT_NE(copy->map_.get(), area->map_.get());
102  copy = area->ShallowCopy(2, std::string());
103  EXPECT_EQ(copy->map_.get(), area->map_.get());
104  EXPECT_TRUE(area->SetItem(kKey, kValue, &old_nullable_value));
105  EXPECT_NE(copy->map_.get(), area->map_.get());
106  copy = area->ShallowCopy(2, std::string());
107  EXPECT_EQ(copy->map_.get(), area->map_.get());
108  EXPECT_NE(0u, area->Length());
109  EXPECT_TRUE(area->Clear());
110  EXPECT_EQ(0u, area->Length());
111  EXPECT_NE(copy->map_.get(), area->map_.get());
112
113  // Verify that once Shutdown(), behaves that way.
114  area->Shutdown();
115  EXPECT_TRUE(area->is_shutdown_);
116  EXPECT_FALSE(area->map_.get());
117  EXPECT_EQ(0u, area->Length());
118  EXPECT_TRUE(area->Key(0).is_null());
119  EXPECT_TRUE(area->GetItem(kKey).is_null());
120  EXPECT_FALSE(area->SetItem(kKey, kValue, &old_nullable_value));
121  EXPECT_FALSE(area->RemoveItem(kKey, &old_value));
122  EXPECT_FALSE(area->Clear());
123}
124
125TEST_F(DOMStorageAreaTest, BackingDatabaseOpened) {
126  const int64 kSessionStorageNamespaceId = kLocalStorageNamespaceId + 1;
127  base::ScopedTempDir temp_dir;
128  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
129  const base::FilePath kExpectedOriginFilePath = temp_dir.path().Append(
130      DOMStorageArea::DatabaseFileNameFromOrigin(kOrigin));
131
132  // No directory, backing should be null.
133  {
134    scoped_refptr<DOMStorageArea> area(
135        new DOMStorageArea(kOrigin, base::FilePath(), NULL));
136    EXPECT_EQ(NULL, area->backing_.get());
137    EXPECT_TRUE(area->is_initial_import_done_);
138    EXPECT_FALSE(base::PathExists(kExpectedOriginFilePath));
139  }
140
141  // Valid directory and origin but no session storage backing. Backing should
142  // be null.
143  {
144    scoped_refptr<DOMStorageArea> area(
145        new DOMStorageArea(kSessionStorageNamespaceId, std::string(), kOrigin,
146                           NULL, NULL));
147    EXPECT_EQ(NULL, area->backing_.get());
148    EXPECT_TRUE(area->is_initial_import_done_);
149
150    base::NullableString16 old_value;
151    EXPECT_TRUE(area->SetItem(kKey, kValue, &old_value));
152    ASSERT_TRUE(old_value.is_null());
153
154    // Check that saving a value has still left us without a backing database.
155    EXPECT_EQ(NULL, area->backing_.get());
156    EXPECT_FALSE(base::PathExists(kExpectedOriginFilePath));
157  }
158
159  // This should set up a DOMStorageArea that is correctly backed to disk.
160  {
161    scoped_refptr<DOMStorageArea> area(new DOMStorageArea(
162        kOrigin,
163        temp_dir.path(),
164        new MockDOMStorageTaskRunner(base::MessageLoopProxy::current().get())));
165
166    EXPECT_TRUE(area->backing_.get());
167    DOMStorageDatabase* database = static_cast<LocalStorageDatabaseAdapter*>(
168        area->backing_.get())->db_.get();
169    EXPECT_FALSE(database->IsOpen());
170    EXPECT_FALSE(area->is_initial_import_done_);
171
172    // Inject an in-memory db to speed up the test.
173    // We will verify that something is written into the database but not
174    // that a file is written to disk - DOMStorageDatabase unit tests cover
175    // that.
176    area->backing_.reset(new LocalStorageDatabaseAdapter());
177
178    // Need to write something to ensure that the database is created.
179    base::NullableString16 old_value;
180    EXPECT_TRUE(area->SetItem(kKey, kValue, &old_value));
181    ASSERT_TRUE(old_value.is_null());
182    EXPECT_TRUE(area->is_initial_import_done_);
183    EXPECT_TRUE(area->commit_batch_.get());
184    EXPECT_EQ(0, area->commit_batches_in_flight_);
185
186    base::MessageLoop::current()->RunUntilIdle();
187
188    EXPECT_FALSE(area->commit_batch_.get());
189    EXPECT_EQ(0, area->commit_batches_in_flight_);
190    database = static_cast<LocalStorageDatabaseAdapter*>(
191        area->backing_.get())->db_.get();
192    EXPECT_TRUE(database->IsOpen());
193    EXPECT_EQ(1u, area->Length());
194    EXPECT_EQ(kValue, area->GetItem(kKey).string());
195
196    // Verify the content made it to the in memory database.
197    DOMStorageValuesMap values;
198    area->backing_->ReadAllValues(&values);
199    EXPECT_EQ(1u, values.size());
200    EXPECT_EQ(kValue, values[kKey].string());
201  }
202}
203
204TEST_F(DOMStorageAreaTest, CommitTasks) {
205  base::ScopedTempDir temp_dir;
206  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
207
208  scoped_refptr<DOMStorageArea> area(new DOMStorageArea(
209      kOrigin,
210      temp_dir.path(),
211      new MockDOMStorageTaskRunner(base::MessageLoopProxy::current().get())));
212  // Inject an in-memory db to speed up the test.
213  area->backing_.reset(new LocalStorageDatabaseAdapter());
214
215  // Unrelated to commits, but while we're here, see that querying Length()
216  // causes the backing database to be opened and presumably read from.
217  EXPECT_FALSE(area->is_initial_import_done_);
218  EXPECT_EQ(0u, area->Length());
219  EXPECT_TRUE(area->is_initial_import_done_);
220
221  DOMStorageValuesMap values;
222  base::NullableString16 old_value;
223
224  // See that changes are batched up.
225  EXPECT_FALSE(area->commit_batch_.get());
226  EXPECT_TRUE(area->SetItem(kKey, kValue, &old_value));
227  EXPECT_TRUE(area->HasUncommittedChanges());
228  EXPECT_TRUE(area->commit_batch_.get());
229  EXPECT_FALSE(area->commit_batch_->clear_all_first);
230  EXPECT_EQ(1u, area->commit_batch_->changed_values.size());
231  EXPECT_TRUE(area->SetItem(kKey2, kValue2, &old_value));
232  EXPECT_TRUE(area->commit_batch_.get());
233  EXPECT_FALSE(area->commit_batch_->clear_all_first);
234  EXPECT_EQ(2u, area->commit_batch_->changed_values.size());
235  base::MessageLoop::current()->RunUntilIdle();
236  EXPECT_FALSE(area->HasUncommittedChanges());
237  EXPECT_FALSE(area->commit_batch_.get());
238  EXPECT_EQ(0, area->commit_batches_in_flight_);
239  // Verify the changes made it to the database.
240  values.clear();
241  area->backing_->ReadAllValues(&values);
242  EXPECT_EQ(2u, values.size());
243  EXPECT_EQ(kValue, values[kKey].string());
244  EXPECT_EQ(kValue2, values[kKey2].string());
245
246  // See that clear is handled properly.
247  EXPECT_TRUE(area->Clear());
248  EXPECT_TRUE(area->commit_batch_.get());
249  EXPECT_TRUE(area->commit_batch_->clear_all_first);
250  EXPECT_TRUE(area->commit_batch_->changed_values.empty());
251  base::MessageLoop::current()->RunUntilIdle();
252  EXPECT_FALSE(area->commit_batch_.get());
253  EXPECT_EQ(0, area->commit_batches_in_flight_);
254  // Verify the changes made it to the database.
255  values.clear();
256  area->backing_->ReadAllValues(&values);
257  EXPECT_TRUE(values.empty());
258
259  // See that if changes accrue while a commit is "in flight"
260  // those will also get committed.
261  EXPECT_TRUE(area->SetItem(kKey, kValue, &old_value));
262  EXPECT_TRUE(area->HasUncommittedChanges());
263  // At this point the OnCommitTimer task has been posted. We inject
264  // another task in the queue that will execute after the timer task,
265  // but before the CommitChanges task. From within our injected task,
266  // we'll make an additional SetItem() call.
267  base::MessageLoop::current()->PostTask(
268      FROM_HERE,
269      base::Bind(&DOMStorageAreaTest::InjectedCommitSequencingTask,
270                 base::Unretained(this),
271                 area));
272  base::MessageLoop::current()->RunUntilIdle();
273  EXPECT_TRUE(area->HasOneRef());
274  EXPECT_FALSE(area->HasUncommittedChanges());
275  // Verify the changes made it to the database.
276  values.clear();
277  area->backing_->ReadAllValues(&values);
278  EXPECT_EQ(2u, values.size());
279  EXPECT_EQ(kValue, values[kKey].string());
280  EXPECT_EQ(kValue2, values[kKey2].string());
281}
282
283TEST_F(DOMStorageAreaTest, CommitChangesAtShutdown) {
284  base::ScopedTempDir temp_dir;
285  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
286  scoped_refptr<DOMStorageArea> area(new DOMStorageArea(
287      kOrigin,
288      temp_dir.path(),
289      new MockDOMStorageTaskRunner(base::MessageLoopProxy::current().get())));
290
291  // Inject an in-memory db to speed up the test and also to verify
292  // the final changes are commited in it's dtor.
293  static_cast<LocalStorageDatabaseAdapter*>(area->backing_.get())->db_.reset(
294      new VerifyChangesCommittedDatabase());
295
296  DOMStorageValuesMap values;
297  base::NullableString16 old_value;
298  EXPECT_TRUE(area->SetItem(kKey, kValue, &old_value));
299  EXPECT_TRUE(area->HasUncommittedChanges());
300  area->backing_->ReadAllValues(&values);
301  EXPECT_TRUE(values.empty());  // not committed yet
302  area->Shutdown();
303  base::MessageLoop::current()->RunUntilIdle();
304  EXPECT_TRUE(area->HasOneRef());
305  EXPECT_FALSE(area->backing_.get());
306  // The VerifyChangesCommittedDatabase destructor verifies values
307  // were committed.
308}
309
310TEST_F(DOMStorageAreaTest, DeleteOrigin) {
311  base::ScopedTempDir temp_dir;
312  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
313  scoped_refptr<DOMStorageArea> area(new DOMStorageArea(
314      kOrigin,
315      temp_dir.path(),
316      new MockDOMStorageTaskRunner(base::MessageLoopProxy::current().get())));
317
318  // This test puts files on disk.
319  base::FilePath db_file_path = static_cast<LocalStorageDatabaseAdapter*>(
320      area->backing_.get())->db_->file_path();
321  base::FilePath db_journal_file_path =
322      DOMStorageDatabase::GetJournalFilePath(db_file_path);
323
324  // Nothing bad should happen when invoked w/o any files on disk.
325  area->DeleteOrigin();
326  EXPECT_FALSE(base::PathExists(db_file_path));
327
328  // Commit something in the database and then delete.
329  base::NullableString16 old_value;
330  area->SetItem(kKey, kValue, &old_value);
331  base::MessageLoop::current()->RunUntilIdle();
332  EXPECT_TRUE(base::PathExists(db_file_path));
333  area->DeleteOrigin();
334  EXPECT_EQ(0u, area->Length());
335  EXPECT_FALSE(base::PathExists(db_file_path));
336  EXPECT_FALSE(base::PathExists(db_journal_file_path));
337
338  // Put some uncommitted changes to a non-existing database in
339  // and then delete. No file ever gets created in this case.
340  area->SetItem(kKey, kValue, &old_value);
341  EXPECT_TRUE(area->HasUncommittedChanges());
342  EXPECT_EQ(1u, area->Length());
343  area->DeleteOrigin();
344  EXPECT_TRUE(area->HasUncommittedChanges());
345  EXPECT_EQ(0u, area->Length());
346  base::MessageLoop::current()->RunUntilIdle();
347  EXPECT_FALSE(area->HasUncommittedChanges());
348  EXPECT_FALSE(base::PathExists(db_file_path));
349
350  // Put some uncommitted changes to a an existing database in
351  // and then delete.
352  area->SetItem(kKey, kValue, &old_value);
353  base::MessageLoop::current()->RunUntilIdle();
354  EXPECT_TRUE(base::PathExists(db_file_path));
355  area->SetItem(kKey2, kValue2, &old_value);
356  EXPECT_TRUE(area->HasUncommittedChanges());
357  EXPECT_EQ(2u, area->Length());
358  area->DeleteOrigin();
359  EXPECT_TRUE(area->HasUncommittedChanges());
360  EXPECT_EQ(0u, area->Length());
361  base::MessageLoop::current()->RunUntilIdle();
362  EXPECT_FALSE(area->HasUncommittedChanges());
363  // Since the area had uncommitted changes at the time delete
364  // was called, the file will linger until the shutdown time.
365  EXPECT_TRUE(base::PathExists(db_file_path));
366  area->Shutdown();
367  base::MessageLoop::current()->RunUntilIdle();
368  EXPECT_FALSE(base::PathExists(db_file_path));
369}
370
371TEST_F(DOMStorageAreaTest, PurgeMemory) {
372  base::ScopedTempDir temp_dir;
373  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
374  scoped_refptr<DOMStorageArea> area(new DOMStorageArea(
375      kOrigin,
376      temp_dir.path(),
377      new MockDOMStorageTaskRunner(base::MessageLoopProxy::current().get())));
378
379  // Inject an in-memory db to speed up the test.
380  area->backing_.reset(new LocalStorageDatabaseAdapter());
381
382  // Unowned ptrs we use to verify that 'purge' has happened.
383  DOMStorageDatabase* original_backing =
384      static_cast<LocalStorageDatabaseAdapter*>(
385          area->backing_.get())->db_.get();
386  DOMStorageMap* original_map = area->map_.get();
387
388  // Should do no harm when called on a newly constructed object.
389  EXPECT_FALSE(area->is_initial_import_done_);
390  area->PurgeMemory();
391  EXPECT_FALSE(area->is_initial_import_done_);
392  DOMStorageDatabase* new_backing = static_cast<LocalStorageDatabaseAdapter*>(
393      area->backing_.get())->db_.get();
394  EXPECT_EQ(original_backing, new_backing);
395  EXPECT_EQ(original_map, area->map_.get());
396
397  // Should not do anything when commits are pending.
398  base::NullableString16 old_value;
399  area->SetItem(kKey, kValue, &old_value);
400  EXPECT_TRUE(area->is_initial_import_done_);
401  EXPECT_TRUE(area->HasUncommittedChanges());
402  area->PurgeMemory();
403  EXPECT_TRUE(area->is_initial_import_done_);
404  EXPECT_TRUE(area->HasUncommittedChanges());
405  new_backing = static_cast<LocalStorageDatabaseAdapter*>(
406      area->backing_.get())->db_.get();
407  EXPECT_EQ(original_backing, new_backing);
408  EXPECT_EQ(original_map, area->map_.get());
409
410  // Commit the changes from above,
411  base::MessageLoop::current()->RunUntilIdle();
412  EXPECT_FALSE(area->HasUncommittedChanges());
413  new_backing = static_cast<LocalStorageDatabaseAdapter*>(
414      area->backing_.get())->db_.get();
415  EXPECT_EQ(original_backing, new_backing);
416  EXPECT_EQ(original_map, area->map_.get());
417
418  // Should drop caches and reset database connections
419  // when invoked on an area that's loaded up primed.
420  area->PurgeMemory();
421  EXPECT_FALSE(area->is_initial_import_done_);
422  new_backing = static_cast<LocalStorageDatabaseAdapter*>(
423      area->backing_.get())->db_.get();
424  EXPECT_NE(original_backing, new_backing);
425  EXPECT_NE(original_map, area->map_.get());
426}
427
428TEST_F(DOMStorageAreaTest, DatabaseFileNames) {
429  struct {
430    const char* origin;
431    const char* file_name;
432    const char* journal_file_name;
433  } kCases[] = {
434    { "https://www.google.com/",
435      "https_www.google.com_0.localstorage",
436      "https_www.google.com_0.localstorage-journal" },
437    { "http://www.google.com:8080/",
438      "http_www.google.com_8080.localstorage",
439      "http_www.google.com_8080.localstorage-journal" },
440    { "file:///",
441      "file__0.localstorage",
442      "file__0.localstorage-journal" },
443  };
444
445  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kCases); ++i) {
446    GURL origin = GURL(kCases[i].origin).GetOrigin();
447    base::FilePath file_name =
448        base::FilePath().AppendASCII(kCases[i].file_name);
449    base::FilePath journal_file_name =
450        base::FilePath().AppendASCII(kCases[i].journal_file_name);
451
452    EXPECT_EQ(file_name,
453              DOMStorageArea::DatabaseFileNameFromOrigin(origin));
454    EXPECT_EQ(origin,
455              DOMStorageArea::OriginFromDatabaseFileName(file_name));
456    EXPECT_EQ(journal_file_name,
457              DOMStorageDatabase::GetJournalFilePath(file_name));
458  }
459
460  // Also test some DOMStorageDatabase::GetJournalFilePath cases here.
461  base::FilePath parent = base::FilePath().AppendASCII("a").AppendASCII("b");
462  EXPECT_EQ(
463      parent.AppendASCII("file-journal"),
464      DOMStorageDatabase::GetJournalFilePath(parent.AppendASCII("file")));
465  EXPECT_EQ(
466      base::FilePath().AppendASCII("-journal"),
467      DOMStorageDatabase::GetJournalFilePath(base::FilePath()));
468  EXPECT_EQ(
469      base::FilePath().AppendASCII(".extensiononly-journal"),
470      DOMStorageDatabase::GetJournalFilePath(
471          base::FilePath().AppendASCII(".extensiononly")));
472}
473
474}  // namespace content
475