Commit 0f99a7ab authored by mek's avatar mek Committed by Commit bot

Store a version number in the localstorage database.

Currently an invalid version just results in not using a backing store,
but in the future that will result in upgrading and/or recreating the
database.

BUG=586194

Review-Url: https://codereview.chromium.org/2607493003
Cr-Commit-Position: refs/heads/master@{#440857}
parent cc10ca6c
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
#include "content/browser/dom_storage/local_storage_context_mojo.h" #include "content/browser/dom_storage/local_storage_context_mojo.h"
#include "base/memory/ptr_util.h" #include "base/memory/ptr_util.h"
#include "base/strings/string_number_conversions.h"
#include "components/leveldb/public/cpp/util.h"
#include "content/browser/leveldb_wrapper_impl.h" #include "content/browser/leveldb_wrapper_impl.h"
#include "content/common/dom_storage/dom_storage_types.h" #include "content/common/dom_storage/dom_storage_types.h"
#include "services/file/public/interfaces/constants.mojom.h" #include "services/file/public/interfaces/constants.mojom.h"
...@@ -13,8 +15,24 @@ ...@@ -13,8 +15,24 @@
namespace content { namespace content {
// LevelDB database schema
// =======================
//
// Version 1 (in sorted order):
// key: "VERSION"
// value: "1"
//
// key: "_" + <url::Origin> 'origin'> + '\x00' + <script controlled key>
// value: <script controlled value>
namespace { namespace {
const char kVersionKey[] = "VERSION";
const char kOriginSeparator = '\x00'; const char kOriginSeparator = '\x00';
const char kDataPrefix[] = "_";
const int64_t kMinSchemaVersion = 1;
const int64_t kCurrentSchemaVersion = 1;
void NoOpResult(leveldb::mojom::DatabaseError status) {}
} }
LocalStorageContextMojo::LocalStorageContextMojo( LocalStorageContextMojo::LocalStorageContextMojo(
...@@ -72,6 +90,8 @@ void LocalStorageContextMojo::OpenLocalStorage( ...@@ -72,6 +90,8 @@ void LocalStorageContextMojo::OpenLocalStorage(
void LocalStorageContextMojo::SetDatabaseForTesting( void LocalStorageContextMojo::SetDatabaseForTesting(
leveldb::mojom::LevelDBDatabasePtr database) { leveldb::mojom::LevelDBDatabasePtr database) {
DCHECK_EQ(connection_state_, NO_CONNECTION);
connection_state_ = CONNECTION_IN_PROGRESS;
database_ = std::move(database); database_ = std::move(database);
OnDatabaseOpened(leveldb::mojom::DatabaseError::OK); OnDatabaseOpened(leveldb::mojom::DatabaseError::OK);
} }
...@@ -125,7 +145,46 @@ void LocalStorageContextMojo::OnDatabaseOpened( ...@@ -125,7 +145,46 @@ void LocalStorageContextMojo::OnDatabaseOpened(
directory_.reset(); directory_.reset();
file_system_.reset(); file_system_.reset();
// |leveldb_| should be known to either be valid or invalid by now. Run our // Verify DB schema version.
if (database_) {
database_->Get(leveldb::StdStringToUint8Vector(kVersionKey),
base::Bind(&LocalStorageContextMojo::OnGotDatabaseVersion,
weak_ptr_factory_.GetWeakPtr()));
return;
}
OnGotDatabaseVersion(leveldb::mojom::DatabaseError::IO_ERROR,
std::vector<uint8_t>());
}
void LocalStorageContextMojo::OnGotDatabaseVersion(
leveldb::mojom::DatabaseError status,
const std::vector<uint8_t>& value) {
if (status == leveldb::mojom::DatabaseError::NOT_FOUND) {
// New database, write current version and continue.
// TODO(mek): Delay writing version until first actual data gets committed.
database_->Put(leveldb::StdStringToUint8Vector(kVersionKey),
leveldb::StdStringToUint8Vector(
base::Int64ToString(kCurrentSchemaVersion)),
base::Bind(&NoOpResult));
// new database
} else if (status == leveldb::mojom::DatabaseError::OK) {
// Existing database, check if version number matches current schema
// version.
int64_t db_version;
if (!base::StringToInt64(leveldb::Uint8VectorToStdString(value),
&db_version) ||
db_version < kMinSchemaVersion || db_version > kCurrentSchemaVersion) {
// TODO(mek): delete and recreate database, rather than failing outright.
database_ = nullptr;
}
} else {
// Other read error. Possibly database corruption.
// TODO(mek): delete and recreate database, rather than failing outright.
database_ = nullptr;
}
// |database_| should be known to either be valid or invalid by now. Run our
// delayed bindings. // delayed bindings.
connection_state_ = CONNECTION_FINISHED; connection_state_ = CONNECTION_FINISHED;
for (size_t i = 0; i < on_database_opened_callbacks_.size(); ++i) for (size_t i = 0; i < on_database_opened_callbacks_.size(); ++i)
...@@ -150,7 +209,7 @@ void LocalStorageContextMojo::BindLocalStorage( ...@@ -150,7 +209,7 @@ void LocalStorageContextMojo::BindLocalStorage(
auto found = level_db_wrappers_.find(origin); auto found = level_db_wrappers_.find(origin);
if (found == level_db_wrappers_.end()) { if (found == level_db_wrappers_.end()) {
level_db_wrappers_[origin] = base::MakeUnique<LevelDBWrapperImpl>( level_db_wrappers_[origin] = base::MakeUnique<LevelDBWrapperImpl>(
database_.get(), origin.Serialize() + kOriginSeparator, database_.get(), kDataPrefix + origin.Serialize() + kOriginSeparator,
kPerStorageAreaQuota + kPerStorageAreaOverQuotaAllowance, kPerStorageAreaQuota + kPerStorageAreaOverQuotaAllowance,
base::TimeDelta::FromSeconds(kCommitDefaultDelaySecs), kMaxBytesPerHour, base::TimeDelta::FromSeconds(kCommitDefaultDelaySecs), kMaxBytesPerHour,
kMaxCommitsPerHour, kMaxCommitsPerHour,
......
...@@ -43,6 +43,8 @@ class CONTENT_EXPORT LocalStorageContextMojo { ...@@ -43,6 +43,8 @@ class CONTENT_EXPORT LocalStorageContextMojo {
// Part of our asynchronous directory opening called from OpenLocalStorage(). // Part of our asynchronous directory opening called from OpenLocalStorage().
void OnDirectoryOpened(filesystem::mojom::FileError err); void OnDirectoryOpened(filesystem::mojom::FileError err);
void OnDatabaseOpened(leveldb::mojom::DatabaseError status); void OnDatabaseOpened(leveldb::mojom::DatabaseError status);
void OnGotDatabaseVersion(leveldb::mojom::DatabaseError status,
const std::vector<uint8_t>& value);
// The (possibly delayed) implementation of OpenLocalStorage(). Can be called // The (possibly delayed) implementation of OpenLocalStorage(). Can be called
// directly from that function, or through |on_database_open_callbacks_|. // directly from that function, or through |on_database_open_callbacks_|.
......
...@@ -20,29 +20,45 @@ namespace { ...@@ -20,29 +20,45 @@ namespace {
void NoOpSuccess(bool success) {} void NoOpSuccess(bool success) {}
void GetCallback(const base::Closure& callback,
bool* success_out,
std::vector<uint8_t>* value_out,
bool success,
const std::vector<uint8_t>& value) {
*success_out = success;
*value_out = value;
callback.Run();
}
} // namespace } // namespace
class LocalStorageContextMojoTest : public testing::Test { class LocalStorageContextMojoTest : public testing::Test {
public: public:
LocalStorageContextMojoTest() LocalStorageContextMojoTest() : db_(&mock_data_), db_binding_(&db_) {}
: db_(&mock_data_),
db_binding_(&db_), LocalStorageContextMojo* context() {
context_(nullptr, base::FilePath()) { if (!context_) {
context_.SetDatabaseForTesting(db_binding_.CreateInterfacePtrAndBind()); context_ =
base::MakeUnique<LocalStorageContextMojo>(nullptr, base::FilePath());
context_->SetDatabaseForTesting(db_binding_.CreateInterfacePtrAndBind());
}
return context_.get();
} }
LocalStorageContextMojo* context() { return &context_; }
const std::map<std::vector<uint8_t>, std::vector<uint8_t>>& mock_data() { const std::map<std::vector<uint8_t>, std::vector<uint8_t>>& mock_data() {
return mock_data_; return mock_data_;
} }
void set_mock_data(const std::string& key, const std::string& value) {
mock_data_[StdStringToUint8Vector(key)] = StdStringToUint8Vector(value);
}
private: private:
TestBrowserThreadBundle thread_bundle_; TestBrowserThreadBundle thread_bundle_;
std::map<std::vector<uint8_t>, std::vector<uint8_t>> mock_data_; std::map<std::vector<uint8_t>, std::vector<uint8_t>> mock_data_;
MockLevelDBDatabase db_; MockLevelDBDatabase db_;
mojo::Binding<leveldb::mojom::LevelDBDatabase> db_binding_; mojo::Binding<leveldb::mojom::LevelDBDatabase> db_binding_;
LocalStorageContextMojo context_; std::unique_ptr<LocalStorageContextMojo> context_;
}; };
TEST_F(LocalStorageContextMojoTest, Basic) { TEST_F(LocalStorageContextMojoTest, Basic) {
...@@ -56,8 +72,8 @@ TEST_F(LocalStorageContextMojoTest, Basic) { ...@@ -56,8 +72,8 @@ TEST_F(LocalStorageContextMojoTest, Basic) {
wrapper.reset(); wrapper.reset();
base::RunLoop().RunUntilIdle(); base::RunLoop().RunUntilIdle();
ASSERT_EQ(1u, mock_data().size()); ASSERT_EQ(2u, mock_data().size());
EXPECT_EQ(value, mock_data().begin()->second); EXPECT_EQ(value, mock_data().rbegin()->second);
} }
TEST_F(LocalStorageContextMojoTest, OriginsAreIndependent) { TEST_F(LocalStorageContextMojoTest, OriginsAreIndependent) {
...@@ -77,8 +93,45 @@ TEST_F(LocalStorageContextMojoTest, OriginsAreIndependent) { ...@@ -77,8 +93,45 @@ TEST_F(LocalStorageContextMojoTest, OriginsAreIndependent) {
wrapper.reset(); wrapper.reset();
base::RunLoop().RunUntilIdle(); base::RunLoop().RunUntilIdle();
ASSERT_EQ(2u, mock_data().size()); ASSERT_EQ(3u, mock_data().size());
EXPECT_EQ(value, mock_data().begin()->second); EXPECT_EQ(value, mock_data().rbegin()->second);
}
TEST_F(LocalStorageContextMojoTest, ValidVersion) {
set_mock_data("VERSION", "1");
set_mock_data(std::string("_http://foobar.com") + '\x00' + "key", "value");
mojom::LevelDBWrapperPtr wrapper;
context()->OpenLocalStorage(url::Origin(GURL("http://foobar.com")),
MakeRequest(&wrapper));
base::RunLoop run_loop;
bool success = false;
std::vector<uint8_t> result;
wrapper->Get(
StdStringToUint8Vector("key"),
base::Bind(&GetCallback, run_loop.QuitClosure(), &success, &result));
run_loop.Run();
EXPECT_TRUE(success);
EXPECT_EQ(StdStringToUint8Vector("value"), result);
}
TEST_F(LocalStorageContextMojoTest, InvalidVersion) {
set_mock_data("VERSION", "foobar");
set_mock_data(std::string("_http://foobar.com") + '\x00' + "key", "value");
mojom::LevelDBWrapperPtr wrapper;
context()->OpenLocalStorage(url::Origin(GURL("http://foobar.com")),
MakeRequest(&wrapper));
base::RunLoop run_loop;
bool success = false;
std::vector<uint8_t> result;
wrapper->Get(
StdStringToUint8Vector("key"),
base::Bind(&GetCallback, run_loop.QuitClosure(), &success, &result));
run_loop.Run();
EXPECT_FALSE(success);
} }
} // namespace content } // namespace content
...@@ -243,6 +243,12 @@ void LevelDBWrapperImpl::LoadMap(const base::Closure& completion_callback) { ...@@ -243,6 +243,12 @@ void LevelDBWrapperImpl::LoadMap(const base::Closure& completion_callback) {
if (on_load_complete_tasks_.size() > 1) if (on_load_complete_tasks_.size() > 1)
return; return;
if (!database_) {
OnLoadComplete(leveldb::mojom::DatabaseError::IO_ERROR,
std::vector<leveldb::mojom::KeyValuePtr>());
return;
}
// TODO(michaeln): Import from sqlite localstorage db. // TODO(michaeln): Import from sqlite localstorage db.
database_->GetPrefixed(prefix_, database_->GetPrefixed(prefix_,
base::Bind(&LevelDBWrapperImpl::OnLoadComplete, base::Bind(&LevelDBWrapperImpl::OnLoadComplete,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment