Commit 9da03b73 authored by David Maunder's avatar David Maunder Committed by Commit Bot

Implement Level DB Database for

NonCriticalPersistedTabData.

NonCriticalPersistedTabData is for metadata
to build new features where the data needs
to be persisted across restarts. However,
the metadata is not pertinent to the running
of the app as CriticalPersistedTabData
(tab id, web contents etc.) is.

Bug: 1048712
Change-Id: Ibcaa202322746d69065546fc3f7ea1d66b443ca9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2036656Reviewed-by: default avatarTommy Nyquist <nyquist@chromium.org>
Reviewed-by: default avatarDavid Trainor <dtrainor@chromium.org>
Commit-Queue: David Maunder <davidjm@chromium.org>
Cr-Commit-Position: refs/heads/master@{#753458}
parent 427961c5
......@@ -1825,6 +1825,8 @@ jumbo_static_library("browser") {
"sync/trusted_vault_client_android.h",
"sync/user_event_service_factory.cc",
"sync/user_event_service_factory.h",
"tab/state/tab_state_db.cc",
"tab/state/tab_state_db.h",
"tab_contents/navigation_metrics_recorder.cc",
"tab_contents/navigation_metrics_recorder.h",
"tab_contents/tab_util.cc",
......@@ -1981,6 +1983,7 @@ jumbo_static_library("browser") {
":ntp_background_proto",
":permissions_proto",
":resource_prefetch_predictor_proto",
":tab_state_db_content_proto",
"//base:i18n",
"//base/allocator:buildflags",
"//base/util/memory_pressure:memory_pressure",
......@@ -5568,6 +5571,10 @@ proto_library("availability_protos") {
sources = [ "availability/proto/availability_prober_cache_entry.proto" ]
}
proto_library("tab_state_db_content_proto") {
sources = [ "tab/state/tab_state_db_content.proto" ]
}
proto_library("resource_prefetch_predictor_proto") {
sources = [ "predictors/resource_prefetch_predictor.proto" ]
}
......
include_rules = [
"+components/leveldb_proto",
]
// Copyright 2020 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 "chrome/browser/tab/state/tab_state_db.h"
#include "base/bind.h"
#include "base/files/file_path.h"
#include "base/strings/string_util.h"
#include "base/task/post_task.h"
#include "base/threading/thread_task_runner_handle.h"
#include "components/leveldb_proto/public/proto_database_provider.h"
namespace {
const char kTabStateDBFolder[] = "tab_state_db";
leveldb::ReadOptions CreateReadOptions() {
leveldb::ReadOptions opts;
opts.fill_cache = false;
return opts;
}
bool DatabasePrefixFilter(const std::string& key_prefix,
const std::string& key) {
return base::StartsWith(key, key_prefix, base::CompareCase::SENSITIVE);
}
} // namespace
TabStateDB::TabStateDB(
leveldb_proto::ProtoDatabaseProvider* proto_database_provider,
const base::FilePath& profile_directory,
base::OnceClosure closure)
: database_status_(leveldb_proto::Enums::InitStatus::kNotInitialized),
storage_database_(
proto_database_provider->GetDB<tab_state_db::TabStateContentProto>(
leveldb_proto::ProtoDbType::TAB_STATE_DATABASE,
profile_directory.AppendASCII(kTabStateDBFolder),
base::CreateSequencedTaskRunner(
{base::ThreadPool(), base::MayBlock(),
base::TaskPriority::USER_VISIBLE}))) {
storage_database_->Init(base::BindOnce(&TabStateDB::OnDatabaseInitialized,
weak_ptr_factory_.GetWeakPtr(),
std::move(closure)));
}
TabStateDB::~TabStateDB() = default;
bool TabStateDB::IsInitialized() const {
return database_status_ == leveldb_proto::Enums::InitStatus::kOK;
}
void TabStateDB::LoadContent(const std::string& key, LoadCallback callback) {
storage_database_->LoadEntriesWithFilter(
base::BindRepeating(&DatabasePrefixFilter, key), CreateReadOptions(),
/* target_prefix */ "",
base::BindOnce(&TabStateDB::OnLoadContent, weak_ptr_factory_.GetWeakPtr(),
std::move(callback)));
}
void TabStateDB::InsertContent(const std::string& key,
const std::vector<uint8_t>& value,
OperationCallback callback) {
auto contents_to_save = std::make_unique<ContentEntry>();
tab_state_db::TabStateContentProto proto;
proto.set_key(key);
proto.set_content_data(value.data(), value.size());
contents_to_save->emplace_back(proto.key(), std::move(proto));
storage_database_->UpdateEntries(
std::move(contents_to_save), std::make_unique<std::vector<std::string>>(),
base::BindOnce(&TabStateDB::OnOperationCommitted,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void TabStateDB::DeleteContent(const std::string& key,
OperationCallback callback) {
storage_database_->UpdateEntriesWithRemoveFilter(
std::make_unique<ContentEntry>(),
std::move(base::BindRepeating(&DatabasePrefixFilter, std::move(key))),
base::BindOnce(&TabStateDB::OnOperationCommitted,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void TabStateDB::DeleteAllContent(OperationCallback callback) {
storage_database_->Destroy(std::move(callback));
}
TabStateDB::TabStateDB(
std::unique_ptr<leveldb_proto::ProtoDatabase<
tab_state_db::TabStateContentProto>> storage_database,
scoped_refptr<base::SequencedTaskRunner> task_runner,
base::OnceClosure closure)
: database_status_(leveldb_proto::Enums::InitStatus::kNotInitialized),
storage_database_(std::move(storage_database)) {
storage_database_->Init(base::BindOnce(&TabStateDB::OnDatabaseInitialized,
weak_ptr_factory_.GetWeakPtr(),
std::move(closure)));
}
void TabStateDB::OnDatabaseInitialized(
base::OnceClosure closure,
leveldb_proto::Enums::InitStatus status) {
DCHECK_EQ(database_status_,
leveldb_proto::Enums::InitStatus::kNotInitialized);
database_status_ = status;
std::move(closure).Run();
}
void TabStateDB::OnLoadContent(
LoadCallback callback,
bool success,
std::unique_ptr<std::vector<tab_state_db::TabStateContentProto>> content) {
std::vector<KeyAndValue> results;
if (success) {
for (const auto& proto : *content) {
DCHECK(proto.has_key());
DCHECK(proto.has_content_data());
results.emplace_back(proto.key(),
std::vector<uint8_t>(proto.content_data().begin(),
proto.content_data().end()));
}
}
std::move(callback).Run(success, std::move(results));
}
void TabStateDB::OnOperationCommitted(OperationCallback callback,
bool success) {
std::move(callback).Run(success);
}
// Copyright 2020 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.
#ifndef CHROME_BROWSER_TAB_STATE_TAB_STATE_DB_H_
#define CHROME_BROWSER_TAB_STATE_TAB_STATE_DB_H_
#include <string>
#include <vector>
#include "base/memory/weak_ptr.h"
#include "base/sequenced_task_runner.h"
#include "chrome/browser/tab/state/tab_state_db_content.pb.h"
#include "components/leveldb_proto/public/proto_database.h"
namespace leveldb_proto {
class ProtoDatabaseProvider;
} // namespace leveldb_proto
class TabStateDBTest;
// TabStateDatabase is leveldb backend store for NonCriticalPersistedTabData.
// NonCriticalPersistedTabData is an extension of TabState where data for
// new features which are not critical to the core functionality of the app
// are acquired and persisted across restarts. The intended key format is
// <NonCriticalPersistedTabData id>_<Tab id>
// NonCriticalPersistedTabData is stored in key/value pairs.
// TODO(crbug.com/1061258) add a KeyedService so TabStateDB is per profile
class TabStateDB {
public:
using KeyAndValue = std::pair<std::string, std::vector<uint8_t>>;
// Callback when content is acquired
using LoadCallback = base::OnceCallback<void(bool, std::vector<KeyAndValue>)>;
// Used for confirming an operation was completed successfully (e.g.
// insert, delete). This will be invoked on a different SequenceRunner
// to TabStateDB.
using OperationCallback = base::OnceCallback<void(bool)>;
// Entry in the database
using ContentEntry = leveldb_proto::ProtoDatabase<
tab_state_db::TabStateContentProto>::KeyEntryVector;
// Initializes the database
TabStateDB(leveldb_proto::ProtoDatabaseProvider* proto_database_provider,
const base::FilePath& profile_directory,
base::OnceClosure closure);
~TabStateDB();
// Returns true if initialization has finished successfully, otherwise false.
bool IsInitialized() const;
// Loads the content data for the key and passes them to the callback
void LoadContent(const std::string& key, LoadCallback callback);
// Inserts a value for a given key and passes the result (success/failure) to
// OperationCallback
void InsertContent(const std::string& key,
const std::vector<uint8_t>& value,
OperationCallback callback);
// Deletes content in the database, matching all keys which have a prefix
// that matches the key
void DeleteContent(const std::string& key, OperationCallback callback);
// Delete all content in the database
void DeleteAllContent(OperationCallback callback);
private:
friend class ::TabStateDBTest;
// Used for tests
explicit TabStateDB(std::unique_ptr<leveldb_proto::ProtoDatabase<
tab_state_db::TabStateContentProto>> storage_database,
scoped_refptr<base::SequencedTaskRunner> task_runner,
base::OnceClosure closure);
// Passes back database status following database initialization
void OnDatabaseInitialized(base::OnceClosure closure,
leveldb_proto::Enums::InitStatus status);
// Callback when content is loaded
void OnLoadContent(
LoadCallback callback,
bool success,
std::unique_ptr<std::vector<tab_state_db::TabStateContentProto>> content);
// Callback when an operation (e.g. insert or delete) is called
void OnOperationCommitted(OperationCallback callback, bool success);
// Status of the database initialization.
leveldb_proto::Enums::InitStatus database_status_;
// The database for storing content storage information.
std::unique_ptr<
leveldb_proto::ProtoDatabase<tab_state_db::TabStateContentProto>>
storage_database_;
base::WeakPtrFactory<TabStateDB> weak_ptr_factory_{this};
DISALLOW_COPY_AND_ASSIGN(TabStateDB);
};
#endif // CHROME_BROWSER_TAB_STATE_TAB_STATE_DB_H_
// Copyright 2020 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.
syntax = "proto2";
option optimize_for = LITE_RUNTIME;
package tab_state_db;
// Used for storing TabState Content.
message TabStateContentProto {
// Original key for data.
optional string key = 1;
// Content data.
optional bytes content_data = 2;
}
// Copyright 2020 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 "chrome/browser/tab/state/tab_state_db.h"
#include <map>
#include "base/bind.h"
#include "base/memory/ptr_util.h"
#include "base/test/task_environment.h"
#include "components/leveldb_proto/testing/fake_db.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using testing::_;
namespace {
const char kMockKey[] = "key";
const char kMockKeyPrefix[] = "k";
const std::vector<uint8_t> kMockValue = {0xfa, 0x5b, 0x4c, 0x12};
} // namespace
class TabStateDBTest : public testing::Test {
public:
TabStateDBTest() : content_db_(nullptr) {}
// Initialize the test database
void InitDatabase() {
auto storage_db = std::make_unique<
leveldb_proto::test::FakeDB<tab_state_db::TabStateContentProto>>(
&content_db_storage_);
content_db_ = storage_db.get();
base::RunLoop run_loop;
tab_state_db_ = base::WrapUnique(new TabStateDB(
std::move(storage_db),
base::CreateSequencedTaskRunner({base::ThreadPool(), base::MayBlock(),
base::TaskPriority::USER_VISIBLE}),
run_loop.QuitClosure()));
MockInitCallback(content_db_, leveldb_proto::Enums::InitStatus::kOK);
run_loop.Run();
}
// Wait for all tasks to be cleared off the queue
void RunUntilIdle() { task_environment_.RunUntilIdle(); }
void MockInitCallback(leveldb_proto::test::FakeDB<
tab_state_db::TabStateContentProto>* storage_db,
leveldb_proto::Enums::InitStatus status) {
storage_db->InitStatusCallback(status);
RunUntilIdle();
}
void MockInsertCallback(leveldb_proto::test::FakeDB<
tab_state_db::TabStateContentProto>* storage_db,
bool result) {
storage_db->UpdateCallback(result);
RunUntilIdle();
}
void MockLoadCallback(leveldb_proto::test::FakeDB<
tab_state_db::TabStateContentProto>* storage_db,
bool res) {
storage_db->LoadCallback(res);
RunUntilIdle();
}
void MockDeleteCallback(leveldb_proto::test::FakeDB<
tab_state_db::TabStateContentProto>* storage_db,
bool res) {
storage_db->UpdateCallback(res);
RunUntilIdle();
}
void OperationEvaluation(base::OnceClosure closure,
bool expected_success,
bool actual_success) {
EXPECT_EQ(expected_success, actual_success);
std::move(closure).Run();
}
void GetEvaluation(base::OnceClosure closure,
std::vector<TabStateDB::KeyAndValue> expected,
bool result,
std::vector<TabStateDB::KeyAndValue> found) {
for (size_t i = 0; i < expected.size(); i++) {
EXPECT_EQ(found[i].first, expected[i].first);
EXPECT_EQ(found[i].second, expected[i].second);
}
std::move(closure).Run();
}
TabStateDB* tab_state_db() { return tab_state_db_.get(); }
leveldb_proto::test::FakeDB<tab_state_db::TabStateContentProto>*
content_db() {
return content_db_;
}
private:
base::test::TaskEnvironment task_environment_;
std::map<std::string, tab_state_db::TabStateContentProto> content_db_storage_;
leveldb_proto::test::FakeDB<tab_state_db::TabStateContentProto>* content_db_;
std::unique_ptr<TabStateDB> tab_state_db_;
DISALLOW_COPY_AND_ASSIGN(TabStateDBTest);
};
TEST_F(TabStateDBTest, TestInit) {
InitDatabase();
EXPECT_EQ(true, tab_state_db()->IsInitialized());
}
TEST_F(TabStateDBTest, TestKeyInsertionSucceeded) {
InitDatabase();
base::RunLoop run_loop[2];
tab_state_db()->InsertContent(
kMockKey, kMockValue,
base::BindOnce(&TabStateDBTest::OperationEvaluation,
base::Unretained(this), run_loop[0].QuitClosure(), true));
MockInsertCallback(content_db(), true);
run_loop[0].Run();
std::vector<TabStateDB::KeyAndValue> expected;
expected.emplace_back(kMockKey, kMockValue);
tab_state_db()->LoadContent(
kMockKey,
base::BindOnce(&TabStateDBTest::GetEvaluation, base::Unretained(this),
run_loop[1].QuitClosure(), expected));
MockLoadCallback(content_db(), true);
run_loop[1].Run();
}
TEST_F(TabStateDBTest, TestKeyInsertionFailed) {
InitDatabase();
base::RunLoop run_loop[2];
tab_state_db()->InsertContent(
kMockKey, kMockValue,
base::BindOnce(&TabStateDBTest::OperationEvaluation,
base::Unretained(this), run_loop[0].QuitClosure(), false));
MockInsertCallback(content_db(), false);
run_loop[0].Run();
std::vector<TabStateDB::KeyAndValue> expected;
tab_state_db()->LoadContent(
kMockKey,
base::BindOnce(&TabStateDBTest::GetEvaluation, base::Unretained(this),
run_loop[1].QuitClosure(), expected));
MockLoadCallback(content_db(), true);
run_loop[1].Run();
}
TEST_F(TabStateDBTest, TestKeyInsertionPrefix) {
InitDatabase();
base::RunLoop run_loop[2];
tab_state_db()->InsertContent(
kMockKey, kMockValue,
base::BindOnce(&TabStateDBTest::OperationEvaluation,
base::Unretained(this), run_loop[0].QuitClosure(), true));
MockInsertCallback(content_db(), true);
run_loop[0].Run();
std::vector<TabStateDB::KeyAndValue> expected;
expected.emplace_back(kMockKey, kMockValue);
tab_state_db()->LoadContent(
kMockKeyPrefix,
base::BindOnce(&TabStateDBTest::GetEvaluation, base::Unretained(this),
run_loop[1].QuitClosure(), expected));
MockLoadCallback(content_db(), true);
run_loop[1].Run();
}
TEST_F(TabStateDBTest, TestDelete) {
InitDatabase();
base::RunLoop run_loop[4];
tab_state_db()->InsertContent(
kMockKey, kMockValue,
base::BindOnce(&TabStateDBTest::OperationEvaluation,
base::Unretained(this), run_loop[0].QuitClosure(), true));
MockInsertCallback(content_db(), true);
run_loop[0].Run();
std::vector<TabStateDB::KeyAndValue> expected;
expected.emplace_back(kMockKey, kMockValue);
tab_state_db()->LoadContent(
kMockKey,
base::BindOnce(&TabStateDBTest::GetEvaluation, base::Unretained(this),
run_loop[1].QuitClosure(), expected));
MockLoadCallback(content_db(), true);
run_loop[1].Run();
tab_state_db()->DeleteContent(
kMockKey,
base::BindOnce(&TabStateDBTest::OperationEvaluation,
base::Unretained(this), run_loop[2].QuitClosure(), true));
MockDeleteCallback(content_db(), true);
run_loop[2].Run();
std::vector<TabStateDB::KeyAndValue> expected_after_delete;
tab_state_db()->LoadContent(
kMockKey,
base::BindOnce(&TabStateDBTest::GetEvaluation, base::Unretained(this),
run_loop[3].QuitClosure(), expected_after_delete));
MockLoadCallback(content_db(), true);
run_loop[3].Run();
}
......@@ -3341,6 +3341,7 @@ test("unit_tests") {
"../browser/resource_coordinator/tab_load_tracker_unittest.cc",
"../browser/resources_util_unittest.cc",
"../browser/search/contextual_search_policy_handler_android_unittest.cc",
"../browser/tab/state/tab_state_db_unittest.cc",
# TODO(hashimoto): those tests should be componentized and moved to
# //components:components_unittests, http://crbug.com/527882.
......
......@@ -79,6 +79,8 @@ std::string SharedProtoDatabaseClientList::ProtoDbTypeToString(
return "PrintJobDatabase";
case ProtoDbType::FEED_STREAM_DATABASE:
return "FeedStreamDatabase";
case ProtoDbType::TAB_STATE_DATABASE:
return "TabStateDatabase";
case ProtoDbType::LAST:
NOTREACHED();
return std::string();
......
......@@ -49,6 +49,7 @@ enum class ProtoDbType {
// DB Used by shared database, will always be unique.
SHARED_DB_METADATA = 25,
FEED_STREAM_DATABASE = 26,
TAB_STATE_DATABASE = 27,
LAST,
};
......@@ -60,6 +61,7 @@ constexpr ProtoDbType kWhitelistedDbForSharedImpl[]{
ProtoDbType::NOTIFICATION_SCHEDULER_NOTIFICATION_STORE,
ProtoDbType::PRINT_JOB_DATABASE,
ProtoDbType::FEED_STREAM_DATABASE,
ProtoDbType::TAB_STATE_DATABASE,
ProtoDbType::LAST, // Marks the end of list.
};
......
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