0

Quota: Mojofy WebSQL's QuotaClient.

Bug: 1163048
Change-Id: Ifda6373f4550c7d7083b111c10e19376f841f936
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2628489
Reviewed-by: enne <enne@chromium.org>
Commit-Queue: Victor Costan <pwnall@chromium.org>
Cr-Commit-Position: refs/heads/master@{#889504}
This commit is contained in:
Victor Costan
2021-06-04 23:05:34 +00:00
committed by Chromium LUCI CQ
parent 6b2740923f
commit 93ce33c491
7 changed files with 127 additions and 142 deletions

@ -234,6 +234,7 @@ component("browser") {
"//base/third_party/dynamic_annotations",
"//build:chromeos_buildflags",
"//components/services/storage/public/cpp",
"//components/services/storage/public/cpp/filesystem",
"//mojo/public/cpp/bindings",
"//net",
"//services/network:network_service",

@ -31,80 +31,13 @@ using blink::mojom::StorageType;
namespace storage {
namespace {
int64_t GetOriginUsageOnDBThread(DatabaseTracker* db_tracker,
const url::Origin& origin) {
OriginInfo info;
if (db_tracker->GetOriginInfo(GetIdentifierFromOrigin(origin), &info))
return info.TotalSize();
return 0;
}
std::vector<url::Origin> GetOriginsOnDBThread(DatabaseTracker* db_tracker) {
std::vector<url::Origin> all_origins;
std::vector<std::string> origin_identifiers;
if (db_tracker->GetAllOriginIdentifiers(&origin_identifiers)) {
all_origins.reserve(origin_identifiers.size());
for (const auto& identifier : origin_identifiers) {
all_origins.push_back(GetOriginFromIdentifier(identifier));
}
}
return all_origins;
}
std::vector<url::Origin> GetOriginsForHostOnDBThread(
DatabaseTracker* db_tracker,
const std::string& host) {
std::vector<url::Origin> host_origins;
// In the vast majority of cases, this vector will end up with exactly one
// origin. The origin will be https://host or http://host.
host_origins.reserve(1);
std::vector<std::string> origin_identifiers;
if (db_tracker->GetAllOriginIdentifiers(&origin_identifiers)) {
for (const auto& identifier : origin_identifiers) {
url::Origin origin = GetOriginFromIdentifier(identifier);
if (host == origin.host())
host_origins.push_back(std::move(origin));
}
}
return host_origins;
}
void DidDeleteOriginData(
scoped_refptr<base::SequencedTaskRunner> original_task_runner,
QuotaClient::DeleteOriginDataCallback callback,
int result) {
blink::mojom::QuotaStatusCode status;
if (result == net::OK)
status = blink::mojom::QuotaStatusCode::kOk;
else
status = blink::mojom::QuotaStatusCode::kUnknown;
original_task_runner->PostTask(FROM_HERE,
base::BindOnce(std::move(callback), status));
}
} // namespace
DatabaseQuotaClient::DatabaseQuotaClient(
scoped_refptr<DatabaseTracker> db_tracker)
: db_tracker_(std::move(db_tracker)) {
DCHECK(db_tracker_.get());
DatabaseQuotaClient::DatabaseQuotaClient(DatabaseTracker& db_tracker)
: db_tracker_(db_tracker) {
DETACH_FROM_SEQUENCE(sequence_checker_);
}
DatabaseQuotaClient::~DatabaseQuotaClient() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!db_tracker_->task_runner()->RunsTasksInCurrentSequence()) {
db_tracker_->task_runner()->ReleaseSoon(FROM_HERE, std::move(db_tracker_));
}
}
void DatabaseQuotaClient::OnQuotaManagerDestroyed() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void DatabaseQuotaClient::GetOriginUsage(const url::Origin& origin,
@ -114,11 +47,12 @@ void DatabaseQuotaClient::GetOriginUsage(const url::Origin& origin,
DCHECK(!callback.is_null());
DCHECK_EQ(type, StorageType::kTemporary);
db_tracker_->task_runner()->PostTaskAndReplyWithResult(
FROM_HERE,
base::BindOnce(&GetOriginUsageOnDBThread, base::RetainedRef(db_tracker_),
origin),
std::move(callback));
OriginInfo info;
if (db_tracker_.GetOriginInfo(GetIdentifierFromOrigin(origin), &info)) {
std::move(callback).Run(info.TotalSize());
} else {
std::move(callback).Run(0);
}
}
void DatabaseQuotaClient::GetOriginsForType(
@ -128,10 +62,14 @@ void DatabaseQuotaClient::GetOriginsForType(
DCHECK(!callback.is_null());
DCHECK_EQ(type, StorageType::kTemporary);
db_tracker_->task_runner()->PostTaskAndReplyWithResult(
FROM_HERE,
base::BindOnce(&GetOriginsOnDBThread, base::RetainedRef(db_tracker_)),
std::move(callback));
std::vector<url::Origin> all_origins;
std::vector<std::string> origin_identifiers;
if (db_tracker_.GetAllOriginIdentifiers(&origin_identifiers)) {
all_origins.reserve(origin_identifiers.size());
for (const auto& identifier : origin_identifiers)
all_origins.push_back(GetOriginFromIdentifier(identifier));
}
std::move(callback).Run(all_origins);
}
void DatabaseQuotaClient::GetOriginsForHost(
@ -142,11 +80,20 @@ void DatabaseQuotaClient::GetOriginsForHost(
DCHECK(!callback.is_null());
DCHECK_EQ(type, StorageType::kTemporary);
db_tracker_->task_runner()->PostTaskAndReplyWithResult(
FROM_HERE,
base::BindOnce(&GetOriginsForHostOnDBThread,
base::RetainedRef(db_tracker_), host),
std::move(callback));
std::vector<url::Origin> host_origins;
// In the vast majority of cases, this vector will end up with exactly one
// origin. The origin will be https://host or http://host.
host_origins.reserve(1);
std::vector<std::string> origin_identifiers;
if (db_tracker_.GetAllOriginIdentifiers(&origin_identifiers)) {
for (const auto& identifier : origin_identifiers) {
url::Origin origin = GetOriginFromIdentifier(identifier);
if (host == origin.host())
host_origins.push_back(std::move(origin));
}
}
std::move(callback).Run(host_origins);
}
void DatabaseQuotaClient::DeleteOriginData(const url::Origin& origin,
@ -156,12 +103,15 @@ void DatabaseQuotaClient::DeleteOriginData(const url::Origin& origin,
DCHECK(!callback.is_null());
DCHECK_EQ(type, StorageType::kTemporary);
db_tracker_->task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&DatabaseTracker::DeleteDataForOrigin, db_tracker_, origin,
base::BindOnce(&DidDeleteOriginData,
base::SequencedTaskRunnerHandle::Get(),
std::move(callback))));
db_tracker_.DeleteDataForOrigin(
origin, base::BindOnce(
[](DeleteOriginDataCallback callback, int result) {
std::move(callback).Run(
(result == net::OK)
? blink::mojom::QuotaStatusCode::kOk
: blink::mojom::QuotaStatusCode::kUnknown);
},
std::move(callback)));
}
void DatabaseQuotaClient::PerformStorageCleanup(

@ -14,7 +14,7 @@
#include "base/sequence_checker.h"
#include "base/single_thread_task_runner.h"
#include "base/thread_annotations.h"
#include "storage/browser/quota/quota_client.h"
#include "components/services/storage/public/mojom/quota_client.mojom.h"
#include "storage/browser/quota/quota_client_type.h"
#include "third_party/blink/public/mojom/quota/quota_types.mojom.h"
#include "url/origin.h"
@ -27,15 +27,16 @@ class DatabaseTracker;
//
// This interface is used on the IO thread by the quota manager.
class COMPONENT_EXPORT(STORAGE_BROWSER) DatabaseQuotaClient
: public QuotaClient {
: public mojom::QuotaClient {
public:
explicit DatabaseQuotaClient(scoped_refptr<DatabaseTracker> tracker);
explicit DatabaseQuotaClient(DatabaseTracker& tracker);
DatabaseQuotaClient(const DatabaseQuotaClient&) = delete;
DatabaseQuotaClient& operator=(const DatabaseQuotaClient&) = delete;
~DatabaseQuotaClient() override;
// QuotaClient method overrides
void OnQuotaManagerDestroyed() override;
void GetOriginUsage(const url::Origin& origin,
blink::mojom::StorageType type,
GetOriginUsageCallback callback) override;
@ -51,15 +52,10 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) DatabaseQuotaClient
PerformStorageCleanupCallback callback) override;
private:
~DatabaseQuotaClient() override;
SEQUENCE_CHECKER(sequence_checker_);
// The scoped_refptr is only be dereferenced on the QuotaClient's sequence.
// However, the DatabaseTracker it points to must only be used on the database
// sequence.
scoped_refptr<DatabaseTracker> db_tracker_
GUARDED_BY_CONTEXT(sequence_checker_);
// Reference use is safe here because the DatabaseTracker owns this.
DatabaseTracker& db_tracker_ GUARDED_BY_CONTEXT(sequence_checker_);
};
} // namespace storage

@ -21,6 +21,7 @@
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "components/services/storage/public/mojom/quota_client.mojom.h"
#include "net/base/completion_once_callback.h"
#include "net/base/net_errors.h"
#include "storage/browser/database/database_quota_client.h"
@ -147,7 +148,7 @@ class DatabaseQuotaClientTest : public testing::Test {
run_loop.Run();
}
static int64_t GetOriginUsage(QuotaClient& client,
static int64_t GetOriginUsage(mojom::QuotaClient& client,
const url::Origin& origin,
blink::mojom::StorageType type) {
int result = -1;
@ -163,7 +164,7 @@ class DatabaseQuotaClientTest : public testing::Test {
}
static std::vector<url::Origin> GetOriginsForType(
QuotaClient& client,
mojom::QuotaClient& client,
blink::mojom::StorageType type) {
std::vector<url::Origin> result;
base::RunLoop loop;
@ -178,7 +179,7 @@ class DatabaseQuotaClientTest : public testing::Test {
}
static std::vector<url::Origin> GetOriginsForHost(
QuotaClient& client,
mojom::QuotaClient& client,
blink::mojom::StorageType type,
const std::string& host) {
std::vector<url::Origin> result;
@ -194,7 +195,7 @@ class DatabaseQuotaClientTest : public testing::Test {
}
static blink::mojom::QuotaStatusCode DeleteOriginData(
QuotaClient& client,
mojom::QuotaClient& client,
blink::mojom::StorageType type,
const url::Origin& origin) {
blink::mojom::QuotaStatusCode result =
@ -216,62 +217,62 @@ class DatabaseQuotaClientTest : public testing::Test {
};
TEST_F(DatabaseQuotaClientTest, GetOriginUsage) {
auto client = base::MakeRefCounted<DatabaseQuotaClient>(mock_tracker_);
DatabaseQuotaClient client(*mock_tracker_);
EXPECT_EQ(0, GetOriginUsage(*client, kOriginA, kTemp));
EXPECT_EQ(0, GetOriginUsage(client, kOriginA, kTemp));
mock_tracker_->AddMockDatabase(kOriginA, "fooDB", 1000);
EXPECT_EQ(1000, GetOriginUsage(*client, kOriginA, kTemp));
EXPECT_EQ(1000, GetOriginUsage(client, kOriginA, kTemp));
EXPECT_EQ(0, GetOriginUsage(*client, kOriginB, kTemp));
EXPECT_EQ(0, GetOriginUsage(client, kOriginB, kTemp));
}
TEST_F(DatabaseQuotaClientTest, GetOriginsForHost) {
auto client = base::MakeRefCounted<DatabaseQuotaClient>(mock_tracker_);
DatabaseQuotaClient client(*mock_tracker_);
EXPECT_EQ(kOriginA.host(), kOriginB.host());
EXPECT_NE(kOriginA.host(), kOriginOther.host());
std::vector<url::Origin> origins =
GetOriginsForHost(*client, kTemp, kOriginA.host());
GetOriginsForHost(client, kTemp, kOriginA.host());
EXPECT_TRUE(origins.empty());
mock_tracker_->AddMockDatabase(kOriginA, "fooDB", 1000);
origins = GetOriginsForHost(*client, kTemp, kOriginA.host());
origins = GetOriginsForHost(client, kTemp, kOriginA.host());
EXPECT_EQ(origins.size(), 1ul);
EXPECT_THAT(origins, testing::Contains(kOriginA));
mock_tracker_->AddMockDatabase(kOriginB, "barDB", 1000);
origins = GetOriginsForHost(*client, kTemp, kOriginA.host());
origins = GetOriginsForHost(client, kTemp, kOriginA.host());
EXPECT_EQ(origins.size(), 2ul);
EXPECT_THAT(origins, testing::Contains(kOriginA));
EXPECT_THAT(origins, testing::Contains(kOriginB));
EXPECT_TRUE(GetOriginsForHost(*client, kTemp, kOriginOther.host()).empty());
EXPECT_TRUE(GetOriginsForHost(client, kTemp, kOriginOther.host()).empty());
}
TEST_F(DatabaseQuotaClientTest, GetOriginsForType) {
auto client = base::MakeRefCounted<DatabaseQuotaClient>(mock_tracker_);
DatabaseQuotaClient client(*mock_tracker_);
EXPECT_TRUE(GetOriginsForType(*client, kTemp).empty());
EXPECT_TRUE(GetOriginsForType(client, kTemp).empty());
mock_tracker_->AddMockDatabase(kOriginA, "fooDB", 1000);
std::vector<url::Origin> origins = GetOriginsForType(*client, kTemp);
std::vector<url::Origin> origins = GetOriginsForType(client, kTemp);
EXPECT_EQ(origins.size(), 1ul);
EXPECT_THAT(origins, testing::Contains(kOriginA));
}
TEST_F(DatabaseQuotaClientTest, DeleteOriginData) {
auto client = base::MakeRefCounted<DatabaseQuotaClient>(mock_tracker_);
DatabaseQuotaClient client(*mock_tracker_);
mock_tracker_->set_async_delete(false);
EXPECT_EQ(blink::mojom::QuotaStatusCode::kOk,
DeleteOriginData(*client, kTemp, kOriginA));
DeleteOriginData(client, kTemp, kOriginA));
EXPECT_EQ(1, mock_tracker_->delete_called_count());
mock_tracker_->set_async_delete(true);
EXPECT_EQ(blink::mojom::QuotaStatusCode::kOk,
DeleteOriginData(*client, kTemp, kOriginA));
DeleteOriginData(client, kTemp, kOriginA));
EXPECT_EQ(2, mock_tracker_->delete_called_count());
}

@ -17,6 +17,7 @@
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/location.h"
#include "base/memory/scoped_refptr.h"
#include "base/metrics/user_metrics.h"
#include "base/strings/string_number_conversions.h"
@ -25,6 +26,10 @@
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "base/types/pass_key.h"
#include "components/services/storage/public/cpp/quota_client_callback_wrapper.h"
#include "components/services/storage/public/mojom/quota_client.mojom.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "net/base/net_errors.h"
#include "sql/database.h"
#include "sql/meta_table.h"
@ -112,22 +117,44 @@ DatabaseTracker::DatabaseTracker(
quota_manager_proxy_(std::move(quota_manager_proxy)),
task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
{base::MayBlock(), base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN})) {}
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN})),
quota_client_(std::make_unique<DatabaseQuotaClient>(*this)),
quota_client_wrapper_(
std::make_unique<QuotaClientCallbackWrapper>(quota_client_.get())),
quota_client_receiver_(quota_client_wrapper_.get()) {}
DatabaseTracker::~DatabaseTracker() {
// base::RefCountedThreadSafe inserts the appropriate barriers to ensure
// member access in the destructor does not introduce data races.
DCHECK(dbs_to_be_deleted_.empty());
DCHECK(deletion_callbacks_.empty());
DCHECK(!quota_client_);
DCHECK(!quota_client_wrapper_);
DCHECK(!quota_client_receiver_.is_bound());
}
void DatabaseTracker::RegisterQuotaClient() {
if (quota_manager_proxy_) {
// TODO(crbug.com/1163048): Use mojo and switch to RegisterClient().
quota_manager_proxy_->RegisterLegacyClient(
base::MakeRefCounted<DatabaseQuotaClient>(this),
QuotaClientType::kDatabase, {blink::mojom::StorageType::kTemporary});
}
if (!quota_manager_proxy_)
return;
// QuotaManagerProxy::RegisterClient() must be called synchronously during
// DatabaseTracker creation until crbug.com/1182630 is fixed.
mojo::PendingRemote<storage::mojom::QuotaClient> quota_client_remote;
mojo::PendingReceiver<storage::mojom::QuotaClient> quota_client_receiver =
quota_client_remote.InitWithNewPipeAndPassReceiver();
quota_manager_proxy_->RegisterClient(std::move(quota_client_remote),
storage::QuotaClientType::kDatabase,
{blink::mojom::StorageType::kTemporary});
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
[](scoped_refptr<DatabaseTracker> self,
mojo::PendingReceiver<storage::mojom::QuotaClient> receiver) {
self->quota_client_receiver_.Bind(std::move(receiver));
},
base::RetainedRef(this), std::move(quota_client_receiver)));
}
void DatabaseTracker::DatabaseOpened(const std::string& origin_identifier,
@ -924,6 +951,13 @@ void DatabaseTracker::Shutdown() {
return;
}
shutting_down_ = true;
// The mojo receiver must be reset before the instance it calls into is
// destroyed.
quota_client_receiver_.reset();
quota_client_wrapper_.reset();
quota_client_.reset();
if (is_incognito_)
DeleteIncognitoDBDirectory();
else if (!force_keep_session_state_)

@ -25,6 +25,8 @@
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "base/types/pass_key.h"
#include "components/services/storage/public/mojom/quota_client.mojom.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "net/base/completion_once_callback.h"
#include "storage/browser/database/database_connections.h"
#include "url/origin.h"
@ -36,6 +38,8 @@ class MetaTable;
namespace storage {
class DatabaseQuotaClient;
class QuotaClientCallbackWrapper;
class QuotaManagerProxy;
class SpecialStoragePolicy;
@ -329,6 +333,9 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) DatabaseTracker
// Sequence where file I/O is allowed.
const scoped_refptr<base::SequencedTaskRunner> task_runner_;
std::unique_ptr<DatabaseQuotaClient> quota_client_;
std::unique_ptr<storage::QuotaClientCallbackWrapper> quota_client_wrapper_;
// When in Incognito mode, store a DELETE_ON_CLOSE handle to each
// main DB and journal file that was accessed. When the Incognito profile
// goes away (or when the browser crashes), all these handles will be
@ -344,6 +351,8 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) DatabaseTracker
std::map<std::string, std::u16string> incognito_origin_directories_;
int incognito_origin_directories_generator_ = 0;
mojo::Receiver<mojom::QuotaClient> quota_client_receiver_;
FRIEND_TEST_ALL_PREFIXES(DatabaseTracker, TestHelper);
};

@ -16,6 +16,7 @@
#include "base/memory/scoped_refptr.h"
#include "base/notreached.h"
#include "base/run_loop.h"
#include "base/sequenced_task_runner.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "base/threading/sequenced_task_runner_handle.h"
@ -95,25 +96,27 @@ void CheckNotificationReceived(TestObserver* observer,
EXPECT_EQ(expected_database_size, observer->GetNotificationDatabaseSize());
}
// Must be destroyed on the sequence that called RegisterClient() most recently.
class TestQuotaManagerProxy : public QuotaManagerProxy {
public:
TestQuotaManagerProxy()
: QuotaManagerProxy(nullptr, base::SequencedTaskRunnerHandle::Get()),
registered_client_(nullptr) {}
: QuotaManagerProxy(
/*quota_manager_impl=*/nullptr,
base::SequencedTaskRunnerHandle::Get()) {}
void RegisterLegacyClient(
scoped_refptr<QuotaClient> client,
QuotaClientType client_type,
const std::vector<blink::mojom::StorageType>& storage_types) override {
EXPECT_FALSE(registered_client_);
registered_client_ = client;
NOTREACHED();
}
void RegisterClient(
mojo::PendingRemote<mojom::QuotaClient> client,
QuotaClientType client_type,
const std::vector<blink::mojom::StorageType>& storage_types) override {
NOTREACHED();
EXPECT_FALSE(registered_client_);
registered_client_.Bind(std::move(client));
}
void NotifyStorageAccessed(const url::Origin& origin,
@ -152,13 +155,6 @@ class TestQuotaManagerProxy : public QuotaManagerProxy {
scoped_refptr<base::SequencedTaskRunner> callback_task_runner,
UsageAndQuotaCallback callback) override {}
void SimulateQuotaManagerDestroyed() {
if (registered_client_) {
registered_client_->OnQuotaManagerDestroyed();
registered_client_ = nullptr;
}
}
bool WasAccessNotified(const url::Origin& origin) {
return accesses_[origin] != 0;
}
@ -173,7 +169,7 @@ class TestQuotaManagerProxy : public QuotaManagerProxy {
modifications_.clear();
}
scoped_refptr<QuotaClient> registered_client_;
mojo::Remote<mojom::QuotaClient> registered_client_;
// Map from origin to count of access notifications.
std::map<url::Origin, int> accesses_;
@ -182,7 +178,7 @@ class TestQuotaManagerProxy : public QuotaManagerProxy {
std::map<url::Origin, std::pair<int, int64_t>> modifications_;
protected:
~TestQuotaManagerProxy() override { EXPECT_FALSE(registered_client_); }
~TestQuotaManagerProxy() override = default;
};
bool EnsureFileOfSize(const base::FilePath& file_path, int64_t length) {
@ -575,8 +571,6 @@ class DatabaseTracker_TestHelper_Test {
// Cleanup.
crashed_renderer_connections.RemoveAllConnections();
test_quota_proxy->SimulateQuotaManagerDestroyed();
tracker->Shutdown();
}));
run_loop.Run();