blob: c701b619c126f5b72e6c18bc0161ab2946e8fcbb [file] [log] [blame]
// Copyright 2018 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/session_storage_context_mojo.h"
#include <stdint.h>
#include <vector>
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/guid.h"
#include "base/memory/ref_counted.h"
#include "base/sequenced_task_runner.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind_test_util.h"
#include "base/test/scoped_feature_list.h"
#include "components/services/leveldb/public/cpp/util.h"
#include "content/browser/dom_storage/session_storage_database.h"
#include "content/browser/dom_storage/test/fake_leveldb_database_error_on_write.h"
#include "content/browser/dom_storage/test/fake_leveldb_service.h"
#include "content/browser/dom_storage/test/mojo_test_with_file_service.h"
#include "content/browser/dom_storage/test/storage_area_test_util.h"
#include "content/common/dom_storage/dom_storage_types.h"
#include "content/public/browser/session_storage_usage_info.h"
#include "content/public/test/test_utils.h"
#include "mojo/core/embedder/embedder.h"
#include "mojo/public/cpp/bindings/strong_associated_binding.h"
#include "services/file/public/mojom/constants.mojom.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features.h"
namespace content {
namespace {
using leveldb::StdStringToUint8Vector;
using leveldb::String16ToUint8Vector;
using leveldb::Uint8VectorToStdString;
using leveldb::mojom::DatabaseError;
using leveldb::mojom::KeyValuePtr;
static const char kSessionStorageDirectory[] = "Session Storage";
static const int kTestProcessId = 0;
void GetStorageUsageCallback(base::OnceClosure callback,
std::vector<SessionStorageUsageInfo>* out_result,
std::vector<SessionStorageUsageInfo> result) {
*out_result = std::move(result);
std::move(callback).Run();
}
class SessionStorageContextMojoTest : public test::MojoTestWithFileService {
public:
SessionStorageContextMojoTest() {}
~SessionStorageContextMojoTest() override {
if (context_)
ShutdownContext();
}
void SetUp() override {
features_.InitAndEnableFeature(blink::features::kOnionSoupDOMStorage);
mojo::core::SetDefaultProcessErrorCallback(base::BindRepeating(
&SessionStorageContextMojoTest::OnBadMessage, base::Unretained(this)));
}
void TearDown() override {
mojo::core::SetDefaultProcessErrorCallback(
mojo::core::ProcessErrorCallback());
}
mojo::ReportBadMessageCallback GetBadMessageCallback() {
return base::BindOnce(&SessionStorageContextMojoTest::OnBadMessage,
base::Unretained(this));
}
void OnBadMessage(const std::string& reason) { bad_message_called_ = true; }
void SetBackingMode(SessionStorageContextMojo::BackingMode backing_mode) {
DCHECK(!context_);
backing_mode_ = backing_mode;
}
SessionStorageContextMojo* context() {
if (!context_) {
context_ = new SessionStorageContextMojo(
base::SequencedTaskRunnerHandle::Get(), connector(), backing_mode_,
base::FilePath(), kSessionStorageDirectory);
}
return context_;
}
void ShutdownContext() {
context_->ShutdownAndDelete();
context_ = nullptr;
base::RunLoop().RunUntilIdle();
}
std::vector<SessionStorageUsageInfo> GetStorageUsageSync() {
base::RunLoop run_loop;
std::vector<SessionStorageUsageInfo> result;
context()->GetStorageUsage(base::BindOnce(&GetStorageUsageCallback,
run_loop.QuitClosure(), &result));
run_loop.Run();
return result;
}
void DoTestPut(const std::string& namespace_id,
const url::Origin& origin,
base::StringPiece key,
base::StringPiece value,
const std::string& source) {
context()->CreateSessionNamespace(namespace_id);
blink::mojom::SessionStorageNamespacePtr ss_namespace;
context()->OpenSessionStorage(kTestProcessId, namespace_id,
GetBadMessageCallback(),
mojo::MakeRequest(&ss_namespace));
blink::mojom::StorageAreaAssociatedPtr leveldb;
ss_namespace->OpenArea(origin, mojo::MakeRequest(&leveldb));
EXPECT_TRUE(test::PutSync(
leveldb.get(), leveldb::StringPieceToUint8Vector(key),
leveldb::StringPieceToUint8Vector(value), base::nullopt, source));
context()->DeleteSessionNamespace(namespace_id, true);
}
base::Optional<std::vector<uint8_t>> DoTestGet(
const std::string& namespace_id,
const url::Origin& origin,
base::StringPiece key) {
context()->CreateSessionNamespace(namespace_id);
blink::mojom::SessionStorageNamespacePtr ss_namespace;
context()->OpenSessionStorage(kTestProcessId, namespace_id,
GetBadMessageCallback(),
mojo::MakeRequest(&ss_namespace));
blink::mojom::StorageAreaAssociatedPtr leveldb;
ss_namespace->OpenArea(origin, mojo::MakeRequest(&leveldb));
// Use the GetAll interface because Gets are being removed.
std::vector<blink::mojom::KeyValuePtr> data;
EXPECT_TRUE(test::GetAllSync(leveldb.get(), &data));
context()->DeleteSessionNamespace(namespace_id, true);
std::vector<uint8_t> key_as_bytes = leveldb::StringPieceToUint8Vector(key);
for (const auto& key_value : data) {
if (key_value->key == key_as_bytes) {
return key_value->value;
}
}
return base::nullopt;
}
protected:
bool bad_message_called_ = false;
private:
SessionStorageContextMojo::BackingMode backing_mode_ =
SessionStorageContextMojo::BackingMode::kRestoreDiskState;
base::test::ScopedFeatureList features_;
SessionStorageContextMojo* context_ = nullptr;
DISALLOW_COPY_AND_ASSIGN(SessionStorageContextMojoTest);
};
TEST_F(SessionStorageContextMojoTest, MigrationV0ToV1) {
std::string namespace_id1 = base::GenerateGUID();
std::string namespace_id2 = base::GenerateGUID();
url::Origin origin1 = url::Origin::Create(GURL("http://foobar.com"));
url::Origin origin2 = url::Origin::Create(GURL("http://example.com"));
base::string16 key = base::ASCIIToUTF16("key");
base::string16 value = base::ASCIIToUTF16("value");
base::string16 key2 = base::ASCIIToUTF16("key2");
key2.push_back(0xd83d);
key2.push_back(0xde00);
base::FilePath old_db_path =
temp_path().AppendASCII(kSessionStorageDirectory);
{
scoped_refptr<SessionStorageDatabase> db =
base::MakeRefCounted<SessionStorageDatabase>(
old_db_path, base::ThreadTaskRunnerHandle::Get().get());
DOMStorageValuesMap data;
data[key] = base::NullableString16(value, false);
data[key2] = base::NullableString16(value, false);
EXPECT_TRUE(db->CommitAreaChanges(namespace_id1, origin1, false, data));
EXPECT_TRUE(db->CloneNamespace(namespace_id1, namespace_id2));
}
EXPECT_TRUE(base::PathExists(old_db_path));
// The first call to context() here constructs it.
context()->CreateSessionNamespace(namespace_id1);
context()->CreateSessionNamespace(namespace_id2);
blink::mojom::SessionStorageNamespacePtr ss_namespace1;
context()->OpenSessionStorage(kTestProcessId, namespace_id1,
GetBadMessageCallback(),
mojo::MakeRequest(&ss_namespace1));
blink::mojom::SessionStorageNamespacePtr ss_namespace2;
context()->OpenSessionStorage(kTestProcessId, namespace_id2,
GetBadMessageCallback(),
mojo::MakeRequest(&ss_namespace2));
blink::mojom::StorageAreaAssociatedPtr leveldb_n2_o1;
blink::mojom::StorageAreaAssociatedPtr leveldb_n2_o2;
ss_namespace2->OpenArea(origin1, mojo::MakeRequest(&leveldb_n2_o1));
ss_namespace2->OpenArea(origin2, mojo::MakeRequest(&leveldb_n2_o2));
std::vector<blink::mojom::KeyValuePtr> data;
EXPECT_TRUE(test::GetAllSync(leveldb_n2_o1.get(), &data));
// There should have been a migration to get rid of the "map-0-" refcount
// field.
EXPECT_EQ(2ul, data.size());
std::vector<uint8_t> key_as_vector =
StdStringToUint8Vector(base::UTF16ToUTF8(key));
EXPECT_TRUE(base::ContainsValue(
data, blink::mojom::KeyValue::New(key_as_vector,
String16ToUint8Vector(value))));
EXPECT_TRUE(base::ContainsValue(
data, blink::mojom::KeyValue::New(key_as_vector,
String16ToUint8Vector(value))));
}
TEST_F(SessionStorageContextMojoTest, StartupShutdownSave) {
std::string namespace_id1 = base::GenerateGUID();
url::Origin origin1 = url::Origin::Create(GURL("http://foobar.com"));
context()->CreateSessionNamespace(namespace_id1);
blink::mojom::SessionStorageNamespacePtr ss_namespace1;
context()->OpenSessionStorage(kTestProcessId, namespace_id1,
GetBadMessageCallback(),
mojo::MakeRequest(&ss_namespace1));
blink::mojom::StorageAreaAssociatedPtr leveldb_n1_o1;
ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb_n1_o1));
// Verify no data.
std::vector<blink::mojom::KeyValuePtr> data;
EXPECT_TRUE(test::GetAllSync(leveldb_n1_o1.get(), &data));
EXPECT_EQ(0ul, data.size());
// Put some data.
EXPECT_TRUE(test::PutSync(
leveldb_n1_o1.get(), leveldb::StringPieceToUint8Vector("key1"),
leveldb::StringPieceToUint8Vector("value1"), base::nullopt, "source1"));
// Verify data is there.
EXPECT_TRUE(test::GetAllSync(leveldb_n1_o1.get(), &data));
EXPECT_EQ(1ul, data.size());
// Delete the namespace and shutdown the context, BUT persist the namespace so
// it can be loaded again.
context()->DeleteSessionNamespace(namespace_id1, true);
ShutdownContext();
// This will re-open the context, and load the persisted namespace.
context()->CreateSessionNamespace(namespace_id1);
context()->OpenSessionStorage(kTestProcessId, namespace_id1,
GetBadMessageCallback(),
mojo::MakeRequest(&ss_namespace1));
ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb_n1_o1));
// The data from before should be here.
EXPECT_TRUE(test::GetAllSync(leveldb_n1_o1.get(), &data));
EXPECT_EQ(1ul, data.size());
// Delete the namespace and shutdown the context and do not persist the data.
context()->DeleteSessionNamespace(namespace_id1, false);
ShutdownContext();
// This will re-open the context, and the namespace should be empty.
context()->CreateSessionNamespace(namespace_id1);
context()->OpenSessionStorage(kTestProcessId, namespace_id1,
GetBadMessageCallback(),
mojo::MakeRequest(&ss_namespace1));
ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb_n1_o1));
// The data from before should not be here.
EXPECT_TRUE(test::GetAllSync(leveldb_n1_o1.get(), &data));
EXPECT_EQ(0ul, data.size());
}
TEST_F(SessionStorageContextMojoTest, CloneBeforeBrowserClone) {
std::string namespace_id1 = base::GenerateGUID();
std::string namespace_id2 = base::GenerateGUID();
url::Origin origin1 = url::Origin::Create(GURL("http://foobar.com"));
context()->CreateSessionNamespace(namespace_id1);
blink::mojom::SessionStorageNamespacePtr ss_namespace1;
context()->OpenSessionStorage(kTestProcessId, namespace_id1,
GetBadMessageCallback(),
mojo::MakeRequest(&ss_namespace1));
blink::mojom::StorageAreaAssociatedPtr leveldb_n1_o1;
ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb_n1_o1));
// Put some data.
EXPECT_TRUE(test::PutSync(
leveldb_n1_o1.get(), leveldb::StringPieceToUint8Vector("key1"),
leveldb::StringPieceToUint8Vector("value1"), base::nullopt, "source1"));
ss_namespace1->Clone(namespace_id2);
leveldb_n1_o1.FlushForTesting();
// Do the browser-side clone afterwards.
context()->CloneSessionNamespace(
namespace_id1, namespace_id2,
SessionStorageContextMojo::CloneType::kWaitForCloneOnNamespace);
// Open the second namespace.
blink::mojom::SessionStorageNamespacePtr ss_namespace2;
context()->OpenSessionStorage(kTestProcessId, namespace_id2,
GetBadMessageCallback(),
mojo::MakeRequest(&ss_namespace2));
blink::mojom::StorageAreaAssociatedPtr leveldb_n2_o1;
ss_namespace2->OpenArea(origin1, mojo::MakeRequest(&leveldb_n2_o1));
// The data should be in namespace 2.
std::vector<blink::mojom::KeyValuePtr> data;
EXPECT_TRUE(test::GetAllSync(leveldb_n2_o1.get(), &data));
EXPECT_EQ(1ul, data.size());
}
TEST_F(SessionStorageContextMojoTest, Cloning) {
std::string namespace_id1 = base::GenerateGUID();
std::string namespace_id2 = base::GenerateGUID();
url::Origin origin1 = url::Origin::Create(GURL("http://foobar.com"));
context()->CreateSessionNamespace(namespace_id1);
blink::mojom::SessionStorageNamespacePtr ss_namespace1;
context()->OpenSessionStorage(kTestProcessId, namespace_id1,
GetBadMessageCallback(),
mojo::MakeRequest(&ss_namespace1));
blink::mojom::StorageAreaAssociatedPtr leveldb_n1_o1;
ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb_n1_o1));
// Context-triggered clone before the put. The clone doesn't actually count
// until a clone comes from the namespace.
context()->CloneSessionNamespace(
namespace_id1, namespace_id2,
SessionStorageContextMojo::CloneType::kWaitForCloneOnNamespace);
// Put some data.
EXPECT_TRUE(test::PutSync(
leveldb_n1_o1.get(), leveldb::StringPieceToUint8Vector("key1"),
leveldb::StringPieceToUint8Vector("value1"), base::nullopt, "source1"));
ss_namespace1->Clone(namespace_id2);
leveldb_n1_o1.FlushForTesting();
// Open the second namespace.
blink::mojom::SessionStorageNamespacePtr ss_namespace2;
context()->OpenSessionStorage(kTestProcessId, namespace_id2,
GetBadMessageCallback(),
mojo::MakeRequest(&ss_namespace2));
blink::mojom::StorageAreaAssociatedPtr leveldb_n2_o1;
ss_namespace2->OpenArea(origin1, mojo::MakeRequest(&leveldb_n2_o1));
// Delete the namespace and shutdown the context, BUT persist the namespace so
// it can be loaded again. This tests the case where our cloning works even
// though the namespace is deleted (but persisted on disk).
context()->DeleteSessionNamespace(namespace_id1, true);
// The data from before should be in namespace 2.
std::vector<blink::mojom::KeyValuePtr> data;
EXPECT_TRUE(test::GetAllSync(leveldb_n2_o1.get(), &data));
EXPECT_EQ(1ul, data.size());
// Put some data in namespace 2.
EXPECT_TRUE(test::PutSync(
leveldb_n2_o1.get(), leveldb::StringPieceToUint8Vector("key2"),
leveldb::StringPieceToUint8Vector("value2"), base::nullopt, "source1"));
EXPECT_TRUE(test::GetAllSync(leveldb_n2_o1.get(), &data));
EXPECT_EQ(2ul, data.size());
// Re-open namespace 1, check that we don't have the extra data.
context()->CreateSessionNamespace(namespace_id1);
context()->OpenSessionStorage(kTestProcessId, namespace_id1,
GetBadMessageCallback(),
mojo::MakeRequest(&ss_namespace1));
ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb_n1_o1));
// We should only have the first value.
EXPECT_TRUE(test::GetAllSync(leveldb_n1_o1.get(), &data));
EXPECT_EQ(1ul, data.size());
}
TEST_F(SessionStorageContextMojoTest, ImmediateCloning) {
std::string namespace_id1 = base::GenerateGUID();
std::string namespace_id2 = base::GenerateGUID();
std::string namespace_id3 = base::GenerateGUID();
url::Origin origin1 = url::Origin::Create(GURL("http://foobar.com"));
context()->CreateSessionNamespace(namespace_id1);
blink::mojom::SessionStorageNamespacePtr ss_namespace1;
context()->OpenSessionStorage(kTestProcessId, namespace_id1,
GetBadMessageCallback(),
mojo::MakeRequest(&ss_namespace1));
blink::mojom::StorageAreaAssociatedPtr leveldb_n1_o1;
ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb_n1_o1));
// Immediate clone.
context()->CloneSessionNamespace(
namespace_id1, namespace_id2,
SessionStorageContextMojo::CloneType::kImmediate);
// Open the second namespace, ensure empty.
{
blink::mojom::SessionStorageNamespacePtr ss_namespace2;
context()->OpenSessionStorage(kTestProcessId, namespace_id2,
GetBadMessageCallback(),
mojo::MakeRequest(&ss_namespace2));
blink::mojom::StorageAreaAssociatedPtr leveldb_n2_o1;
ss_namespace2->OpenArea(origin1, mojo::MakeRequest(&leveldb_n2_o1));
std::vector<blink::mojom::KeyValuePtr> data;
EXPECT_TRUE(test::GetAllSync(leveldb_n2_o1.get(), &data));
EXPECT_EQ(0ul, data.size());
}
// Delete that namespace, copy again after a put.
context()->DeleteSessionNamespace(namespace_id2, false);
// Put some data.
EXPECT_TRUE(test::PutSync(
leveldb_n1_o1.get(), leveldb::StringPieceToUint8Vector("key1"),
leveldb::StringPieceToUint8Vector("value2"), base::nullopt, "source1"));
context()->CloneSessionNamespace(
namespace_id1, namespace_id2,
SessionStorageContextMojo::CloneType::kImmediate);
// Open the second namespace, ensure populated
{
blink::mojom::SessionStorageNamespacePtr ss_namespace2;
context()->OpenSessionStorage(kTestProcessId, namespace_id2,
GetBadMessageCallback(),
mojo::MakeRequest(&ss_namespace2));
blink::mojom::StorageAreaAssociatedPtr leveldb_n2_o1;
ss_namespace2->OpenArea(origin1, mojo::MakeRequest(&leveldb_n2_o1));
std::vector<blink::mojom::KeyValuePtr> data;
EXPECT_TRUE(test::GetAllSync(leveldb_n2_o1.get(), &data));
EXPECT_EQ(1ul, data.size());
}
context()->DeleteSessionNamespace(namespace_id2, false);
// Verify that cloning from the namespace object will result in a bad message.
context()->CloneSessionNamespace(
namespace_id1, namespace_id2,
SessionStorageContextMojo::CloneType::kImmediate);
// This should cause a bad message.
ss_namespace1->Clone(namespace_id2);
leveldb_n1_o1.FlushForTesting();
EXPECT_TRUE(bad_message_called_);
}
TEST_F(SessionStorageContextMojoTest, Scavenging) {
// Create our namespace, destroy our context and leave that namespace on disk,
// and verify that it is scavenged if we re-create the context without calling
// CreateSessionNamespace.
// Create, verify we have no data.
std::string namespace_id1 = base::GenerateGUID();
url::Origin origin1 = url::Origin::Create(GURL("http://foobar.com"));
context()->CreateSessionNamespace(namespace_id1);
// This scavenge call should NOT delete the namespace, as we just created it.
{
base::RunLoop loop;
// Cause the connection to start loading, so we start scavenging mid-load.
context()->Flush();
context()->ScavengeUnusedNamespaces(loop.QuitClosure());
loop.Run();
}
// Restart context.
ShutdownContext();
context()->CreateSessionNamespace(namespace_id1);
blink::mojom::SessionStorageNamespacePtr ss_namespace1;
context()->OpenSessionStorage(kTestProcessId, namespace_id1,
GetBadMessageCallback(),
mojo::MakeRequest(&ss_namespace1));
blink::mojom::StorageAreaAssociatedPtr leveldb_n1_o1;
ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb_n1_o1));
EXPECT_TRUE(test::PutSync(
leveldb_n1_o1.get(), leveldb::StringPieceToUint8Vector("key1"),
leveldb::StringPieceToUint8Vector("value1"), base::nullopt, "source1"));
// This scavenge call should NOT delete the namespace, as we never called
// delete.
context()->ScavengeUnusedNamespaces(base::OnceClosure());
// Restart context.
ShutdownContext();
context()->CreateSessionNamespace(namespace_id1);
// Delete the namespace and shutdown the context, BUT persist the namespace so
// it can be loaded again.
context()->DeleteSessionNamespace(namespace_id1, true);
// This scavenge call should NOT delete the namespace, as we explicity
// persisted the namespace.
{
base::RunLoop loop;
context()->ScavengeUnusedNamespaces(loop.QuitClosure());
loop.Run();
}
ShutdownContext();
// Re-open the context, load the persisted namespace, and verify we still have
// data.
context()->CreateSessionNamespace(namespace_id1);
context()->OpenSessionStorage(kTestProcessId, namespace_id1,
GetBadMessageCallback(),
mojo::MakeRequest(&ss_namespace1));
ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb_n1_o1));
std::vector<blink::mojom::KeyValuePtr> data;
EXPECT_TRUE(test::GetAllSync(leveldb_n1_o1.get(), &data));
EXPECT_EQ(1ul, data.size());
// Shutting down the context without an explicit DeleteSessionNamespace should
// leave the data on disk.
ShutdownContext();
// Re-open the context, and scavenge should now remove the namespace as there
// has been no call to CreateSessionNamespace. Check the data is empty.
{
base::RunLoop loop;
context()->ScavengeUnusedNamespaces(loop.QuitClosure());
loop.Run();
}
context()->CreateSessionNamespace(namespace_id1);
context()->OpenSessionStorage(kTestProcessId, namespace_id1,
GetBadMessageCallback(),
mojo::MakeRequest(&ss_namespace1));
ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb_n1_o1));
EXPECT_TRUE(test::GetAllSync(leveldb_n1_o1.get(), &data));
EXPECT_EQ(0ul, data.size());
}
TEST_F(SessionStorageContextMojoTest, InvalidVersionOnDisk) {
std::string namespace_id = base::GenerateGUID();
url::Origin origin = url::Origin::Create(GURL("http://foobar.com"));
// Create context and add some data to it (and check it's there).
DoTestPut(namespace_id, origin, "key", "value", "source");
base::Optional<std::vector<uint8_t>> opt_value =
DoTestGet(namespace_id, origin, "key");
ASSERT_TRUE(opt_value);
EXPECT_EQ(leveldb::StringPieceToUint8Vector("value"), opt_value.value());
ShutdownContext();
{
// Mess up version number in database.
leveldb_env::ChromiumEnv env;
std::unique_ptr<leveldb::DB> db;
leveldb_env::Options options;
options.env = &env;
base::FilePath db_path =
temp_path().Append(FILE_PATH_LITERAL("Session Storage"));
ASSERT_TRUE(leveldb_env::OpenDB(options, db_path.AsUTF8Unsafe(), &db).ok());
ASSERT_TRUE(db->Put(leveldb::WriteOptions(), "version", "argh").ok());
}
opt_value = DoTestGet(namespace_id, origin, "key");
EXPECT_FALSE(opt_value);
// Write data again.
DoTestPut(namespace_id, origin, "key", "value", "source");
ShutdownContext();
// Data should have been preserved now.
opt_value = DoTestGet(namespace_id, origin, "key");
ASSERT_TRUE(opt_value);
EXPECT_EQ(leveldb::StringPieceToUint8Vector("value"), opt_value.value());
ShutdownContext();
}
TEST_F(SessionStorageContextMojoTest, CorruptionOnDisk) {
std::string namespace_id = base::GenerateGUID();
url::Origin origin = url::Origin::Create(GURL("http://foobar.com"));
// Create context and add some data to it (and check it's there).
DoTestPut(namespace_id, origin, "key", "value", "source");
base::Optional<std::vector<uint8_t>> opt_value =
DoTestGet(namespace_id, origin, "key");
ASSERT_TRUE(opt_value);
EXPECT_EQ(leveldb::StringPieceToUint8Vector("value"), opt_value.value());
ShutdownContext();
// Also flush Task Scheduler tasks to make sure the leveldb is fully closed.
content::RunAllTasksUntilIdle();
// Delete manifest files to mess up opening DB.
base::FilePath db_path =
temp_path().Append(FILE_PATH_LITERAL("Session Storage"));
base::FileEnumerator file_enum(db_path, true, base::FileEnumerator::FILES,
FILE_PATH_LITERAL("MANIFEST*"));
for (base::FilePath name = file_enum.Next(); !name.empty();
name = file_enum.Next()) {
base::DeleteFile(name, false);
}
opt_value = DoTestGet(namespace_id, origin, "key");
EXPECT_FALSE(opt_value);
// Write data again.
DoTestPut(namespace_id, origin, "key", "value", "source");
ShutdownContext();
// Data should have been preserved now.
opt_value = DoTestGet(namespace_id, origin, "key");
ASSERT_TRUE(opt_value);
EXPECT_EQ(leveldb::StringPieceToUint8Vector("value"), opt_value.value());
ShutdownContext();
}
TEST_F(SessionStorageContextMojoTest, RecreateOnCommitFailure) {
std::string namespace_id = base::GenerateGUID();
url::Origin origin1 = url::Origin::Create(GURL("http://foobar.com"));
url::Origin origin2 = url::Origin::Create(GURL("http://asf.com"));
url::Origin origin3 = url::Origin::Create(GURL("http://example.com"));
test::FakeLevelDBService fake_leveldb_service;
file_service()->GetBinderRegistryForTesting()->AddInterface(
leveldb::mojom::LevelDBService::Name_,
base::BindRepeating(&test::FakeLevelDBService::Bind,
base::Unretained(&fake_leveldb_service)));
// Open three connections to the database.
blink::mojom::StorageAreaAssociatedPtr area1;
blink::mojom::StorageAreaAssociatedPtr area2;
blink::mojom::StorageAreaAssociatedPtr area3;
blink::mojom::SessionStorageNamespacePtr ss_namespace;
context()->CreateSessionNamespace(namespace_id);
{
base::RunLoop loop;
fake_leveldb_service.SetOnOpenCallback(loop.QuitClosure());
context()->OpenSessionStorage(kTestProcessId, namespace_id,
GetBadMessageCallback(),
mojo::MakeRequest(&ss_namespace));
ss_namespace->OpenArea(origin1, mojo::MakeRequest(&area1));
ss_namespace->OpenArea(origin2, mojo::MakeRequest(&area2));
ss_namespace->OpenArea(origin3, mojo::MakeRequest(&area3));
loop.Run();
}
// Verify one attempt was made to open the database, and connect that request
// with a database implementation that always fails on write.
ASSERT_EQ(1u, fake_leveldb_service.open_requests().size());
auto& open_request = fake_leveldb_service.open_requests()[0];
std::map<std::vector<uint8_t>, std::vector<uint8_t>> test_data;
auto mock_db = mojo::MakeStrongAssociatedBinding(
std::make_unique<test::FakeLevelDBDatabaseErrorOnWrite>(&test_data),
std::move(open_request.request));
std::move(open_request.callback).Run(leveldb::mojom::DatabaseError::OK);
fake_leveldb_service.open_requests().clear();
// Setup a RunLoop so we can wait until LocalStorageContextMojo tries to
// reconnect to the database, which should happen after several commit
// errors.
base::RunLoop reopen_loop;
fake_leveldb_service.SetOnOpenCallback(reopen_loop.QuitClosure());
// Start a put operation on the third connection before starting to commit
// a lot of data on the first origin. This put operation should result in a
// pending commit that will get cancelled when the database connection is
// closed.
auto value = leveldb::StringPieceToUint8Vector("avalue");
area3->Put(leveldb::StringPieceToUint8Vector("w3key"), value, base::nullopt,
"source",
base::BindOnce([](bool success) { EXPECT_TRUE(success); }));
// Repeatedly write data to the database, to trigger enough commit errors.
int i = 0;
while (!area1.encountered_error()) {
++i;
base::RunLoop put_loop;
// Every write needs to be different to make sure there actually is a
// change to commit.
std::vector<uint8_t> old_value = value;
value[0]++;
area1.set_connection_error_handler(put_loop.QuitClosure());
area1->Put(leveldb::StringPieceToUint8Vector("key"), value, base::nullopt,
"source", base::BindLambdaForTesting([&](bool success) {
EXPECT_TRUE(success);
}));
area1.FlushForTesting();
put_loop.RunUntilIdle();
// And we need to flush after every change. Otherwise changes get batched up
// and only one commit is done some time later.
context()->FlushAreaForTesting(namespace_id, origin1);
}
// Make sure all messages to the DB have been processed (Flush above merely
// schedules a commit, but there is no guarantee about those having been
// processed yet).
fake_leveldb_service.FlushBindingsForTesting();
if (mock_db)
mock_db->FlushForTesting();
// At this point enough commit failures should have happened to cause the
// connection to the database to have been severed.
EXPECT_FALSE(mock_db);
// The connection to the second area should have closed as well.
EXPECT_TRUE(area2.encountered_error());
EXPECT_TRUE(ss_namespace.encountered_error());
// And the old database should have been destroyed.
EXPECT_EQ(1u, fake_leveldb_service.destroy_requests().size());
// Reconnect area1 to the database, and try to read a value.
context()->OpenSessionStorage(kTestProcessId, namespace_id,
GetBadMessageCallback(),
mojo::MakeRequest(&ss_namespace));
ss_namespace->OpenArea(origin1, mojo::MakeRequest(&area1));
base::RunLoop delete_loop;
bool success = true;
test::MockLevelDBObserver observer4;
area1->AddObserver(observer4.Bind());
area1->Delete(leveldb::StringPieceToUint8Vector("key"), base::nullopt,
"source", base::BindLambdaForTesting([&](bool success_in) {
success = success_in;
delete_loop.Quit();
}));
// Wait for LocalStorageContextMojo to try to reconnect to the database, and
// connect that new request to a properly functioning database.
reopen_loop.Run();
ASSERT_EQ(1u, fake_leveldb_service.open_requests().size());
auto& reopen_request = fake_leveldb_service.open_requests()[0];
mock_db = mojo::MakeStrongAssociatedBinding(
std::make_unique<FakeLevelDBDatabase>(&test_data),
std::move(reopen_request.request));
std::move(reopen_request.callback).Run(leveldb::mojom::DatabaseError::OK);
fake_leveldb_service.open_requests().clear();
// And deleting the value from the new area should have failed (as the
// database is empty).
delete_loop.Run();
area1 = nullptr;
ss_namespace.reset();
context()->DeleteSessionNamespace(namespace_id, true);
{
// Committing data should now work.
DoTestPut(namespace_id, origin1, "key", "value", "source");
base::Optional<std::vector<uint8_t>> opt_value =
DoTestGet(namespace_id, origin1, "key");
ASSERT_TRUE(opt_value);
EXPECT_EQ(leveldb::StringPieceToUint8Vector("value"), opt_value.value());
}
}
TEST_F(SessionStorageContextMojoTest, DontRecreateOnRepeatedCommitFailure) {
std::string namespace_id = base::GenerateGUID();
url::Origin origin1 = url::Origin::Create(GURL("http://foobar.com"));
test::FakeLevelDBService fake_leveldb_service;
file_service()->GetBinderRegistryForTesting()->AddInterface(
leveldb::mojom::LevelDBService::Name_,
base::BindRepeating(&test::FakeLevelDBService::Bind,
base::Unretained(&fake_leveldb_service)));
std::map<std::vector<uint8_t>, std::vector<uint8_t>> test_data;
// Open three connections to the database.
blink::mojom::StorageAreaAssociatedPtr area;
blink::mojom::SessionStorageNamespacePtr ss_namespace;
context()->CreateSessionNamespace(namespace_id);
{
base::RunLoop loop;
fake_leveldb_service.SetOnOpenCallback(loop.QuitClosure());
context()->OpenSessionStorage(kTestProcessId, namespace_id,
GetBadMessageCallback(),
mojo::MakeRequest(&ss_namespace));
ss_namespace->OpenArea(origin1, mojo::MakeRequest(&area));
loop.Run();
}
// Verify one attempt was made to open the database, and connect that request
// with a database implementation that always fails on write.
ASSERT_EQ(1u, fake_leveldb_service.open_requests().size());
auto& open_request = fake_leveldb_service.open_requests()[0];
auto mock_db = mojo::MakeStrongAssociatedBinding(
std::make_unique<test::FakeLevelDBDatabaseErrorOnWrite>(&test_data),
std::move(open_request.request));
std::move(open_request.callback).Run(leveldb::mojom::DatabaseError::OK);
fake_leveldb_service.open_requests().clear();
// Setup a RunLoop so we can wait until LocalStorageContextMojo tries to
// reconnect to the database, which should happen after several commit
// errors.
base::RunLoop reopen_loop;
fake_leveldb_service.SetOnOpenCallback(reopen_loop.QuitClosure());
// Repeatedly write data to the database, to trigger enough commit errors.
auto value = leveldb::StringPieceToUint8Vector("avalue");
base::Optional<std::vector<uint8_t>> old_value = base::nullopt;
while (!area.encountered_error()) {
base::RunLoop put_loop;
// Every write needs to be different to make sure there actually is a
// change to commit.
area.set_connection_error_handler(put_loop.QuitClosure());
area->Put(leveldb::StringPieceToUint8Vector("key"), value, old_value,
"source", base::BindLambdaForTesting([&](bool success) {
EXPECT_TRUE(success);
put_loop.Quit();
}));
area.FlushForTesting();
put_loop.RunUntilIdle();
// And we need to flush after every change. Otherwise changes get batched up
// and only one commit is done some time later.
context()->FlushAreaForTesting(namespace_id, origin1);
old_value = value;
value[0]++;
}
// Make sure all messages to the DB have been processed (Flush above merely
// schedules a commit, but there is no guarantee about those having been
// processed yet).
if (mock_db)
mock_db->FlushForTesting();
// At this point enough commit failures should have happened to cause the
// connection to the database to have been severed.
EXPECT_FALSE(mock_db);
// Wait for LocalStorageContextMojo to try to reconnect to the database, and
// connect that new request with a database implementation that always fails
// on write.
reopen_loop.Run();
ASSERT_EQ(1u, fake_leveldb_service.open_requests().size());
auto& reopen_request = fake_leveldb_service.open_requests()[0];
mock_db = mojo::MakeStrongAssociatedBinding(
std::make_unique<test::FakeLevelDBDatabaseErrorOnWrite>(&test_data),
std::move(reopen_request.request));
std::move(reopen_request.callback).Run(leveldb::mojom::DatabaseError::OK);
fake_leveldb_service.open_requests().clear();
// The old database should also have been destroyed.
EXPECT_EQ(1u, fake_leveldb_service.destroy_requests().size());
// Reconnect a area to the database, and repeatedly write data to it again.
// This time all should just keep getting written, and commit errors are
// getting ignored.
context()->OpenSessionStorage(kTestProcessId, namespace_id,
GetBadMessageCallback(),
mojo::MakeRequest(&ss_namespace));
ss_namespace->OpenArea(origin1, mojo::MakeRequest(&area));
old_value = base::nullopt;
for (int i = 0; i < 64; ++i) {
base::RunLoop put_loop;
// Every write needs to be different to make sure there actually is a
// change to commit.
area.set_connection_error_handler(put_loop.QuitClosure());
area->Put(leveldb::StringPieceToUint8Vector("key"), value, old_value,
"source", base::BindLambdaForTesting([&](bool success) {
EXPECT_TRUE(success);
put_loop.Quit();
}));
area.FlushForTesting();
put_loop.RunUntilIdle();
// And we need to flush after every change. Otherwise changes get batched up
// and only one commit is done some time later.
context()->FlushAreaForTesting(namespace_id, origin1);
old_value = value;
value[0]++;
}
// Make sure all messages to the DB have been processed (Flush above merely
// schedules a commit, but there is no guarantee about those having been
// processed yet).
if (mock_db)
mock_db->FlushForTesting();
EXPECT_TRUE(mock_db);
EXPECT_FALSE(area.encountered_error());
context()->DeleteSessionNamespace(namespace_id, false);
}
TEST_F(SessionStorageContextMojoTest, GetUsage) {
std::string namespace_id1 = base::GenerateGUID();
url::Origin origin1 = url::Origin::Create(GURL("http://foobar.com"));
context()->CreateSessionNamespace(namespace_id1);
blink::mojom::SessionStorageNamespacePtr ss_namespace1;
context()->OpenSessionStorage(kTestProcessId, namespace_id1,
GetBadMessageCallback(),
mojo::MakeRequest(&ss_namespace1));
blink::mojom::StorageAreaAssociatedPtr leveldb_n1_o1;
ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb_n1_o1));
// Put some data.
EXPECT_TRUE(test::PutSync(
leveldb_n1_o1.get(), leveldb::StringPieceToUint8Vector("key1"),
leveldb::StringPieceToUint8Vector("value1"), base::nullopt, "source1"));
base::RunLoop loop;
context()->GetStorageUsage(base::BindLambdaForTesting(
[&](std::vector<SessionStorageUsageInfo> usage) {
loop.Quit();
ASSERT_EQ(1u, usage.size());
EXPECT_EQ(origin1.GetURL(), usage[0].origin);
EXPECT_EQ(namespace_id1, usage[0].namespace_id);
}));
loop.Run();
}
// Tests that a SessionStorageContext still works if the database is
// disconnected.
TEST_F(SessionStorageContextMojoTest, MojoConnectionDisconnects) {
std::string namespace_id = base::GenerateGUID();
url::Origin origin = url::Origin::Create(GURL("http://foobar.com"));
auto key = StdStringToUint8Vector("key");
auto value = StdStringToUint8Vector("value");
std::map<std::vector<uint8_t>, std::vector<uint8_t>> test_data;
FakeLevelDBDatabase db(&test_data);
mojo::AssociatedBinding<leveldb::mojom::LevelDBDatabase> db_binding(&db);
leveldb::mojom::LevelDBDatabaseAssociatedPtr database_ptr;
leveldb::mojom::LevelDBDatabaseAssociatedRequest request =
MakeRequestAssociatedWithDedicatedPipe(&database_ptr);
context()->SetDatabaseForTesting(std::move(database_ptr));
db_binding.Bind(std::move(request));
// Put some data.
context()->CreateSessionNamespace(namespace_id);
blink::mojom::SessionStorageNamespacePtr ss_namespace;
context()->OpenSessionStorage(kTestProcessId, namespace_id,
GetBadMessageCallback(),
mojo::MakeRequest(&ss_namespace));
blink::mojom::StorageAreaAssociatedPtr area;
ss_namespace->OpenArea(origin, mojo::MakeRequest(&area));
EXPECT_TRUE(test::PutSync(area.get(), key, value, base::nullopt, "source"));
std::vector<uint8_t> result;
std::vector<blink::mojom::KeyValuePtr> data;
ASSERT_TRUE(test::GetAllSync(area.get(), &data));
EXPECT_EQ(1ul, data.size());
// Close the database connection.
db_binding.Close();
base::RunLoop().RunUntilIdle();
context()->CreateSessionNamespace(namespace_id);
context()->OpenSessionStorage(kTestProcessId, namespace_id,
GetBadMessageCallback(),
mojo::MakeRequest(&ss_namespace));
ss_namespace->OpenArea(origin, mojo::MakeRequest(&area));
// We can't access the data anymore.
ASSERT_TRUE(test::GetAllSync(area.get(), &data));
EXPECT_EQ(0ul, data.size());
// Check that session storage still works without a database.
EXPECT_TRUE(test::PutSync(area.get(), key, value, base::nullopt, "source"));
ASSERT_TRUE(test::GetAllSync(area.get(), &data));
EXPECT_EQ(1ul, data.size());
context()->DeleteStorage(origin, namespace_id, base::DoNothing());
ASSERT_TRUE(test::GetAllSync(area.get(), &data));
EXPECT_EQ(0ul, data.size());
EXPECT_TRUE(test::PutSync(area.get(), key, value, base::nullopt, "source"));
ASSERT_TRUE(test::GetAllSync(area.get(), &data));
EXPECT_EQ(1ul, data.size());
context()->DeleteSessionNamespace(namespace_id, true);
context()->ScavengeUnusedNamespaces(base::DoNothing());
ShutdownContext();
context()->CreateSessionNamespace(namespace_id);
context()->OpenSessionStorage(kTestProcessId, namespace_id,
GetBadMessageCallback(),
mojo::MakeRequest(&ss_namespace));
ss_namespace->OpenArea(origin, mojo::MakeRequest(&area));
ASSERT_TRUE(test::GetAllSync(area.get(), &data));
EXPECT_EQ(0ul, data.size());
}
TEST_F(SessionStorageContextMojoTest, DeleteStorage) {
std::string namespace_id1 = base::GenerateGUID();
url::Origin origin1 = url::Origin::Create(GURL("http://foobar.com"));
// First, test deleting data for a namespace that is open.
context()->CreateSessionNamespace(namespace_id1);
blink::mojom::SessionStorageNamespacePtr ss_namespace1;
context()->OpenSessionStorage(kTestProcessId, namespace_id1,
GetBadMessageCallback(),
mojo::MakeRequest(&ss_namespace1));
blink::mojom::StorageAreaAssociatedPtr leveldb_n1_o1;
ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb_n1_o1));
// Put some data.
EXPECT_TRUE(test::PutSync(
leveldb_n1_o1.get(), leveldb::StringPieceToUint8Vector("key1"),
leveldb::StringPieceToUint8Vector("value1"), base::nullopt, "source1"));
context()->DeleteStorage(origin1, namespace_id1, base::DoNothing());
std::vector<blink::mojom::KeyValuePtr> data;
ASSERT_TRUE(test::GetAllSync(leveldb_n1_o1.get(), &data));
EXPECT_EQ(0ul, data.size());
// Next, test that it deletes the data even if there isn't a namespace open.
// Put some data.
EXPECT_TRUE(test::PutSync(
leveldb_n1_o1.get(), leveldb::StringPieceToUint8Vector("key1"),
leveldb::StringPieceToUint8Vector("value1"), base::nullopt, "source1"));
// Delete the namespace and shutdown the context, BUT persist the namespace so
// it can be loaded again.
context()->DeleteSessionNamespace(namespace_id1, true);
ShutdownContext();
// This restarts the context, then deletes the storage.
context()->DeleteStorage(origin1, namespace_id1, base::DoNothing());
context()->CreateSessionNamespace(namespace_id1);
context()->OpenSessionStorage(kTestProcessId, namespace_id1,
GetBadMessageCallback(),
mojo::MakeRequest(&ss_namespace1));
ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb_n1_o1));
data.clear();
EXPECT_TRUE(test::GetAllSync(leveldb_n1_o1.get(), &data));
EXPECT_EQ(0ul, data.size());
}
TEST_F(SessionStorageContextMojoTest, PurgeInactiveWrappers) {
std::string namespace_id1 = base::GenerateGUID();
std::string namespace_id2 = base::GenerateGUID();
url::Origin origin1 = url::Origin::Create(GURL("http://foobar.com"));
context()->CreateSessionNamespace(namespace_id1);
blink::mojom::SessionStorageNamespacePtr ss_namespace1;
context()->OpenSessionStorage(kTestProcessId, namespace_id1,
GetBadMessageCallback(),
mojo::MakeRequest(&ss_namespace1));
blink::mojom::StorageAreaAssociatedPtr leveldb;
ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb));
// Put some data in both.
EXPECT_TRUE(test::PutSync(
leveldb.get(), leveldb::StringPieceToUint8Vector("key1"),
leveldb::StringPieceToUint8Vector("value1"), base::nullopt, "source1"));
context()->FlushAreaForTesting(namespace_id1, origin1);
ss_namespace1.reset();
leveldb.reset();
// Clear all the data from the backing database.
base::RunLoop loop;
context()->DatabaseForTesting()->DeletePrefixed(
leveldb::StringPieceToUint8Vector("map"),
base::BindLambdaForTesting([&](DatabaseError status) {
loop.Quit();
EXPECT_EQ(DatabaseError::OK, status);
}));
loop.Run();
// Now open many new wrappers (for different origins) to trigger clean up.
for (int i = 1; i <= 100; ++i) {
blink::mojom::SessionStorageNamespacePtr ss_namespace1;
context()->OpenSessionStorage(kTestProcessId, namespace_id1,
GetBadMessageCallback(),
mojo::MakeRequest(&ss_namespace1));
blink::mojom::StorageAreaAssociatedPtr leveldb;
ss_namespace1->OpenArea(url::Origin::Create(GURL(base::StringPrintf(
"http://example.com:%d", i))),
mojo::MakeRequest(&leveldb));
base::RunLoop().RunUntilIdle();
ss_namespace1.reset();
leveldb.reset();
}
// And make sure caches were actually cleared.
context()->OpenSessionStorage(kTestProcessId, namespace_id1,
GetBadMessageCallback(),
mojo::MakeRequest(&ss_namespace1));
ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb));
std::vector<blink::mojom::KeyValuePtr> data;
ASSERT_TRUE(test::GetAllSync(leveldb.get(), &data));
EXPECT_EQ(0ul, data.size());
}
TEST_F(SessionStorageContextMojoTest, ClearDiskState) {
SetBackingMode(SessionStorageContextMojo::BackingMode::kClearDiskStateOnOpen);
std::string namespace_id1 = base::GenerateGUID();
url::Origin origin1 = url::Origin::Create(GURL("http://foobar.com"));
context()->CreateSessionNamespace(namespace_id1);
blink::mojom::SessionStorageNamespacePtr ss_namespace1;
context()->OpenSessionStorage(kTestProcessId, namespace_id1,
GetBadMessageCallback(),
mojo::MakeRequest(&ss_namespace1));
blink::mojom::StorageAreaAssociatedPtr leveldb_n1_o1;
ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb_n1_o1));
// Verify no data.
std::vector<blink::mojom::KeyValuePtr> data;
EXPECT_TRUE(test::GetAllSync(leveldb_n1_o1.get(), &data));
EXPECT_EQ(0ul, data.size());
// Put some data.
EXPECT_TRUE(test::PutSync(
leveldb_n1_o1.get(), leveldb::StringPieceToUint8Vector("key1"),
leveldb::StringPieceToUint8Vector("value1"), base::nullopt, "source1"));
// Delete the namespace and shutdown the context, BUT persist the namespace on
// disk.
context()->DeleteSessionNamespace(namespace_id1, true);
ShutdownContext();
// This will re-open the context, and load the persisted namespace, but it
// should have been deleted due to our backing mode.
context()->CreateSessionNamespace(namespace_id1);
context()->OpenSessionStorage(kTestProcessId, namespace_id1,
GetBadMessageCallback(),
mojo::MakeRequest(&ss_namespace1));
ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb_n1_o1));
// The data from before should not be here, because the context clears disk
// space on open.
EXPECT_TRUE(test::GetAllSync(leveldb_n1_o1.get(), &data));
EXPECT_EQ(0ul, data.size());
}
} // namespace
TEST_F(SessionStorageContextMojoTest, PurgeMemoryDoesNotCrashOrHang) {
std::string namespace_id1 = base::GenerateGUID();
std::string namespace_id2 = base::GenerateGUID();
url::Origin origin1 = url::Origin::Create(GURL("http://foobar.com"));
context()->CreateSessionNamespace(namespace_id1);
blink::mojom::SessionStorageNamespacePtr ss_namespace1;
context()->OpenSessionStorage(kTestProcessId, namespace_id1,
GetBadMessageCallback(),
mojo::MakeRequest(&ss_namespace1));
blink::mojom::StorageAreaAssociatedPtr leveldb_n1_o1;
ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb_n1_o1));
context()->CreateSessionNamespace(namespace_id2);
blink::mojom::SessionStorageNamespacePtr ss_namespace2;
context()->OpenSessionStorage(kTestProcessId, namespace_id2,
GetBadMessageCallback(),
mojo::MakeRequest(&ss_namespace2));
blink::mojom::StorageAreaAssociatedPtr leveldb_n2_o1;
ss_namespace2->OpenArea(origin1, mojo::MakeRequest(&leveldb_n2_o1));
// Put some data in both.
EXPECT_TRUE(test::PutSync(
leveldb_n1_o1.get(), leveldb::StringPieceToUint8Vector("key1"),
leveldb::StringPieceToUint8Vector("value1"), base::nullopt, "source1"));
EXPECT_TRUE(test::PutSync(
leveldb_n2_o1.get(), leveldb::StringPieceToUint8Vector("key1"),
leveldb::StringPieceToUint8Vector("value2"), base::nullopt, "source1"));
context()->FlushAreaForTesting(namespace_id1, origin1);
leveldb_n2_o1.reset();
ss_namespace2.reset();
base::RunLoop().RunUntilIdle();
// Verify this doesn't crash or hang.
context()->PurgeMemory();
size_t memory_used = context()
->namespaces_[namespace_id1]
->origin_areas_[origin1]
->data_map()
->storage_area()
->memory_used();
EXPECT_EQ(0ul, memory_used);
// Test the values is still there.
std::vector<blink::mojom::KeyValuePtr> data;
EXPECT_TRUE(test::GetAllSync(leveldb_n1_o1.get(), &data));
EXPECT_EQ(1ul, data.size());
base::Optional<std::vector<uint8_t>> opt_value2 =
DoTestGet(namespace_id2, origin1, "key1");
ASSERT_TRUE(opt_value2);
EXPECT_EQ(leveldb::StringPieceToUint8Vector("value2"), opt_value2.value());
}
} // namespace content