blob: bb88f7d89d1b1b4975705966973f89244fac5b7a [file] [log] [blame]
// Copyright 2013 The Chromium Authors
// 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/instance/cursor.h"
#include <stddef.h>
#include <cstdint>
#include <memory>
#include <utility>
#include <vector>
#include "base/check.h"
#include "base/check_op.h"
#include "base/memory/ptr_util.h"
#include "base/memory/weak_ptr.h"
#include "base/notreached.h"
#include "base/trace_event/trace_event.h"
#include "content/browser/indexed_db/indexed_db_database_error.h"
#include "content/browser/indexed_db/indexed_db_external_object.h"
#include "content/browser/indexed_db/indexed_db_value.h"
#include "content/browser/indexed_db/instance/callback_helpers.h"
#include "content/browser/indexed_db/instance/transaction.h"
#include "content/browser/indexed_db/status.h"
#include "mojo/public/cpp/bindings/pending_associated_remote.h"
#include "mojo/public/cpp/bindings/self_owned_associated_receiver.h"
#include "third_party/blink/public/mojom/indexeddb/indexeddb.mojom.h"
#include "third_party/perfetto/include/perfetto/tracing/track.h"
using blink::IndexedDBKey;
namespace content::indexed_db {
namespace {
// This should never be script visible: the cursor should either be closed when
// it hits the end of the range (and script throws an error before the call
// could be made), if the transaction has finished (ditto), or if there's an
// incoming request from the front end but the transaction has aborted on the
// back end; in that case the tx will already have sent an abort to the request
// so this would be ignored.
DatabaseError CreateCursorClosedError() {
return DatabaseError(blink::mojom::IDBException::kUnknownError,
"The cursor has been closed.");
}
DatabaseError CreateError(blink::mojom::IDBException code,
const char* message,
base::WeakPtr<Transaction> transaction) {
if (transaction) {
transaction->IncrementNumErrorsSent();
}
return DatabaseError(code, message);
}
} // namespace
// static
Cursor* Cursor::CreateAndBind(
std::unique_ptr<BackingStore::Cursor> cursor,
indexed_db::CursorType cursor_type,
blink::mojom::IDBTaskType task_type,
base::WeakPtr<Transaction> transaction,
mojo::PendingAssociatedRemote<blink::mojom::IDBCursor>& pending_remote) {
auto instance = base::WrapUnique(
new Cursor(std::move(cursor), cursor_type, task_type, transaction));
Cursor* instance_ptr = instance.get();
mojo::MakeSelfOwnedAssociatedReceiver(
std::move(instance), pending_remote.InitWithNewEndpointAndPassReceiver());
return instance_ptr;
}
Cursor::Cursor(std::unique_ptr<BackingStore::Cursor> cursor,
indexed_db::CursorType cursor_type,
blink::mojom::IDBTaskType task_type,
base::WeakPtr<Transaction> transaction)
: bucket_locator_(transaction->bucket_context()->bucket_locator()),
task_type_(task_type),
cursor_type_(cursor_type),
transaction_(std::move(transaction)),
cursor_(std::move(cursor)) {
TRACE_EVENT_BEGIN("IndexedDB", "Cursor::open",
perfetto::Track::FromPointer(this));
}
Cursor::~Cursor() {
// Call to make sure we complete our lifetime trace.
Close();
}
void Cursor::Advance(uint32_t count,
blink::mojom::IDBCursor::AdvanceCallback callback) {
TRACE_EVENT0("IndexedDB", "Cursor::Advance");
if (!transaction_) {
Close();
}
if (closed_) {
const DatabaseError error(CreateCursorClosedError());
std::move(callback).Run(blink::mojom::IDBCursorResult::NewErrorResult(
blink::mojom::IDBError::New(error.code(), error.message())));
return;
}
blink::mojom::IDBCursor::AdvanceCallback aborting_callback =
CreateCallbackAbortOnDestruct<blink::mojom::IDBCursor::AdvanceCallback,
blink::mojom::IDBCursorResultPtr>(
std::move(callback), transaction_);
transaction_->ScheduleTask(
task_type_, BindWeakOperation<Cursor>(&Cursor::AdvanceOperation,
ptr_factory_.GetWeakPtr(), count,
std::move(aborting_callback)));
}
Status Cursor::AdvanceOperation(
uint32_t count,
blink::mojom::IDBCursor::AdvanceCallback callback,
Transaction* /*transaction*/) {
TRACE_EVENT0("IndexedDB", "Cursor::AdvanceOperation");
if (!cursor_) {
std::move(callback).Run(blink::mojom::IDBCursorResult::NewEmpty(true));
return Status::OK();
}
if (StatusOr<bool> result = cursor_->Advance(count);
!result.has_value() || !*result) {
cursor_.reset();
if (result.has_value()) {
std::move(callback).Run(blink::mojom::IDBCursorResult::NewEmpty(true));
return Status::OK();
}
// CreateError() needs to be called before calling Close() so
// |transaction_| is alive.
auto error = CreateError(blink::mojom::IDBException::kUnknownError,
"Error advancing cursor", transaction_);
Close();
std::move(callback).Run(blink::mojom::IDBCursorResult::NewErrorResult(
blink::mojom::IDBError::New(error.code(), error.message())));
return result.error();
}
blink::mojom::IDBValuePtr mojo_value;
IndexedDBValue* value = Value();
if (value) {
mojo_value = transaction_->BuildMojoValue(std::move(*value));
} else {
mojo_value = blink::mojom::IDBValue::New();
}
std::vector<IndexedDBKey> keys;
keys.emplace_back(key().Clone());
std::vector<IndexedDBKey> primary_keys;
primary_keys.emplace_back(primary_key().Clone());
std::vector<blink::mojom::IDBValuePtr> values;
values.push_back(std::move(mojo_value));
std::move(callback).Run(blink::mojom::IDBCursorResult::NewValues(
blink::mojom::IDBCursorValue::New(
std::move(keys), std::move(primary_keys), std::move(values))));
return Status::OK();
}
void Cursor::Continue(IndexedDBKey key,
IndexedDBKey primary_key,
blink::mojom::IDBCursor::ContinueCallback callback) {
TRACE_EVENT0("IndexedDB", "Cursor::Continue");
if (!transaction_) {
Close();
}
if (closed_) {
const DatabaseError error(CreateCursorClosedError());
std::move(callback).Run(blink::mojom::IDBCursorResult::NewErrorResult(
blink::mojom::IDBError::New(error.code(), error.message())));
return;
}
blink::mojom::IDBCursor::ContinueCallback aborting_callback =
CreateCallbackAbortOnDestruct<blink::mojom::IDBCursor::ContinueCallback,
blink::mojom::IDBCursorResultPtr>(
std::move(callback), transaction_);
transaction_->ScheduleTask(
task_type_,
BindWeakOperation<Cursor>(
&Cursor::ContinueOperation, ptr_factory_.GetWeakPtr(), std::move(key),
std::move(primary_key), std::move(aborting_callback)));
}
Status Cursor::ContinueOperation(
IndexedDBKey key,
IndexedDBKey primary_key,
blink::mojom::IDBCursor::ContinueCallback callback,
Transaction* /*transaction*/) {
TRACE_EVENT0("IndexedDB", "Cursor::ContinueOperation");
if (!cursor_) {
std::move(callback).Run(blink::mojom::IDBCursorResult::NewEmpty(true));
return Status::OK();
}
if (StatusOr<bool> result = cursor_->Continue(key, primary_key);
!result.has_value() || !*result) {
cursor_.reset();
if (result.has_value()) {
// This happens if we reach the end of the iterator and can't continue.
std::move(callback).Run(blink::mojom::IDBCursorResult::NewEmpty(true));
return Status::OK();
}
// |transaction_| must be valid for CreateError(), so we can't call
// Close() until after calling CreateError().
DatabaseError error = CreateError(blink::mojom::IDBException::kUnknownError,
"Error continuing cursor.", transaction_);
Close();
std::move(callback).Run(blink::mojom::IDBCursorResult::NewErrorResult(
blink::mojom::IDBError::New(error.code(), error.message())));
return result.error();
}
blink::mojom::IDBValuePtr mojo_value;
IndexedDBValue* value = Value();
if (value) {
mojo_value = transaction_->BuildMojoValue(std::move(*value));
} else {
mojo_value = blink::mojom::IDBValue::New();
}
std::vector<IndexedDBKey> keys;
keys.emplace_back(this->key().Clone());
std::vector<IndexedDBKey> primary_keys;
primary_keys.emplace_back(this->primary_key().Clone());
std::vector<blink::mojom::IDBValuePtr> values;
values.push_back(std::move(mojo_value));
std::move(callback).Run(blink::mojom::IDBCursorResult::NewValues(
blink::mojom::IDBCursorValue::New(
std::move(keys), std::move(primary_keys), std::move(values))));
return Status::OK();
}
void Cursor::Prefetch(int number_to_fetch,
blink::mojom::IDBCursor::PrefetchCallback callback) {
TRACE_EVENT0("IndexedDB", "Cursor::Prefetch");
if (!transaction_) {
Close();
}
if (closed_) {
const DatabaseError error(CreateCursorClosedError());
std::move(callback).Run(blink::mojom::IDBCursorResult::NewErrorResult(
blink::mojom::IDBError::New(error.code(), error.message())));
return;
}
blink::mojom::IDBCursor::PrefetchCallback aborting_callback =
CreateCallbackAbortOnDestruct<blink::mojom::IDBCursor::PrefetchCallback,
blink::mojom::IDBCursorResultPtr>(
std::move(callback), transaction_);
transaction_->ScheduleTask(
task_type_,
BindWeakOperation<Cursor>(&Cursor::PrefetchIterationOperation,
ptr_factory_.GetWeakPtr(), number_to_fetch,
std::move(aborting_callback)));
}
Status Cursor::PrefetchIterationOperation(
int number_to_fetch,
blink::mojom::IDBCursor::PrefetchCallback callback,
Transaction* /*transaction*/) {
TRACE_EVENT0("IndexedDB", "Cursor::PrefetchIterationOperation");
Status s = Status::OK();
std::vector<IndexedDBKey> found_keys;
std::vector<IndexedDBKey> found_primary_keys;
std::vector<IndexedDBValue> found_values;
// TODO(cmumford): Use IPC::mojom::kChannelMaximumMessageSize
const size_t max_size_estimate = 10 * 1024 * 1024;
size_t size_estimate = 0;
// TODO(cmumford): Handle this error (crbug.com/363397). Although this will
// properly fail, caller will not know why, and any corruption
// will be ignored.
for (int i = 0; i < number_to_fetch; ++i) {
if (!cursor_ || reached_end_during_prefetch_) {
break;
}
StatusOr<bool> result = cursor_->Continue();
if (!result.has_value()) {
cursor_.reset();
// |transaction_| must be valid for CreateError(), so we can't call
// Close() until after calling CreateError().
DatabaseError error =
CreateError(blink::mojom::IDBException::kUnknownError,
"Error continuing cursor.", transaction_);
Close();
std::move(callback).Run(blink::mojom::IDBCursorResult::NewErrorResult(
blink::mojom::IDBError::New(error.code(), error.message())));
return result.error();
}
if (!*result) {
// We've reached the end, so just return what we have.
reached_end_during_prefetch_ = true;
break;
}
if (i == 0) {
// First prefetched result is always used, so that's the position
// a cursor should be reset to if the prefetch is invalidated.
cursor_->SavePosition();
}
found_keys.emplace_back(cursor_->GetKey().Clone());
found_primary_keys.emplace_back(cursor_->GetPrimaryKey().Clone());
switch (cursor_type_) {
case indexed_db::CursorType::kKeyOnly:
found_values.push_back(IndexedDBValue());
break;
case indexed_db::CursorType::kKeyAndValue: {
found_values.push_back(std::move(cursor_->GetValue()));
size_estimate += found_values.back().SizeEstimate();
break;
}
default:
NOTREACHED();
}
size_estimate += cursor_->GetKey().size_estimate();
size_estimate += cursor_->GetPrimaryKey().size_estimate();
if (size_estimate > max_size_estimate) {
break;
}
}
if (found_keys.empty()) {
std::move(callback).Run(blink::mojom::IDBCursorResult::NewEmpty(true));
return Status::OK();
}
DCHECK_EQ(found_keys.size(), found_primary_keys.size());
DCHECK_EQ(found_keys.size(), found_values.size());
std::vector<blink::mojom::IDBValuePtr> mojo_values;
mojo_values.reserve(found_values.size());
for (IndexedDBValue& value : found_values) {
mojo_values.emplace_back(transaction_->BuildMojoValue(std::move(value)));
}
std::move(callback).Run(blink::mojom::IDBCursorResult::NewValues(
blink::mojom::IDBCursorValue::New(std::move(found_keys),
std::move(found_primary_keys),
std::move(mojo_values))));
return Status::OK();
}
void Cursor::PrefetchReset(int used_prefetches) {
TRACE_EVENT0("IndexedDB", "Cursor::PrefetchReset");
if (closed_) {
return;
}
reached_end_during_prefetch_ = false;
if (!cursor_->TryResetToLastSavedPosition()) {
cursor_.reset();
}
// First prefetched result is always used.
if (cursor_) {
DCHECK_GT(used_prefetches, 0);
if (used_prefetches > 1) {
auto result = cursor_->Advance(used_prefetches - 1);
DCHECK(!result.has_value() || result.value());
}
}
}
void Cursor::Close() {
if (closed_) {
return;
}
// Corresponds to the TRACE_EVENT_BEGIN in the constructor.
TRACE_EVENT_END("IndexedDB", perfetto::Track::FromPointer(this));
TRACE_EVENT0("IndexedDB", "Cursor::Close");
closed_ = true;
cursor_.reset();
if (transaction_) {
transaction_->UnregisterOpenCursor(this);
}
transaction_.reset();
}
} // namespace content::indexed_db