blob: 60b3e98a7d8f6a2e1c1b48a13b23f89cf21672db [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/dom_storage/local_storage_context_mojo.h"
#include "base/files/file_enumerator.h"
#include "base/files/scoped_temp_dir.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "components/filesystem/public/interfaces/file_system.mojom.h"
#include "components/leveldb/public/cpp/util.h"
#include "content/browser/dom_storage/dom_storage_area.h"
#include "content/browser/dom_storage/dom_storage_context_impl.h"
#include "content/browser/dom_storage/dom_storage_namespace.h"
#include "content/browser/dom_storage/dom_storage_task_runner.h"
#include "content/common/dom_storage/dom_storage_types.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/local_storage_usage_info.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "content/test/mock_leveldb_database.h"
#include "mojo/public/cpp/bindings/associated_binding.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "mojo/public/cpp/bindings/binding_set.h"
#include "services/file/file_service.h"
#include "services/file/public/interfaces/constants.mojom.h"
#include "services/file/user_id_map.h"
#include "services/service_manager/public/cpp/service_context.h"
#include "services/service_manager/public/cpp/service_test.h"
#include "services/service_manager/public/interfaces/service_factory.mojom.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/leveldatabase/env_chromium.h"
#include "third_party/leveldatabase/src/include/leveldb/db.h"
using leveldb::StdStringToUint8Vector;
using leveldb::Uint8VectorToStdString;
namespace content {
namespace {
void NoOpSuccess(bool success) {}
void GetStorageUsageCallback(const base::Closure& callback,
std::vector<LocalStorageUsageInfo>* out_result,
std::vector<LocalStorageUsageInfo> result) {
*out_result = std::move(result);
callback.Run();
}
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();
}
void NoOpGet(bool success, const std::vector<uint8_t>& value) {}
class TestLevelDBObserver : public mojom::LevelDBObserver {
public:
struct Observation {
enum { kAdd, kChange, kDelete, kDeleteAll } type;
std::string key;
std::string old_value;
std::string new_value;
std::string source;
};
TestLevelDBObserver() : binding_(this) {}
mojom::LevelDBObserverAssociatedPtrInfo Bind() {
mojom::LevelDBObserverAssociatedPtrInfo ptr_info;
binding_.Bind(&ptr_info);
return ptr_info;
}
const std::vector<Observation>& observations() { return observations_; }
private:
void KeyAdded(const std::vector<uint8_t>& key,
const std::vector<uint8_t>& value,
const std::string& source) override {
observations_.push_back({Observation::kAdd, Uint8VectorToStdString(key), "",
Uint8VectorToStdString(value), source});
}
void KeyChanged(const std::vector<uint8_t>& key,
const std::vector<uint8_t>& new_value,
const std::vector<uint8_t>& old_value,
const std::string& source) override {
observations_.push_back({Observation::kChange, Uint8VectorToStdString(key),
Uint8VectorToStdString(old_value),
Uint8VectorToStdString(new_value), source});
}
void KeyDeleted(const std::vector<uint8_t>& key,
const std::vector<uint8_t>& old_value,
const std::string& source) override {
observations_.push_back({Observation::kDelete, Uint8VectorToStdString(key),
Uint8VectorToStdString(old_value), "", source});
}
void AllDeleted(const std::string& source) override {
observations_.push_back({Observation::kDeleteAll, "", "", "", source});
}
std::vector<Observation> observations_;
mojo::AssociatedBinding<mojom::LevelDBObserver> binding_;
};
} // namespace
class LocalStorageContextMojoTest : public testing::Test {
public:
LocalStorageContextMojoTest()
: db_(&mock_data_),
db_binding_(&db_),
task_runner_(new MockDOMStorageTaskRunner(
base::ThreadTaskRunnerHandle::Get().get())) {
EXPECT_TRUE(temp_path_.CreateUniqueTempDir());
dom_storage_context_ = new DOMStorageContextImpl(
temp_path_.GetPath(), base::FilePath(), nullptr, task_runner_);
}
~LocalStorageContextMojoTest() override {
if (dom_storage_context_)
dom_storage_context_->Shutdown();
}
LocalStorageContextMojo* context() {
if (!context_) {
context_ = base::MakeUnique<LocalStorageContextMojo>(
nullptr, task_runner_, temp_path_.GetPath(),
base::FilePath(FILE_PATH_LITERAL("leveldb")));
db_binding_.Bind(context_->DatabaseRequestForTesting());
}
return context_.get();
}
DOMStorageNamespace* local_storage_namespace() {
return dom_storage_context_->GetStorageNamespace(kLocalStorageNamespaceId);
}
void FlushAndPurgeDOMStorageMemory() {
dom_storage_context_->Flush();
base::RunLoop().RunUntilIdle();
dom_storage_context_->PurgeMemory(DOMStorageContextImpl::PURGE_AGGRESSIVE);
}
const std::map<std::vector<uint8_t>, std::vector<uint8_t>>& mock_data() {
return mock_data_;
}
void set_mock_data(const std::string& key, const std::string& value) {
mock_data_[StdStringToUint8Vector(key)] = StdStringToUint8Vector(value);
}
std::vector<LocalStorageUsageInfo> GetStorageUsageSync() {
base::RunLoop run_loop;
std::vector<LocalStorageUsageInfo> result;
context()->GetStorageUsage(base::BindOnce(&GetStorageUsageCallback,
run_loop.QuitClosure(), &result));
run_loop.Run();
return result;
}
private:
TestBrowserThreadBundle thread_bundle_;
base::ScopedTempDir temp_path_;
std::map<std::vector<uint8_t>, std::vector<uint8_t>> mock_data_;
MockLevelDBDatabase db_;
mojo::AssociatedBinding<leveldb::mojom::LevelDBDatabase> db_binding_;
scoped_refptr<MockDOMStorageTaskRunner> task_runner_;
scoped_refptr<DOMStorageContextImpl> dom_storage_context_;
std::unique_ptr<LocalStorageContextMojo> context_;
DISALLOW_COPY_AND_ASSIGN(LocalStorageContextMojoTest);
};
TEST_F(LocalStorageContextMojoTest, Basic) {
auto key = StdStringToUint8Vector("key");
auto value = StdStringToUint8Vector("value");
mojom::LevelDBWrapperPtr wrapper;
context()->OpenLocalStorage(url::Origin(GURL("http://foobar.com")),
MakeRequest(&wrapper));
wrapper->Put(key, value, "source", base::Bind(&NoOpSuccess));
wrapper.reset();
base::RunLoop().RunUntilIdle();
// Should have three rows of data, one for the version, one for the actual
// data and one for metadata.
ASSERT_EQ(3u, mock_data().size());
}
TEST_F(LocalStorageContextMojoTest, OriginsAreIndependent) {
url::Origin origin1(GURL("http://foobar.com:123"));
url::Origin origin2(GURL("http://foobar.com:1234"));
auto key1 = StdStringToUint8Vector("4key");
auto key2 = StdStringToUint8Vector("key");
auto value = StdStringToUint8Vector("value");
mojom::LevelDBWrapperPtr wrapper;
context()->OpenLocalStorage(origin1, MakeRequest(&wrapper));
wrapper->Put(key1, value, "source", base::Bind(&NoOpSuccess));
wrapper.reset();
context()->OpenLocalStorage(origin2, MakeRequest(&wrapper));
wrapper->Put(key2, value, "source", base::Bind(&NoOpSuccess));
wrapper.reset();
base::RunLoop().RunUntilIdle();
ASSERT_EQ(5u, mock_data().size());
}
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);
}
TEST_F(LocalStorageContextMojoTest, VersionOnlyWrittenOnCommit) {
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);
wrapper.reset();
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(mock_data().empty());
}
TEST_F(LocalStorageContextMojoTest, GetStorageUsage_NoData) {
std::vector<LocalStorageUsageInfo> info = GetStorageUsageSync();
EXPECT_EQ(0u, info.size());
}
TEST_F(LocalStorageContextMojoTest, GetStorageUsage_Data) {
url::Origin origin1(GURL("http://foobar.com"));
url::Origin origin2(GURL("http://example.com"));
auto key1 = StdStringToUint8Vector("key1");
auto key2 = StdStringToUint8Vector("key");
auto value = StdStringToUint8Vector("value");
base::Time before_write = base::Time::Now();
mojom::LevelDBWrapperPtr wrapper;
context()->OpenLocalStorage(origin1, MakeRequest(&wrapper));
wrapper->Put(key1, value, "source", base::Bind(&NoOpSuccess));
wrapper->Put(key2, value, "source", base::Bind(&NoOpSuccess));
wrapper.reset();
context()->OpenLocalStorage(origin2, MakeRequest(&wrapper));
wrapper->Put(key2, value, "source", base::Bind(&NoOpSuccess));
wrapper.reset();
// GetStorageUsage only include committed data, so nothing at this point.
std::vector<LocalStorageUsageInfo> info = GetStorageUsageSync();
EXPECT_EQ(0u, info.size());
// Make sure all data gets committed to disk.
base::RunLoop().RunUntilIdle();
base::Time after_write = base::Time::Now();
info = GetStorageUsageSync();
ASSERT_EQ(2u, info.size());
if (url::Origin(info[0].origin) == origin2)
std::swap(info[0], info[1]);
EXPECT_EQ(origin1, url::Origin(info[0].origin));
EXPECT_EQ(origin2, url::Origin(info[1].origin));
EXPECT_LE(before_write, info[0].last_modified);
EXPECT_LE(before_write, info[1].last_modified);
EXPECT_GE(after_write, info[0].last_modified);
EXPECT_GE(after_write, info[1].last_modified);
EXPECT_GT(info[0].data_size, info[1].data_size);
}
TEST_F(LocalStorageContextMojoTest, MetaDataClearedOnDelete) {
url::Origin origin1(GURL("http://foobar.com"));
url::Origin origin2(GURL("http://example.com"));
auto key = StdStringToUint8Vector("key");
auto value = StdStringToUint8Vector("value");
mojom::LevelDBWrapperPtr wrapper;
context()->OpenLocalStorage(origin1, MakeRequest(&wrapper));
wrapper->Put(key, value, "source", base::Bind(&NoOpSuccess));
wrapper.reset();
context()->OpenLocalStorage(origin2, MakeRequest(&wrapper));
wrapper->Put(key, value, "source", base::Bind(&NoOpSuccess));
wrapper.reset();
context()->OpenLocalStorage(origin1, MakeRequest(&wrapper));
wrapper->Delete(key, "source", base::Bind(&NoOpSuccess));
wrapper.reset();
// Make sure all data gets committed to disk.
base::RunLoop().RunUntilIdle();
// Data from origin2 should exist, including meta-data, but nothing should
// exist for origin1.
EXPECT_EQ(3u, mock_data().size());
for (const auto& it : mock_data()) {
if (Uint8VectorToStdString(it.first) == "VERSION")
continue;
EXPECT_EQ(std::string::npos,
Uint8VectorToStdString(it.first).find(origin1.Serialize()));
EXPECT_NE(std::string::npos,
Uint8VectorToStdString(it.first).find(origin2.Serialize()));
}
}
TEST_F(LocalStorageContextMojoTest, MetaDataClearedOnDeleteAll) {
url::Origin origin1(GURL("http://foobar.com"));
url::Origin origin2(GURL("http://example.com"));
auto key = StdStringToUint8Vector("key");
auto value = StdStringToUint8Vector("value");
mojom::LevelDBWrapperPtr wrapper;
context()->OpenLocalStorage(origin1, MakeRequest(&wrapper));
wrapper->Put(key, value, "source", base::Bind(&NoOpSuccess));
wrapper.reset();
context()->OpenLocalStorage(origin2, MakeRequest(&wrapper));
wrapper->Put(key, value, "source", base::Bind(&NoOpSuccess));
wrapper.reset();
context()->OpenLocalStorage(origin1, MakeRequest(&wrapper));
wrapper->DeleteAll("source", base::Bind(&NoOpSuccess));
wrapper.reset();
// Make sure all data gets committed to disk.
base::RunLoop().RunUntilIdle();
// Data from origin2 should exist, including meta-data, but nothing should
// exist for origin1.
EXPECT_EQ(3u, mock_data().size());
for (const auto& it : mock_data()) {
if (Uint8VectorToStdString(it.first) == "VERSION")
continue;
EXPECT_EQ(std::string::npos,
Uint8VectorToStdString(it.first).find(origin1.Serialize()));
EXPECT_NE(std::string::npos,
Uint8VectorToStdString(it.first).find(origin2.Serialize()));
}
}
TEST_F(LocalStorageContextMojoTest, DeleteStorage) {
set_mock_data("VERSION", "1");
set_mock_data(std::string("_http://foobar.com") + '\x00' + "key", "value");
context()->DeleteStorage(url::Origin(GURL("http://foobar.com")));
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1u, mock_data().size());
}
TEST_F(LocalStorageContextMojoTest, DeleteStorageWithoutConnection) {
url::Origin origin1(GURL("http://foobar.com"));
url::Origin origin2(GURL("http://example.com"));
auto key = StdStringToUint8Vector("key");
auto value = StdStringToUint8Vector("value");
mojom::LevelDBWrapperPtr wrapper;
context()->OpenLocalStorage(origin1, MakeRequest(&wrapper));
wrapper->Put(key, value, "source", base::Bind(&NoOpSuccess));
wrapper.reset();
context()->OpenLocalStorage(origin2, MakeRequest(&wrapper));
wrapper->Put(key, value, "source", base::Bind(&NoOpSuccess));
wrapper.reset();
// Make sure all data gets committed to disk.
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(mock_data().empty());
context()->DeleteStorage(origin1);
base::RunLoop().RunUntilIdle();
// Data from origin2 should exist, including meta-data, but nothing should
// exist for origin1.
EXPECT_EQ(3u, mock_data().size());
for (const auto& it : mock_data()) {
if (Uint8VectorToStdString(it.first) == "VERSION")
continue;
EXPECT_EQ(std::string::npos,
Uint8VectorToStdString(it.first).find(origin1.Serialize()));
EXPECT_NE(std::string::npos,
Uint8VectorToStdString(it.first).find(origin2.Serialize()));
}
}
TEST_F(LocalStorageContextMojoTest, DeleteStorageNotifiesWrapper) {
url::Origin origin1(GURL("http://foobar.com"));
url::Origin origin2(GURL("http://example.com"));
auto key = StdStringToUint8Vector("key");
auto value = StdStringToUint8Vector("value");
mojom::LevelDBWrapperPtr wrapper;
context()->OpenLocalStorage(origin1, MakeRequest(&wrapper));
wrapper->Put(key, value, "source", base::Bind(&NoOpSuccess));
wrapper.reset();
context()->OpenLocalStorage(origin2, MakeRequest(&wrapper));
wrapper->Put(key, value, "source", base::Bind(&NoOpSuccess));
wrapper.reset();
// Make sure all data gets committed to disk.
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(mock_data().empty());
TestLevelDBObserver observer;
context()->OpenLocalStorage(origin1, MakeRequest(&wrapper));
wrapper->AddObserver(observer.Bind());
base::RunLoop().RunUntilIdle();
context()->DeleteStorage(origin1);
base::RunLoop().RunUntilIdle();
ASSERT_EQ(1u, observer.observations().size());
EXPECT_EQ(TestLevelDBObserver::Observation::kDeleteAll,
observer.observations()[0].type);
// Data from origin2 should exist, including meta-data, but nothing should
// exist for origin1.
EXPECT_EQ(3u, mock_data().size());
for (const auto& it : mock_data()) {
if (Uint8VectorToStdString(it.first) == "VERSION")
continue;
EXPECT_EQ(std::string::npos,
Uint8VectorToStdString(it.first).find(origin1.Serialize()));
EXPECT_NE(std::string::npos,
Uint8VectorToStdString(it.first).find(origin2.Serialize()));
}
}
TEST_F(LocalStorageContextMojoTest, DeleteStorageWithPendingWrites) {
url::Origin origin1(GURL("http://foobar.com"));
url::Origin origin2(GURL("http://example.com"));
auto key = StdStringToUint8Vector("key");
auto value = StdStringToUint8Vector("value");
mojom::LevelDBWrapperPtr wrapper;
context()->OpenLocalStorage(origin1, MakeRequest(&wrapper));
wrapper->Put(key, value, "source", base::Bind(&NoOpSuccess));
wrapper.reset();
context()->OpenLocalStorage(origin2, MakeRequest(&wrapper));
wrapper->Put(key, value, "source", base::Bind(&NoOpSuccess));
wrapper.reset();
// Make sure all data gets committed to disk.
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(mock_data().empty());
TestLevelDBObserver observer;
context()->OpenLocalStorage(origin1, MakeRequest(&wrapper));
wrapper->AddObserver(observer.Bind());
wrapper->Put(StdStringToUint8Vector("key2"), value, "source",
base::Bind(&NoOpSuccess));
base::RunLoop().RunUntilIdle();
context()->DeleteStorage(origin1);
base::RunLoop().RunUntilIdle();
ASSERT_EQ(2u, observer.observations().size());
EXPECT_EQ(TestLevelDBObserver::Observation::kAdd,
observer.observations()[0].type);
EXPECT_EQ(TestLevelDBObserver::Observation::kDeleteAll,
observer.observations()[1].type);
// Data from origin2 should exist, including meta-data, but nothing should
// exist for origin1.
EXPECT_EQ(3u, mock_data().size());
for (const auto& it : mock_data()) {
if (Uint8VectorToStdString(it.first) == "VERSION")
continue;
EXPECT_EQ(std::string::npos,
Uint8VectorToStdString(it.first).find(origin1.Serialize()));
EXPECT_NE(std::string::npos,
Uint8VectorToStdString(it.first).find(origin2.Serialize()));
}
}
TEST_F(LocalStorageContextMojoTest, DeleteStorageForPhysicalOrigin) {
url::Origin origin1a(GURL("http://foobar.com"));
url::Origin origin1b(GURL("http-so://suborigin.foobar.com"));
url::Origin origin2(GURL("https://foobar.com"));
auto key = StdStringToUint8Vector("key");
auto value = StdStringToUint8Vector("value");
mojom::LevelDBWrapperPtr wrapper;
context()->OpenLocalStorage(origin1a, MakeRequest(&wrapper));
wrapper->Put(key, value, "source", base::Bind(&NoOpSuccess));
wrapper.reset();
context()->OpenLocalStorage(origin1b, MakeRequest(&wrapper));
wrapper->Put(key, value, "source", base::Bind(&NoOpSuccess));
wrapper.reset();
context()->OpenLocalStorage(origin2, MakeRequest(&wrapper));
wrapper->Put(key, value, "source", base::Bind(&NoOpSuccess));
wrapper.reset();
// Make sure all data gets committed to disk.
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(mock_data().empty());
context()->DeleteStorageForPhysicalOrigin(origin1b);
base::RunLoop().RunUntilIdle();
// Data from origin2 should exist, including meta-data, but nothing should
// exist for origin1.
EXPECT_EQ(3u, mock_data().size());
for (const auto& it : mock_data()) {
if (Uint8VectorToStdString(it.first) == "VERSION")
continue;
EXPECT_EQ(std::string::npos,
Uint8VectorToStdString(it.first).find(origin1a.Serialize()));
EXPECT_EQ(std::string::npos,
Uint8VectorToStdString(it.first).find(origin1b.Serialize()));
EXPECT_NE(std::string::npos,
Uint8VectorToStdString(it.first).find(origin2.Serialize()));
}
}
TEST_F(LocalStorageContextMojoTest, Migration) {
url::Origin origin1(GURL("http://foobar.com"));
url::Origin origin2(GURL("http://example.com"));
base::string16 key = base::ASCIIToUTF16("key");
base::string16 value = base::ASCIIToUTF16("value");
DOMStorageNamespace* local = local_storage_namespace();
DOMStorageArea* area = local->OpenStorageArea(origin1.GetURL());
base::NullableString16 dummy;
area->SetItem(key, value, &dummy);
local->CloseStorageArea(area);
FlushAndPurgeDOMStorageMemory();
// Opening origin2 and accessing its data should not migrate anything.
mojom::LevelDBWrapperPtr wrapper;
context()->OpenLocalStorage(origin2, MakeRequest(&wrapper));
wrapper->Get(std::vector<uint8_t>(), base::Bind(&NoOpGet));
wrapper.reset();
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(mock_data().empty());
// Opening origin1 and accessing its data should migrate its storage.
context()->OpenLocalStorage(origin1, MakeRequest(&wrapper));
wrapper->Get(std::vector<uint8_t>(), base::Bind(&NoOpGet));
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(mock_data().empty());
base::RunLoop run_loop;
bool success = false;
std::vector<uint8_t> result;
wrapper->Get(
LocalStorageContextMojo::MigrateString(key),
base::Bind(&GetCallback, run_loop.QuitClosure(), &success, &result));
run_loop.Run();
EXPECT_TRUE(success);
EXPECT_EQ(LocalStorageContextMojo::MigrateString(value), result);
// Origin1 should no longer exist in old storage.
area = local->OpenStorageArea(origin1.GetURL());
ASSERT_EQ(0u, area->Length());
local->CloseStorageArea(area);
}
namespace {
class ServiceTestClient : public service_manager::test::ServiceTestClient,
public service_manager::mojom::ServiceFactory {
public:
explicit ServiceTestClient(service_manager::test::ServiceTest* test)
: service_manager::test::ServiceTestClient(test) {
registry_.AddInterface<service_manager::mojom::ServiceFactory>(base::Bind(
&ServiceTestClient::BindServiceFactoryRequest, base::Unretained(this)));
}
~ServiceTestClient() override {}
protected:
void OnBindInterface(const service_manager::BindSourceInfo& source_info,
const std::string& interface_name,
mojo::ScopedMessagePipeHandle interface_pipe) override {
registry_.BindInterface(source_info, interface_name,
std::move(interface_pipe));
}
void CreateService(service_manager::mojom::ServiceRequest request,
const std::string& name) override {
if (name == file::mojom::kServiceName) {
file_service_context_.reset(new service_manager::ServiceContext(
file::CreateFileService(
BrowserThread::GetTaskRunnerForThread(BrowserThread::FILE),
BrowserThread::GetTaskRunnerForThread(BrowserThread::DB)),
std::move(request)));
}
}
void BindServiceFactoryRequest(
const service_manager::BindSourceInfo& source_info,
service_manager::mojom::ServiceFactoryRequest request) {
service_factory_bindings_.AddBinding(this, std::move(request));
}
private:
service_manager::BinderRegistry registry_;
mojo::BindingSet<service_manager::mojom::ServiceFactory>
service_factory_bindings_;
std::unique_ptr<service_manager::ServiceContext> file_service_context_;
};
} // namespace
class LocalStorageContextMojoTestWithService
: public service_manager::test::ServiceTest {
public:
LocalStorageContextMojoTestWithService()
: ServiceTest("content_unittests", false),
thread_bundle_(TestBrowserThreadBundle::REAL_FILE_THREAD) {}
~LocalStorageContextMojoTestWithService() override {}
protected:
void SetUp() override {
ServiceTest::SetUp();
ASSERT_TRUE(temp_path_.CreateUniqueTempDir());
file::AssociateServiceUserIdWithUserDir(test_userid(),
temp_path_.GetPath());
}
void TearDown() override {
ServiceTest::TearDown();
}
std::unique_ptr<service_manager::Service> CreateService() override {
return base::MakeUnique<ServiceTestClient>(this);
}
const base::FilePath& temp_path() { return temp_path_.GetPath(); }
base::FilePath FirstEntryInDir() {
base::FileEnumerator enumerator(
temp_path(), false /* recursive */,
base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES);
return enumerator.Next();
}
void DoTestPut(LocalStorageContextMojo* context,
const std::vector<uint8_t>& key,
const std::vector<uint8_t>& value) {
mojom::LevelDBWrapperPtr wrapper;
context->OpenLocalStorage(url::Origin(GURL("http://foobar.com")),
MakeRequest(&wrapper));
wrapper->Put(key, value, "source", base::Bind(&NoOpSuccess));
wrapper.reset();
base::RunLoop().RunUntilIdle();
}
bool DoTestGet(LocalStorageContextMojo* context,
const std::vector<uint8_t>& key,
std::vector<uint8_t>* result) {
mojom::LevelDBWrapperPtr wrapper;
context->OpenLocalStorage(url::Origin(GURL("http://foobar.com")),
MakeRequest(&wrapper));
base::RunLoop run_loop;
bool success = false;
wrapper->Get(key, base::Bind(&GetCallback, run_loop.QuitClosure(), &success,
result));
run_loop.Run();
return success;
}
private:
TestBrowserThreadBundle thread_bundle_;
base::ScopedTempDir temp_path_;
DISALLOW_COPY_AND_ASSIGN(LocalStorageContextMojoTestWithService);
};
TEST_F(LocalStorageContextMojoTestWithService, InMemory) {
auto context = base::MakeUnique<LocalStorageContextMojo>(
connector(), nullptr, base::FilePath(), base::FilePath());
auto key = StdStringToUint8Vector("key");
auto value = StdStringToUint8Vector("value");
mojom::LevelDBWrapperPtr wrapper;
context->OpenLocalStorage(url::Origin(GURL("http://foobar.com")),
MakeRequest(&wrapper));
DoTestPut(context.get(), key, value);
std::vector<uint8_t> result;
EXPECT_TRUE(DoTestGet(context.get(), key, &result));
EXPECT_EQ(value, result);
context.reset();
base::RunLoop().RunUntilIdle();
// Should not have created any files.
EXPECT_TRUE(FirstEntryInDir().empty());
// Re-opening should get fresh data.
context = base::MakeUnique<LocalStorageContextMojo>(
connector(), nullptr, base::FilePath(), base::FilePath());
EXPECT_FALSE(DoTestGet(context.get(), key, &result));
}
TEST_F(LocalStorageContextMojoTestWithService, InMemoryInvalidPath) {
auto context = base::MakeUnique<LocalStorageContextMojo>(
connector(), nullptr, base::FilePath(),
base::FilePath(FILE_PATH_LITERAL("../../")));
auto key = StdStringToUint8Vector("key");
auto value = StdStringToUint8Vector("value");
mojom::LevelDBWrapperPtr wrapper;
context->OpenLocalStorage(url::Origin(GURL("http://foobar.com")),
MakeRequest(&wrapper));
DoTestPut(context.get(), key, value);
std::vector<uint8_t> result;
EXPECT_TRUE(DoTestGet(context.get(), key, &result));
EXPECT_EQ(value, result);
context.reset();
base::RunLoop().RunUntilIdle();
// Should not have created any files.
EXPECT_TRUE(FirstEntryInDir().empty());
}
TEST_F(LocalStorageContextMojoTestWithService, OnDisk) {
base::FilePath test_path(FILE_PATH_LITERAL("test_path"));
auto context = base::MakeUnique<LocalStorageContextMojo>(
connector(), nullptr, base::FilePath(), test_path);
auto key = StdStringToUint8Vector("key");
auto value = StdStringToUint8Vector("value");
DoTestPut(context.get(), key, value);
std::vector<uint8_t> result;
EXPECT_TRUE(DoTestGet(context.get(), key, &result));
EXPECT_EQ(value, result);
context.reset();
base::RunLoop().RunUntilIdle();
// Should have created files.
EXPECT_EQ(test_path, FirstEntryInDir().BaseName());
// Should be able to re-open.
context = base::MakeUnique<LocalStorageContextMojo>(
connector(), nullptr, base::FilePath(), test_path);
EXPECT_TRUE(DoTestGet(context.get(), key, &result));
EXPECT_EQ(value, result);
}
TEST_F(LocalStorageContextMojoTestWithService, InvalidVersionOnDisk) {
base::FilePath test_path(FILE_PATH_LITERAL("test_path"));
// Create context and add some data to it.
auto context = base::MakeUnique<LocalStorageContextMojo>(
connector(), nullptr, base::FilePath(), test_path);
auto key = StdStringToUint8Vector("key");
auto value = StdStringToUint8Vector("value");
DoTestPut(context.get(), key, value);
std::vector<uint8_t> result;
EXPECT_TRUE(DoTestGet(context.get(), key, &result));
EXPECT_EQ(value, result);
context.reset();
base::RunLoop().RunUntilIdle();
{
// Mess up version number in database.
leveldb_env::ChromiumEnv env;
leveldb::DB* db = nullptr;
leveldb::Options options;
options.env = &env;
base::FilePath db_path =
temp_path().Append(test_path).Append(FILE_PATH_LITERAL("leveldb"));
ASSERT_TRUE(leveldb::DB::Open(options, db_path.AsUTF8Unsafe(), &db).ok());
std::unique_ptr<leveldb::DB> db_owner(db);
ASSERT_TRUE(db->Put(leveldb::WriteOptions(), "VERSION", "argh").ok());
}
// Make sure data is gone.
context = base::MakeUnique<LocalStorageContextMojo>(
connector(), nullptr, base::FilePath(), test_path);
EXPECT_FALSE(DoTestGet(context.get(), key, &result));
// Write data again.
DoTestPut(context.get(), key, value);
context.reset();
base::RunLoop().RunUntilIdle();
// Data should have been preserved now.
context = base::MakeUnique<LocalStorageContextMojo>(
connector(), nullptr, base::FilePath(), test_path);
EXPECT_TRUE(DoTestGet(context.get(), key, &result));
EXPECT_EQ(value, result);
}
} // namespace content