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