blob: f36e66667858d0aae1fd2f16310856ba1f4f1356 [file] [log] [blame]
// Copyright (c) 2012 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/indexed_db/indexed_db_dispatcher_host.h"
#include <utility>
#include "base/bind.h"
#include "base/files/file_path.h"
#include "base/guid.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/process/process.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/task_runner.h"
#include "base/task/thread_pool.h"
#include "build/build_config.h"
#include "components/services/storage/filesystem_proxy_factory.h"
#include "content/browser/indexed_db/cursor_impl.h"
#include "content/browser/indexed_db/file_stream_reader_to_data_pipe.h"
#include "content/browser/indexed_db/indexed_db_callbacks.h"
#include "content/browser/indexed_db/indexed_db_connection.h"
#include "content/browser/indexed_db/indexed_db_context_impl.h"
#include "content/browser/indexed_db/indexed_db_cursor.h"
#include "content/browser/indexed_db/indexed_db_database_callbacks.h"
#include "content/browser/indexed_db/indexed_db_factory_impl.h"
#include "content/browser/indexed_db/indexed_db_pending_connection.h"
#include "content/browser/indexed_db/indexed_db_tracing.h"
#include "content/browser/indexed_db/transaction_impl.h"
#include "mojo/public/cpp/bindings/receiver_set.h"
#include "net/base/net_errors.h"
#include "storage/browser/blob/blob_impl.h"
#include "storage/browser/database/database_util.h"
#include "storage/browser/file_system/file_stream_reader.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
namespace content {
namespace {
blink::mojom::IDBStatus GetIndexedDBStatus(leveldb::Status status) {
if (status.ok())
return blink::mojom::IDBStatus::OK;
else if (status.IsNotFound())
return blink::mojom::IDBStatus::NotFound;
else if (status.IsCorruption())
return blink::mojom::IDBStatus::Corruption;
else if (status.IsNotSupportedError())
return blink::mojom::IDBStatus::NotSupported;
else if (status.IsInvalidArgument())
return blink::mojom::IDBStatus::InvalidArgument;
else
return blink::mojom::IDBStatus::IOError;
}
void CallCompactionStatusCallbackOnIDBThread(
IndexedDBDispatcherHost::AbortTransactionsAndCompactDatabaseCallback
mojo_callback,
leveldb::Status status) {
std::move(mojo_callback).Run(GetIndexedDBStatus(status));
}
void CallAbortStatusCallbackOnIDBThread(
IndexedDBDispatcherHost::AbortTransactionsForDatabaseCallback mojo_callback,
leveldb::Status status) {
std::move(mojo_callback).Run(GetIndexedDBStatus(status));
}
} // namespace
// BlobDataItemReader implementation providing a BlobDataItem -> file adapter.
class IndexedDBDataItemReader : public storage::mojom::BlobDataItemReader {
public:
IndexedDBDataItemReader(
IndexedDBDispatcherHost* host,
const base::FilePath& file_path,
base::Time expected_modification_time,
base::RepeatingClosure release_callback,
scoped_refptr<base::TaskRunner> file_task_runner,
scoped_refptr<base::TaskRunner> io_task_runner,
mojo::PendingReceiver<storage::mojom::BlobDataItemReader>
initial_receiver)
: host_(host),
file_path_(file_path),
expected_modification_time_(std::move(expected_modification_time)),
release_callback_(std::move(release_callback)),
file_task_runner_(std::move(file_task_runner)),
io_task_runner_(std::move(io_task_runner)) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(host);
DCHECK(file_task_runner_);
AddReader(std::move(initial_receiver));
// Unretained(this) is safe because |this| owns |receivers_|.
receivers_.set_disconnect_handler(
base::BindRepeating(&IndexedDBDataItemReader::OnClientDisconnected,
base::Unretained(this)));
}
IndexedDBDataItemReader(const IndexedDBDataItemReader&) = delete;
IndexedDBDataItemReader& operator=(const IndexedDBDataItemReader&) = delete;
~IndexedDBDataItemReader() override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
release_callback_.Run();
}
void AddReader(mojo::PendingReceiver<BlobDataItemReader> receiver) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(receiver.is_valid());
receivers_.Add(this, std::move(receiver));
}
void Read(uint64_t offset,
uint64_t length,
mojo::ScopedDataPipeProducerHandle pipe,
ReadCallback callback) override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto reader = storage::FileStreamReader::CreateForIndexedDBDataItemReader(
file_task_runner_.get(), file_path_, storage::CreateFilesystemProxy(),
offset, expected_modification_time_);
auto adapter = std::make_unique<FileStreamReaderToDataPipe>(
std::move(reader), std::move(pipe));
auto* raw_adapter = adapter.get();
// Have the adapter (owning the reader) be owned by the result callback.
auto current_task_runner = base::SequencedTaskRunnerHandle::Get();
auto result_callback = base::BindOnce(
[](std::unique_ptr<FileStreamReaderToDataPipe> reader,
scoped_refptr<base::SequencedTaskRunner> task_runner,
ReadCallback callback, int result) {
// |callback| is expected to be run on the original sequence
// that called this Read function, so post it back.
task_runner->PostTask(FROM_HERE,
base::BindOnce(std::move(callback), result));
},
std::move(adapter), std::move(current_task_runner),
std::move(callback));
// On Windows, all async file IO needs to be done on the io thread.
// Do this on all platforms for consistency, even if not necessary on posix.
io_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
[](FileStreamReaderToDataPipe* adapter, uint64_t length,
base::OnceCallback<void(int)> result_callback) {
adapter->Start(std::move(result_callback), length);
},
// |raw_adapter| is owned by |result_callback|.
base::Unretained(raw_adapter), length, std::move(result_callback)));
}
void ReadSideData(ReadSideDataCallback callback) override {
// This type should never have side data.
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
std::move(callback).Run(net::ERR_NOT_IMPLEMENTED, mojo_base::BigBuffer());
}
private:
void OnClientDisconnected() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!receivers_.empty())
return;
host_->RemoveBoundReaders(file_path_);
// |this| is likely deleted at this point.
}
mojo::ReceiverSet<storage::mojom::BlobDataItemReader> receivers_;
// |this| is owned by |host|, so this raw "client pointer" is safe.
raw_ptr<IndexedDBDispatcherHost> host_;
base::FilePath file_path_;
base::Time expected_modification_time_;
base::RepeatingClosure release_callback_;
// There are a lot of task runners in this class:
// * IndexedDBDataItemReader itself needs to run on the IDB sequence.
// This is because releasing a ref needs to be done synchronously when
// the mojo interface connection is broken to avoid racing with adding
// refs, and the active blob registry is on the IDB sequence.
// * LocalFileStreamReader wants its own |file_task_runner_| to run
// various asynchronous file operations on.
// * net::FileStream (used by LocalFileStreamReader) needs to be run
// on an IO thread for asynchronous file operations (on Windows), which
// is done by passing in an |io_task_runner| to do this.
scoped_refptr<base::TaskRunner> file_task_runner_;
scoped_refptr<base::TaskRunner> io_task_runner_;
SEQUENCE_CHECKER(sequence_checker_);
};
IndexedDBDispatcherHost::IndexedDBDispatcherHost(
IndexedDBContextImpl* indexed_db_context,
scoped_refptr<base::TaskRunner> io_task_runner)
: indexed_db_context_(indexed_db_context),
io_task_runner_(std::move(io_task_runner)),
file_task_runner_(base::ThreadPool::CreateTaskRunner(
{base::MayBlock(), base::TaskPriority::USER_VISIBLE})) {
DETACH_FROM_SEQUENCE(sequence_checker_);
DCHECK(indexed_db_context_);
}
IndexedDBDispatcherHost::~IndexedDBDispatcherHost() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void IndexedDBDispatcherHost::AddReceiver(
const blink::StorageKey& storage_key,
mojo::PendingReceiver<blink::mojom::IDBFactory> pending_receiver) {
DCHECK(IDBTaskRunner()->RunsTasksInCurrentSequence());
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
receivers_.Add(this, std::move(pending_receiver), storage_key);
}
void IndexedDBDispatcherHost::AddDatabaseBinding(
std::unique_ptr<blink::mojom::IDBDatabase> database,
mojo::PendingAssociatedReceiver<blink::mojom::IDBDatabase>
pending_receiver) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
database_receivers_.Add(std::move(database), std::move(pending_receiver));
}
mojo::PendingAssociatedRemote<blink::mojom::IDBCursor>
IndexedDBDispatcherHost::CreateCursorBinding(
const blink::StorageKey& storage_key,
std::unique_ptr<IndexedDBCursor> cursor) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto cursor_impl = std::make_unique<CursorImpl>(
std::move(cursor), storage_key, this, IDBTaskRunner());
auto* cursor_impl_ptr = cursor_impl.get();
mojo::PendingAssociatedRemote<blink::mojom::IDBCursor> remote;
mojo::ReceiverId receiver_id = cursor_receivers_.Add(
std::move(cursor_impl), remote.InitWithNewEndpointAndPassReceiver());
cursor_impl_ptr->OnRemoveBinding(
base::BindOnce(&IndexedDBDispatcherHost::RemoveCursorBinding,
weak_factory_.GetWeakPtr(), receiver_id));
return remote;
}
void IndexedDBDispatcherHost::RemoveCursorBinding(
mojo::ReceiverId receiver_id) {
cursor_receivers_.Remove(receiver_id);
}
void IndexedDBDispatcherHost::AddTransactionBinding(
std::unique_ptr<blink::mojom::IDBTransaction> transaction,
mojo::PendingAssociatedReceiver<blink::mojom::IDBTransaction> receiver) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
transaction_receivers_.Add(std::move(transaction), std::move(receiver));
}
storage::mojom::BlobStorageContext*
IndexedDBDispatcherHost::mojo_blob_storage_context() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return indexed_db_context_->blob_storage_context();
}
storage::mojom::FileSystemAccessContext*
IndexedDBDispatcherHost::file_system_access_context() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return indexed_db_context_->file_system_access_context();
}
void IndexedDBDispatcherHost::GetDatabaseInfo(
mojo::PendingAssociatedRemote<blink::mojom::IDBCallbacks>
pending_callbacks) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
const auto& storage_key = receivers_.current_context();
auto callbacks = base::MakeRefCounted<IndexedDBCallbacks>(
this->AsWeakPtr(), storage_key, std::move(pending_callbacks),
IDBTaskRunner());
base::FilePath indexed_db_path = indexed_db_context_->data_path();
indexed_db_context_->GetIDBFactory()->GetDatabaseInfo(
std::move(callbacks), storage_key, indexed_db_path);
}
void IndexedDBDispatcherHost::Open(
mojo::PendingAssociatedRemote<blink::mojom::IDBCallbacks> pending_callbacks,
mojo::PendingAssociatedRemote<blink::mojom::IDBDatabaseCallbacks>
database_callbacks_remote,
const std::u16string& name,
int64_t version,
mojo::PendingAssociatedReceiver<blink::mojom::IDBTransaction>
transaction_receiver,
int64_t transaction_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
const auto& storage_key = receivers_.current_context();
auto callbacks = base::MakeRefCounted<IndexedDBCallbacks>(
this->AsWeakPtr(), storage_key, std::move(pending_callbacks),
IDBTaskRunner());
auto database_callbacks = base::MakeRefCounted<IndexedDBDatabaseCallbacks>(
indexed_db_context_, std::move(database_callbacks_remote),
IDBTaskRunner());
base::FilePath indexed_db_path = indexed_db_context_->data_path();
auto create_transaction_callback =
base::BindOnce(&IndexedDBDispatcherHost::CreateAndBindTransactionImpl,
AsWeakPtr(), std::move(transaction_receiver), storage_key);
std::unique_ptr<IndexedDBPendingConnection> connection =
std::make_unique<IndexedDBPendingConnection>(
std::move(callbacks), std::move(database_callbacks), transaction_id,
version, std::move(create_transaction_callback));
// TODO(dgrogan): Don't let a non-existing database be opened (and therefore
// created) if this origin is already over quota.
indexed_db_context_->GetIDBFactory()->Open(name, std::move(connection),
storage_key, indexed_db_path);
}
void IndexedDBDispatcherHost::DeleteDatabase(
mojo::PendingAssociatedRemote<blink::mojom::IDBCallbacks> pending_callbacks,
const std::u16string& name,
bool force_close) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
const auto& storage_key = receivers_.current_context();
auto callbacks = base::MakeRefCounted<IndexedDBCallbacks>(
this->AsWeakPtr(), storage_key, std::move(pending_callbacks),
IDBTaskRunner());
base::FilePath indexed_db_path = indexed_db_context_->data_path();
indexed_db_context_->GetIDBFactory()->DeleteDatabase(
name, std::move(callbacks), storage_key, indexed_db_path, force_close);
}
void IndexedDBDispatcherHost::AbortTransactionsAndCompactDatabase(
AbortTransactionsAndCompactDatabaseCallback mojo_callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
const auto& storage_key = receivers_.current_context();
base::OnceCallback<void(leveldb::Status)> callback_on_io = base::BindOnce(
&CallCompactionStatusCallbackOnIDBThread, std::move(mojo_callback));
indexed_db_context_->GetIDBFactory()->AbortTransactionsAndCompactDatabase(
std::move(callback_on_io), storage_key);
}
void IndexedDBDispatcherHost::AbortTransactionsForDatabase(
AbortTransactionsForDatabaseCallback mojo_callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
const auto& storage_key = receivers_.current_context();
base::OnceCallback<void(leveldb::Status)> callback_on_io = base::BindOnce(
&CallAbortStatusCallbackOnIDBThread, std::move(mojo_callback));
indexed_db_context_->GetIDBFactory()->AbortTransactionsForDatabase(
std::move(callback_on_io), storage_key);
}
void IndexedDBDispatcherHost::CreateAndBindTransactionImpl(
mojo::PendingAssociatedReceiver<blink::mojom::IDBTransaction>
transaction_receiver,
const blink::StorageKey& storage_key,
base::WeakPtr<IndexedDBTransaction> transaction) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto transaction_impl = std::make_unique<TransactionImpl>(
transaction, storage_key, this->AsWeakPtr(), IDBTaskRunner());
AddTransactionBinding(std::move(transaction_impl),
std::move(transaction_receiver));
}
void IndexedDBDispatcherHost::BindFileReader(
const base::FilePath& path,
base::Time expected_modification_time,
base::RepeatingClosure release_callback,
mojo::PendingReceiver<storage::mojom::BlobDataItemReader> receiver) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(receiver.is_valid());
DCHECK(file_task_runner_);
auto itr = file_reader_map_.find(path);
if (itr != file_reader_map_.end()) {
itr->second->AddReader(std::move(receiver));
return;
}
auto reader = std::make_unique<IndexedDBDataItemReader>(
this, path, expected_modification_time, std::move(release_callback),
file_task_runner_, io_task_runner_, std::move(receiver));
file_reader_map_.insert({path, std::move(reader)});
}
void IndexedDBDispatcherHost::RemoveBoundReaders(const base::FilePath& path) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
file_reader_map_.erase(path);
}
void IndexedDBDispatcherHost::CreateAllExternalObjects(
const blink::StorageKey& storage_key,
const std::vector<IndexedDBExternalObject>& objects,
std::vector<blink::mojom::IDBExternalObjectPtr>* mojo_objects) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
IDB_TRACE("IndexedDBDispatcherHost::CreateAllExternalObjects");
DCHECK_EQ(objects.size(), mojo_objects->size());
if (objects.empty())
return;
for (size_t i = 0; i < objects.size(); ++i) {
auto& blob_info = objects[i];
auto& mojo_object = (*mojo_objects)[i];
switch (blob_info.object_type()) {
case IndexedDBExternalObject::ObjectType::kBlob:
case IndexedDBExternalObject::ObjectType::kFile: {
DCHECK(mojo_object->is_blob_or_file());
auto& output_info = mojo_object->get_blob_or_file();
auto receiver = output_info->blob.InitWithNewPipeAndPassReceiver();
if (blob_info.is_remote_valid()) {
output_info->uuid = blob_info.uuid();
blob_info.Clone(std::move(receiver));
continue;
}
auto element = storage::mojom::BlobDataItem::New();
// TODO(enne): do we have to handle unknown size here??
element->size = blob_info.size();
element->side_data_size = 0;
element->content_type = base::UTF16ToUTF8(blob_info.type());
element->type = storage::mojom::BlobDataItemType::kIndexedDB;
base::Time last_modified;
// Android doesn't seem to consistently be able to set file modification
// times. https://crbug.com/1045488
#if !defined(OS_ANDROID)
last_modified = blob_info.last_modified();
#endif
BindFileReader(blob_info.indexed_db_file_path(), last_modified,
blob_info.release_callback(),
element->reader.InitWithNewPipeAndPassReceiver());
// Write results to output_info.
output_info->uuid = base::GenerateGUID();
mojo_blob_storage_context()->RegisterFromDataItem(
std::move(receiver), output_info->uuid, std::move(element));
break;
}
case IndexedDBExternalObject::ObjectType::kFileSystemAccessHandle: {
DCHECK(mojo_object->is_file_system_access_token());
mojo::PendingRemote<blink::mojom::FileSystemAccessTransferToken>
mojo_token;
if (blob_info.is_file_system_access_remote_valid()) {
blob_info.file_system_access_token_remote()->Clone(
mojo_token.InitWithNewPipeAndPassReceiver());
} else {
DCHECK(!blob_info.file_system_access_token().empty());
file_system_access_context()->DeserializeHandle(
storage_key, blob_info.file_system_access_token(),
mojo_token.InitWithNewPipeAndPassReceiver());
}
mojo_object->get_file_system_access_token() = std::move(mojo_token);
break;
}
}
}
}
void IndexedDBDispatcherHost::InvalidateWeakPtrsAndClearBindings() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
weak_factory_.InvalidateWeakPtrs();
cursor_receivers_.Clear();
database_receivers_.Clear();
transaction_receivers_.Clear();
}
base::SequencedTaskRunner* IndexedDBDispatcherHost::IDBTaskRunner() const {
return indexed_db_context_->IDBTaskRunner();
}
} // namespace content