blob: c540fec17c7d7484b2608ce32f97ea89b25d0985 [file] [log] [blame]
/*
* Copyright (C) 2010 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "third_party/blink/renderer/modules/indexeddb/idb_request.h"
#include <atomic>
#include <memory>
#include <optional>
#include <utility>
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "third_party/blink/public/platform/web_blob_info.h"
#include "third_party/blink/renderer/bindings/core/v8/to_v8_traits.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_binding_for_modules.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_idb_request_ready_state.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_union_idbcursor_idbindex_idbobjectstore.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_union_idbindex_idbobjectstore.h"
#include "third_party/blink/renderer/core/dom/dom_exception.h"
#include "third_party/blink/renderer/core/dom/quota_exceeded_error.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/probe/core_probes.h"
#include "third_party/blink/renderer/modules/indexed_db_names.h"
#include "third_party/blink/renderer/modules/indexeddb/idb_cursor_with_value.h"
#include "third_party/blink/renderer/modules/indexeddb/idb_database.h"
#include "third_party/blink/renderer/modules/indexeddb/idb_event_dispatcher.h"
#include "third_party/blink/renderer/modules/indexeddb/idb_factory_client.h"
#include "third_party/blink/renderer/modules/indexeddb/idb_request_queue_item.h"
#include "third_party/blink/renderer/modules/indexeddb/idb_value.h"
#include "third_party/blink/renderer/modules/indexeddb/idb_value_wrapping.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
#include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"
#include "third_party/perfetto/include/perfetto/tracing/track.h"
namespace blink {
namespace {
const char* RequestTypeToName(IDBRequest::TypeForMetrics type) {
switch (type) {
case IDBRequest::TypeForMetrics::kCursorAdvance:
return "IDBCursor::advance";
case IDBRequest::TypeForMetrics::kCursorContinue:
return "IDBCursor::continue";
case IDBRequest::TypeForMetrics::kCursorContinuePrimaryKey:
return "IDBCursor::continuePrimaryKEy";
case IDBRequest::TypeForMetrics::kCursorDelete:
return "IDBCursor::delete";
case IDBRequest::TypeForMetrics::kFactoryOpen:
return "IDBFactory::open";
case IDBRequest::TypeForMetrics::kFactoryDeleteDatabase:
return "IDBFactory::deleteDatabase";
case IDBRequest::TypeForMetrics::kIndexOpenCursor:
return "IDBIndex::openCursor";
case IDBRequest::TypeForMetrics::kIndexCount:
return "IDBIndex::count";
case IDBRequest::TypeForMetrics::kIndexOpenKeyCursor:
return "IDBIndex::openKeyCursor";
case IDBRequest::TypeForMetrics::kIndexGet:
return "IDBIndex::get";
case IDBRequest::TypeForMetrics::kIndexGetAll:
return "IDBIndex::getAll";
case IDBRequest::TypeForMetrics::kIndexGetAllKeys:
return "IDBIndex::getAllKeys";
case IDBRequest::TypeForMetrics::kIndexGetKey:
return "IDBIndex::getKey";
case IDBRequest::TypeForMetrics::kObjectStoreGet:
return "IDBObjectStore::get";
case IDBRequest::TypeForMetrics::kObjectStoreGetKey:
return "IDBObjectStore::getKey";
case IDBRequest::TypeForMetrics::kObjectStoreGetAll:
return "IDBObjectStore::getAll";
case IDBRequest::TypeForMetrics::kObjectStoreGetAllKeys:
return "IDBObjectStore::getAllKeys";
case IDBRequest::TypeForMetrics::kObjectStoreDelete:
return "IDBObjectStore::delete";
case IDBRequest::TypeForMetrics::kObjectStoreClear:
return "IDBObjectStore::clear";
case IDBRequest::TypeForMetrics::kObjectStoreCreateIndex:
return "IDBObjectStore::createIndex";
case IDBRequest::TypeForMetrics::kObjectStorePut:
return "IDBObjectStore::put";
case IDBRequest::TypeForMetrics::kObjectStoreAdd:
return "IDBObjectStore::add";
case IDBRequest::TypeForMetrics::kObjectStoreUpdate:
return "IDBObjectStore::update";
case IDBRequest::TypeForMetrics::kObjectStoreOpenCursor:
return "IDBObjectStore::openCursor";
case IDBRequest::TypeForMetrics::kObjectStoreOpenKeyCursor:
return "IDBObjectStore::openKeyCursor";
case IDBRequest::TypeForMetrics::kObjectStoreCount:
return "IDBObjectStore::count";
case IDBRequest::TypeForMetrics::kObjectStoreGetAllRecords:
return "IDBObjectStore::getAllRecords";
case IDBRequest::TypeForMetrics::kIndexGetAllRecords:
return "IDBIndex::getAllRecords";
}
}
void RecordHistogram(IDBRequest::TypeForMetrics type,
bool success,
base::TimeDelta duration,
bool is_fg_client) {
switch (type) {
case IDBRequest::TypeForMetrics::kObjectStorePut:
UMA_HISTOGRAM_TIMES("WebCore.IndexedDB.RequestDuration2.ObjectStorePut",
duration);
if (is_fg_client) {
UMA_HISTOGRAM_TIMES(
"WebCore.IndexedDB.RequestDuration2.ObjectStorePut.Foreground",
duration);
}
base::UmaHistogramBoolean(
"WebCore.IndexedDB.RequestDispatchOutcome.ObjectStorePut", success);
break;
case IDBRequest::TypeForMetrics::kObjectStoreAdd:
UMA_HISTOGRAM_TIMES("WebCore.IndexedDB.RequestDuration2.ObjectStoreAdd",
duration);
if (is_fg_client) {
UMA_HISTOGRAM_TIMES(
"WebCore.IndexedDB.RequestDuration2.ObjectStoreAdd.Foreground",
duration);
}
base::UmaHistogramBoolean(
"WebCore.IndexedDB.RequestDispatchOutcome.ObjectStoreAdd", success);
break;
case IDBRequest::TypeForMetrics::kObjectStoreGet:
UMA_HISTOGRAM_TIMES("WebCore.IndexedDB.RequestDuration2.ObjectStoreGet",
duration);
if (is_fg_client) {
UMA_HISTOGRAM_TIMES(
"WebCore.IndexedDB.RequestDuration2.ObjectStoreGet.Foreground",
duration);
}
base::UmaHistogramBoolean(
"WebCore.IndexedDB.RequestDispatchOutcome.ObjectStoreGet", success);
break;
case IDBRequest::TypeForMetrics::kFactoryOpen:
UMA_HISTOGRAM_TIMES("WebCore.IndexedDB.RequestDuration2.Open", duration);
if (is_fg_client) {
UMA_HISTOGRAM_TIMES(
"WebCore.IndexedDB.RequestDuration2.Open.Foreground", duration);
}
base::UmaHistogramBoolean("WebCore.IndexedDB.RequestDispatchOutcome.Open",
success);
break;
case IDBRequest::TypeForMetrics::kCursorAdvance:
case IDBRequest::TypeForMetrics::kCursorContinue:
case IDBRequest::TypeForMetrics::kCursorContinuePrimaryKey:
case IDBRequest::TypeForMetrics::kCursorDelete:
case IDBRequest::TypeForMetrics::kFactoryDeleteDatabase:
case IDBRequest::TypeForMetrics::kIndexOpenCursor:
case IDBRequest::TypeForMetrics::kIndexCount:
case IDBRequest::TypeForMetrics::kIndexOpenKeyCursor:
case IDBRequest::TypeForMetrics::kIndexGet:
case IDBRequest::TypeForMetrics::kIndexGetAll:
case IDBRequest::TypeForMetrics::kIndexGetAllKeys:
case IDBRequest::TypeForMetrics::kIndexGetKey:
case IDBRequest::TypeForMetrics::kObjectStoreGetKey:
case IDBRequest::TypeForMetrics::kObjectStoreGetAll:
case IDBRequest::TypeForMetrics::kObjectStoreGetAllKeys:
case IDBRequest::TypeForMetrics::kObjectStoreDelete:
case IDBRequest::TypeForMetrics::kObjectStoreClear:
case IDBRequest::TypeForMetrics::kObjectStoreCreateIndex:
case IDBRequest::TypeForMetrics::kObjectStoreUpdate:
case IDBRequest::TypeForMetrics::kObjectStoreOpenCursor:
case IDBRequest::TypeForMetrics::kObjectStoreOpenKeyCursor:
case IDBRequest::TypeForMetrics::kObjectStoreCount:
case IDBRequest::TypeForMetrics::kObjectStoreGetAllRecords:
case IDBRequest::TypeForMetrics::kIndexGetAllRecords:
break;
}
}
} // namespace
IDBRequest::AsyncTraceState::AsyncTraceState(TypeForMetrics type)
: type_(type), start_time_(base::TimeTicks::Now()) {
static std::atomic<size_t> counter(0);
id_ = counter.fetch_add(1, std::memory_order_relaxed);
TRACE_EVENT_BEGIN("IndexedDB",
perfetto::DynamicString(RequestTypeToName(type)),
perfetto::Track(id_));
}
void IDBRequest::AsyncTraceState::WillDispatchResult(bool success) {
if (type_) {
RecordHistogram(*type_, success, base::TimeTicks::Now() - start_time_,
is_fg_client_);
RecordAndReset();
}
}
void IDBRequest::AsyncTraceState::RecordAndReset() {
if (type_) {
TRACE_EVENT_END("IndexedDB", perfetto::Track(id_));
type_.reset();
}
}
IDBRequest::AsyncTraceState::~AsyncTraceState() {
RecordAndReset();
}
IDBRequest* IDBRequest::Create(ScriptState* script_state,
IDBIndex* source,
IDBTransaction* transaction,
IDBRequest::AsyncTraceState metrics) {
return Create(script_state,
source ? MakeGarbageCollected<Source>(source) : nullptr,
transaction, std::move(metrics));
}
IDBRequest* IDBRequest::Create(ScriptState* script_state,
IDBObjectStore* source,
IDBTransaction* transaction,
IDBRequest::AsyncTraceState metrics) {
return Create(script_state,
source ? MakeGarbageCollected<Source>(source) : nullptr,
transaction, std::move(metrics));
}
IDBRequest* IDBRequest::Create(ScriptState* script_state,
IDBCursor* source,
IDBTransaction* transaction,
IDBRequest::AsyncTraceState metrics) {
return Create(script_state,
source ? MakeGarbageCollected<Source>(source) : nullptr,
transaction, std::move(metrics));
}
IDBRequest* IDBRequest::Create(ScriptState* script_state,
const Source* source,
IDBTransaction* transaction,
IDBRequest::AsyncTraceState metrics) {
IDBRequest* request = MakeGarbageCollected<IDBRequest>(
script_state, source, transaction, std::move(metrics));
// Requests associated with IDBFactory (open/deleteDatabase/getDatabaseNames)
// do not have an associated transaction.
if (transaction)
transaction->RegisterRequest(request);
return request;
}
IDBRequest::IDBRequest(ScriptState* script_state,
const Source* source,
IDBTransaction* transaction,
AsyncTraceState metrics)
: ActiveScriptWrappable<IDBRequest>({}),
ExecutionContextLifecycleObserver(ExecutionContext::From(script_state)),
transaction_(transaction),
isolate_(script_state->GetIsolate()),
source_(source) {
AssignNewMetrics(std::move(metrics));
async_task_context_.Schedule(ExecutionContext::From(script_state),
indexed_db_names::kIndexedDB);
}
IDBRequest::~IDBRequest() {
if (!GetExecutionContext())
return;
if (ready_state_ == DONE)
DCHECK(metrics_.IsEmpty()) << metrics_.id();
else
DCHECK_EQ(ready_state_, kEarlyDeath);
}
void IDBRequest::Trace(Visitor* visitor) const {
visitor->Trace(transaction_);
visitor->Trace(source_);
visitor->Trace(result_);
visitor->Trace(error_);
visitor->Trace(pending_cursor_);
EventTarget::Trace(visitor);
ExecutionContextLifecycleObserver::Trace(visitor);
}
ScriptValue IDBRequest::result(ScriptState* script_state,
ExceptionState& exception_state) {
if (ready_state_ != DONE) {
// Must throw if returning an empty value. Message is arbitrary since it
// will never be seen.
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidStateError,
IDBDatabase::kRequestNotFinishedErrorMessage);
return ScriptValue();
}
if (!GetExecutionContext()) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
IDBDatabase::kDatabaseClosedErrorMessage);
return ScriptValue();
}
result_dirty_ = false;
v8::Local<v8::Value> value;
if (!result_) {
value = v8::Null(script_state->GetIsolate());
} else {
value = result_->ToV8(script_state);
}
return ScriptValue(script_state->GetIsolate(), value);
}
DOMException* IDBRequest::error(ExceptionState& exception_state) const {
if (ready_state_ != DONE) {
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidStateError,
IDBDatabase::kRequestNotFinishedErrorMessage);
return nullptr;
}
return error_.Get();
}
const IDBRequest::Source* IDBRequest::source(ScriptState* script_state) const {
if (!GetExecutionContext())
return nullptr;
return source_.Get();
}
V8IDBRequestReadyState IDBRequest::readyState() const {
if (!GetExecutionContext()) {
DCHECK(ready_state_ == DONE || ready_state_ == kEarlyDeath);
return V8IDBRequestReadyState(V8IDBRequestReadyState::Enum::kDone);
}
DCHECK(ready_state_ == PENDING || ready_state_ == DONE);
if (ready_state_ == PENDING)
return V8IDBRequestReadyState(V8IDBRequestReadyState::Enum::kPending);
return V8IDBRequestReadyState(V8IDBRequestReadyState::Enum::kDone);
}
void IDBRequest::Abort(bool queue_dispatch) {
DCHECK(!request_aborted_);
if (queue_item_) {
queue_item_->CancelLoading();
}
if (!GetExecutionContext() || GetExecutionContext()->IsContextDestroyed()) {
return;
}
DCHECK(ready_state_ == PENDING || ready_state_ == DONE) << ready_state_;
if (ready_state_ == DONE)
return;
request_aborted_ = true;
auto send_exception =
BindOnce(&IDBRequest::SendError, WrapWeakPersistent(this),
WrapPersistent(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kAbortError,
"The transaction was aborted, so the "
"request cannot be fulfilled.")),
/*force=*/true);
if (queue_dispatch) {
GetExecutionContext()
->GetTaskRunner(TaskType::kDatabaseAccess)
->PostTask(FROM_HERE, std::move(send_exception));
} else {
std::move(send_exception).Run();
}
}
void IDBRequest::SetCursorDetails(indexed_db::CursorType cursor_type,
mojom::IDBCursorDirection direction) {
DCHECK_EQ(ready_state_, PENDING);
DCHECK(!pending_cursor_);
cursor_type_ = cursor_type;
cursor_direction_ = direction;
}
void IDBRequest::SetPendingCursor(IDBCursor* cursor) {
DCHECK_EQ(ready_state_, DONE);
DCHECK(GetExecutionContext());
DCHECK(transaction_);
DCHECK(!pending_cursor_);
DCHECK_EQ(cursor, GetResultCursor());
has_pending_activity_ = true;
pending_cursor_ = cursor;
SetResult(nullptr);
ready_state_ = PENDING;
error_.Clear();
transaction_->RegisterRequest(this);
}
IDBCursor* IDBRequest::GetResultCursor() const {
if (!result_)
return nullptr;
if (result_->GetType() == IDBAny::kIDBCursorType)
return result_->IdbCursor();
if (result_->GetType() == IDBAny::kIDBCursorWithValueType)
return result_->IdbCursorWithValue();
return nullptr;
}
void IDBRequest::SendResultCursorInternal(IDBCursor* cursor,
std::unique_ptr<IDBKey> key,
std::unique_ptr<IDBKey> primary_key,
std::unique_ptr<IDBValue> value) {
DCHECK_EQ(ready_state_, PENDING);
cursor_key_ = std::move(key);
cursor_primary_key_ = std::move(primary_key);
cursor_value_ = std::move(value);
SendResult(MakeGarbageCollected<IDBAny>(cursor));
}
bool IDBRequest::CanStillSendResult() const {
// It's possible to attempt event dispatch after a context is destroyed,
// but before `ContextDestroyed()` has been called. See
// https://crbug.com/733642
const ExecutionContext* execution_context = GetExecutionContext();
if (!execution_context || execution_context->IsContextDestroyed()) {
return false;
}
DCHECK(ready_state_ == PENDING || ready_state_ == DONE);
if (request_aborted_)
return false;
DCHECK_EQ(ready_state_, PENDING);
DCHECK(!error_ && !result_);
return true;
}
void IDBRequest::HandleResponse(std::unique_ptr<IDBKey> key) {
transaction_->EnqueueResult(std::make_unique<IDBRequestQueueItem>(
this, std::move(key),
BindOnce(&IDBTransaction::OnResultReady,
WrapPersistent(transaction_.Get()))));
}
void IDBRequest::HandleResponse(int64_t value) {
transaction_->EnqueueResult(std::make_unique<IDBRequestQueueItem>(
this, value,
BindOnce(&IDBTransaction::OnResultReady,
WrapPersistent(transaction_.Get()))));
}
void IDBRequest::HandleResponse() {
transaction_->EnqueueResult(std::make_unique<IDBRequestQueueItem>(
this, BindOnce(&IDBTransaction::OnResultReady,
WrapPersistent(transaction_.Get()))));
}
void IDBRequest::HandleResponse(std::unique_ptr<IDBValue> value) {
value->SetIsolate(GetIsolate());
transaction_->EnqueueResult(std::make_unique<IDBRequestQueueItem>(
this, std::move(value),
BindOnce(&IDBTransaction::OnResultReady,
WrapPersistent(transaction_.Get()))));
}
void IDBRequest::HandleResponseAdvanceCursor(
std::unique_ptr<IDBKey> key,
std::unique_ptr<IDBKey> primary_key,
std::unique_ptr<IDBValue> optional_value) {
std::unique_ptr<IDBValue> value =
optional_value ? std::move(optional_value) : std::make_unique<IDBValue>();
value->SetIsolate(GetIsolate());
transaction_->EnqueueResult(std::make_unique<IDBRequestQueueItem>(
this, std::move(key), std::move(primary_key), std::move(value),
BindOnce(&IDBTransaction::OnResultReady,
WrapPersistent(transaction_.Get()))));
}
void IDBRequest::OnClear(bool success) {
if (success) {
probe::AsyncTask async_task(GetExecutionContext(), &async_task_context_,
"clear");
HandleResponse();
}
}
void IDBRequest::OnGetAll(
mojom::blink::IDBGetAllResultType result_type,
mojo::PendingAssociatedReceiver<mojom::blink::IDBDatabaseGetAllResultSink>
receiver) {
probe::AsyncTask async_task(GetExecutionContext(), &async_task_context_,
"success");
transaction_->EnqueueResult(std::make_unique<IDBRequestQueueItem>(
this, result_type, std::move(receiver),
BindOnce(&IDBTransaction::OnResultReady,
WrapPersistent(transaction_.Get()))));
}
void IDBRequest::OnDelete(bool success) {
if (success) {
probe::AsyncTask async_task(GetExecutionContext(), &async_task_context_,
"delete");
HandleResponse();
}
}
void IDBRequest::OnCount(bool success, uint32_t count) {
if (success) {
HandleResponse(count);
}
}
void IDBRequest::OnPut(mojom::blink::IDBTransactionPutResultPtr result) {
if (result->is_error_result()) {
HandleError(std::move(result->get_error_result()));
return;
}
probe::AsyncTask async_task(GetExecutionContext(), &async_task_context_,
"put");
DCHECK(result->is_key());
HandleResponse(std::move(result->get_key()));
}
void IDBRequest::OnGet(mojom::blink::IDBDatabaseGetResultPtr result) {
if (result->is_error_result()) {
HandleError(std::move(result->get_error_result()));
return;
}
probe::AsyncTask async_task(GetExecutionContext(), &async_task_context_,
"get");
if (result->is_empty()) {
HandleResponse();
} else if (result->is_key()) {
HandleResponse(std::move(result->get_key()));
} else if (result->is_value()) {
std::unique_ptr<IDBValue> value =
IDBValue::ConvertReturnValue(result->get_value());
HandleResponse(std::move(value));
}
}
void IDBRequest::OnOpenCursor(
mojom::blink::IDBDatabaseOpenCursorResultPtr result) {
if (result->is_error_result()) {
HandleError(std::move(result->get_error_result()));
return;
}
probe::AsyncTask async_task(GetExecutionContext(), &async_task_context_,
"openCursor");
if (result->is_empty()) {
std::unique_ptr<IDBValue> value = IDBValue::ConvertReturnValue(nullptr);
HandleResponse(std::move(value));
return;
}
std::unique_ptr<IDBValue> value;
if (result->get_value()->value) {
value = std::move(*result->get_value()->value);
} else {
value = std::make_unique<IDBValue>();
}
value->SetIsolate(GetIsolate());
transaction_->EnqueueResult(std::make_unique<IDBRequestQueueItem>(
this, std::move(result->get_value()->cursor),
std::move(result->get_value()->key),
std::move(result->get_value()->primary_key), std::move(value),
BindOnce(&IDBTransaction::OnResultReady,
WrapPersistent(transaction_.Get()))));
}
void IDBRequest::OnAdvanceCursor(mojom::blink::IDBCursorResultPtr result) {
if (result->is_error_result()) {
HandleError(std::move(result->get_error_result()));
return;
}
if (result->is_empty() && !result->get_empty()) {
HandleError(nullptr);
return;
}
if (!result->is_empty() && (result->get_values()->keys.size() != 1u ||
result->get_values()->primary_keys.size() != 1u ||
result->get_values()->values.size() != 1u)) {
HandleError(nullptr);
return;
}
probe::AsyncTask async_task(GetExecutionContext(), &async_task_context_,
"advanceCursor");
if (result->is_empty()) {
std::unique_ptr<IDBValue> value = IDBValue::ConvertReturnValue(nullptr);
HandleResponse(std::move(value));
return;
}
HandleResponseAdvanceCursor(std::move(result->get_values()->keys[0]),
std::move(result->get_values()->primary_keys[0]),
std::move(result->get_values()->values[0]));
}
void IDBRequest::OnGotKeyGeneratorCurrentNumber(
int64_t number,
mojom::blink::IDBErrorPtr error) {
if (error) {
HandleError(std::move(error));
} else {
DCHECK_GE(number, 0);
HandleResponse(number);
}
}
void IDBRequest::SendError(DOMException* error, bool force) {
TRACE_EVENT0("IndexedDB", "IDBRequest::SendError()");
probe::AsyncTask async_task(GetExecutionContext(), async_task_context(),
"error");
if (!GetExecutionContext() || (request_aborted_ && !force)) {
metrics_.RecordAndReset();
return;
}
error_ = error;
SetResult(MakeGarbageCollected<IDBAny>(IDBAny::kUndefinedType));
pending_cursor_.Clear();
DispatchEvent(*Event::CreateCancelableBubble(event_type_names::kError));
}
void IDBRequest::HandleError(mojom::blink::IDBErrorPtr error) {
mojom::blink::IDBException code =
error ? error->error_code : mojom::blink::IDBException::kUnknownError;
// In some cases, the backend clears the pending transaction task queue
// which destroys all pending tasks. If our callback was queued with a task
// that gets cleared, we'll get a signal with an IgnorableAbortError as the
// task is torn down. This means the error response can be safely ignored.
if (code == mojom::blink::IDBException::kIgnorableAbortError) {
return;
}
probe::AsyncTask async_task(GetExecutionContext(), &async_task_context_,
"error");
DOMException* dom_exception;
auto message = error ? error->error_message : "Invalid response";
if (code == mojom::blink::IDBException::kQuotaError &&
RuntimeEnabledFeatures::QuotaExceededErrorUpdateEnabled()) {
dom_exception = MakeGarbageCollected<QuotaExceededError>(message);
} else {
dom_exception = MakeGarbageCollected<DOMException>(
static_cast<DOMExceptionCode>(code), message);
}
transaction_->EnqueueResult(std::make_unique<IDBRequestQueueItem>(
this, dom_exception,
BindOnce(&IDBTransaction::OnResultReady,
WrapPersistent(transaction_.Get()))));
}
void IDBRequest::SendResultCursor(
mojo::PendingAssociatedRemote<mojom::blink::IDBCursor>
pending_remote_cursor,
std::unique_ptr<IDBKey> key,
std::unique_ptr<IDBKey> primary_key,
std::unique_ptr<IDBValue> value) {
if (!CanStillSendResult()) {
metrics_.RecordAndReset();
return;
}
DCHECK(!pending_cursor_);
IDBCursor* cursor = nullptr;
IDBCursor::Source* source = nullptr;
DCHECK(source_);
switch (source_->GetContentType()) {
case Source::ContentType::kIDBCursor:
break;
case Source::ContentType::kIDBIndex:
source =
MakeGarbageCollected<IDBCursor::Source>(source_->GetAsIDBIndex());
break;
case Source::ContentType::kIDBObjectStore:
source = MakeGarbageCollected<IDBCursor::Source>(
source_->GetAsIDBObjectStore());
break;
}
DCHECK(source);
switch (cursor_type_) {
case indexed_db::kCursorKeyOnly:
cursor = MakeGarbageCollected<IDBCursor>(std::move(pending_remote_cursor),
cursor_direction_, this, source,
transaction_.Get());
break;
case indexed_db::kCursorKeyAndValue:
cursor = MakeGarbageCollected<IDBCursorWithValue>(
std::move(pending_remote_cursor), cursor_direction_, this, source,
transaction_.Get());
break;
default:
NOTREACHED();
}
SendResultCursorInternal(cursor, std::move(key), std::move(primary_key),
std::move(value));
}
#if DCHECK_IS_ON()
static IDBObjectStore* EffectiveObjectStore(const IDBRequest::Source* source) {
DCHECK(source);
switch (source->GetContentType()) {
case IDBRequest::Source::ContentType::kIDBCursor:
NOTREACHED();
case IDBRequest::Source::ContentType::kIDBIndex:
return source->GetAsIDBIndex()->objectStore();
case IDBRequest::Source::ContentType::kIDBObjectStore:
return source->GetAsIDBObjectStore();
}
NOTREACHED();
}
#endif // DCHECK_IS_ON()
void IDBRequest::SendResult(IDBAny* result) {
TRACE_EVENT1("IndexedDB", "IDBRequest::SendResult", "type",
result->GetType());
if (!CanStillSendResult()) {
metrics_.RecordAndReset();
return;
}
DCHECK(!pending_cursor_);
SetResult(result);
DispatchEvent(*Event::Create(event_type_names::kSuccess));
}
void IDBRequest::AssignNewMetrics(AsyncTraceState metrics) {
DCHECK(metrics_.IsEmpty());
metrics_ = std::move(metrics);
// Grab the lifecycle state for metrics. This should be temporary code.
// `transaction_` only keeps track of an integral `scheduling_priority`.
if (GetExecutionContext()) {
std::ignore = GetExecutionContext()->GetScheduler()->AddLifecycleObserver(
FrameOrWorkerScheduler::ObserverType::kWorkerScheduler,
BindRepeating(
[](scheduler::SchedulingLifecycleState lifecycle_state) {
base::UmaHistogramEnumeration(
"WebCore.IndexedDB.SchedulingLifecycleState", lifecycle_state,
scheduler::SchedulingLifecycleState::kStopped);
}));
}
metrics_.set_is_fg_client(transaction_ &&
(transaction_->db().scheduling_priority() == 0));
}
void IDBRequest::SetResult(IDBAny* result) {
result_ = result;
result_dirty_ = true;
}
void IDBRequest::SendResultValue(std::unique_ptr<IDBValue> value) {
// See crbug.com/1519989
if (!CanStillSendResult()) {
metrics_.RecordAndReset();
return;
}
if (pending_cursor_) {
// Value should be empty, signifying the end of the cursor's range.
DCHECK(!value->Data().size());
DCHECK(!value->BlobInfo().size());
pending_cursor_->Close();
pending_cursor_.Clear();
}
#if DCHECK_IS_ON()
DCHECK(!value->PrimaryKey() ||
value->KeyPath() == EffectiveObjectStore(source_)->IdbKeyPath());
#endif
SendResult(MakeGarbageCollected<IDBAny>(std::move(value)));
}
void IDBRequest::SendResultAdvanceCursor(std::unique_ptr<IDBKey> key,
std::unique_ptr<IDBKey> primary_key,
std::unique_ptr<IDBValue> value) {
if (!CanStillSendResult()) {
metrics_.RecordAndReset();
return;
}
DCHECK(pending_cursor_);
SendResultCursorInternal(pending_cursor_.Release(), std::move(key),
std::move(primary_key), std::move(value));
}
bool IDBRequest::HasPendingActivity() const {
// FIXME: In an ideal world, we should return true as long as anyone has a or
// can get a handle to us and we have event listeners. This is order to
// handle user generated events properly.
return has_pending_activity_ && GetExecutionContext();
}
void IDBRequest::ContextDestroyed() {
if (ready_state_ == PENDING) {
ready_state_ = kEarlyDeath;
if (queue_item_)
queue_item_->CancelLoading();
if (transaction_)
transaction_->UnregisterRequest(this);
}
if (source_ && source_->IsIDBCursor())
source_->GetAsIDBCursor()->ContextWillBeDestroyed();
if (result_)
result_->ContextWillBeDestroyed();
if (pending_cursor_)
pending_cursor_->ContextWillBeDestroyed();
}
const AtomicString& IDBRequest::InterfaceName() const {
return event_target_names::kIDBRequest;
}
ExecutionContext* IDBRequest::GetExecutionContext() const {
return ExecutionContextLifecycleObserver::GetExecutionContext();
}
DispatchEventResult IDBRequest::DispatchEventInternal(Event& event) {
TRACE_EVENT0("IndexedDB", "IDBRequest::dispatchEvent");
event.SetTarget(this);
HeapVector<Member<EventTarget>> targets;
targets.push_back(this);
if (transaction_ && !prevent_propagation_) {
// Per spec: "A request's get the parent algorithm returns the request’s
// transaction."
targets.push_back(transaction_);
// Per spec: "A transaction's get the parent algorithm returns the
// transaction’s connection."
targets.push_back(transaction_->db());
}
// If this event originated from script, it should have no side effects.
if (!event.isTrusted())
return IDBEventDispatcher::Dispatch(event, targets);
DCHECK(event.type() == event_type_names::kSuccess ||
event.type() == event_type_names::kError ||
event.type() == event_type_names::kBlocked ||
event.type() == event_type_names::kUpgradeneeded)
<< "event type was " << event.type();
if (!GetExecutionContext())
return DispatchEventResult::kCanceledBeforeDispatch;
DCHECK_EQ(ready_state_, PENDING);
DCHECK(has_pending_activity_);
DCHECK_EQ(event.RawTarget(), this);
if (event.type() != event_type_names::kBlocked) {
ready_state_ = DONE;
}
// Cursor properties should not be updated until the success event is being
// dispatched.
IDBCursor* cursor_to_notify = nullptr;
if (event.type() == event_type_names::kSuccess) {
cursor_to_notify = GetResultCursor();
if (cursor_to_notify) {
cursor_to_notify->SetValueReady(std::move(cursor_key_),
std::move(cursor_primary_key_),
std::move(cursor_value_));
}
}
if (event.type() == event_type_names::kUpgradeneeded) {
DCHECK(!did_fire_upgrade_needed_event_);
did_fire_upgrade_needed_event_ = true;
}
const bool set_transaction_active =
transaction_ &&
(event.type() == event_type_names::kSuccess ||
event.type() == event_type_names::kUpgradeneeded ||
(event.type() == event_type_names::kError && !request_aborted_));
if (set_transaction_active) {
transaction_->SetActive(true);
}
// The request must be unregistered from the transaction before the event
// handler is invoked, because the handler can call an IDBCursor method that
// reuses this request, like continue() or advance(). http://crbug.com/724109
// describes the consequences of getting this wrong.
if (transaction_ && ready_state_ == DONE)
transaction_->UnregisterRequest(this);
if (event.type() == event_type_names::kError && transaction_)
transaction_->IncrementNumErrorsHandled();
// Now that the event dispatching has been triggered, record that the metric
// has completed.
metrics_.WillDispatchResult(/*success=*/
event.type() != event_type_names::kError);
DispatchEventResult dispatch_result =
IDBEventDispatcher::Dispatch(event, targets);
if (transaction_) {
// Possibly abort the transaction. This must occur after unregistering (so
// this request doesn't receive a second error) and before deactivating
// (which might trigger commit).
if (!request_aborted_) {
// Transactions should be aborted after event dispatch if an exception was
// not caught.
if (event.LegacyDidListenersThrow()) {
transaction_->StartAborting(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kAbortError,
"Uncaught exception in event handler."));
} else if (event.type() == event_type_names::kError &&
dispatch_result == DispatchEventResult::kNotCanceled) {
transaction_->StartAborting(error_);
}
}
// If this was the last request in the transaction's list, it may commit
// here.
if (set_transaction_active) {
transaction_->SetActive(false);
}
}
if (cursor_to_notify)
cursor_to_notify->PostSuccessHandlerCallback();
// An upgradeneeded event will always be followed by a success or error event,
// so must be kept alive.
if (ready_state_ == DONE && event.type() != event_type_names::kUpgradeneeded)
has_pending_activity_ = false;
return dispatch_result;
}
void IDBRequest::TransactionDidFinishAndDispatch() {
DCHECK(transaction_);
DCHECK(transaction_->IsVersionChange());
DCHECK(did_fire_upgrade_needed_event_);
DCHECK_EQ(ready_state_, DONE);
DCHECK(GetExecutionContext());
transaction_.Clear();
if (!GetExecutionContext())
return;
ready_state_ = PENDING;
}
} // namespace blink