diff options
Diffstat (limited to 'Source/WebCore/Modules/indexeddb/IDBRequest.cpp')
-rw-r--r-- | Source/WebCore/Modules/indexeddb/IDBRequest.cpp | 755 |
1 files changed, 380 insertions, 375 deletions
diff --git a/Source/WebCore/Modules/indexeddb/IDBRequest.cpp b/Source/WebCore/Modules/indexeddb/IDBRequest.cpp index 1bc170647..8b84cd19c 100644 --- a/Source/WebCore/Modules/indexeddb/IDBRequest.cpp +++ b/Source/WebCore/Modules/indexeddb/IDBRequest.cpp @@ -1,29 +1,26 @@ /* - * Copyright (C) 2010 Google Inc. All rights reserved. + * Copyright (C) 2015-2017 Apple 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. * - * 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. + * THIS SOFTWARE IS PROVIDED BY APPLE INC. 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 INC. 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 "config.h" @@ -31,519 +28,527 @@ #if ENABLE(INDEXED_DATABASE) -#include "EventException.h" -#include "EventListener.h" +#include "DOMError.h" +#include "Event.h" #include "EventNames.h" #include "EventQueue.h" -#include "ExceptionCodePlaceholder.h" #include "IDBBindingUtilities.h" -#include "IDBCursorBackend.h" -#include "IDBCursorWithValue.h" +#include "IDBConnectionProxy.h" +#include "IDBCursor.h" #include "IDBDatabase.h" +#include "IDBDatabaseException.h" #include "IDBEventDispatcher.h" -#include "IDBTransaction.h" +#include "IDBIndex.h" +#include "IDBKeyData.h" +#include "IDBObjectStore.h" +#include "IDBResultData.h" +#include "JSDOMConvert.h" #include "Logging.h" +#include "ScopeGuard.h" #include "ScriptExecutionContext.h" +#include "ThreadSafeDataBuffer.h" +#include <heap/StrongInlines.h> +#include <wtf/Variant.h> + +using namespace JSC; namespace WebCore { -PassRefPtr<IDBRequest> IDBRequest::create(ScriptExecutionContext* context, PassRefPtr<IDBAny> source, IDBTransaction* transaction) +Ref<IDBRequest> IDBRequest::create(ScriptExecutionContext& context, IDBObjectStore& objectStore, IDBTransaction& transaction) { - RefPtr<IDBRequest> request(adoptRef(new IDBRequest(context, source, IDBDatabaseBackend::NormalTask, transaction))); - request->suspendIfNeeded(); - // Requests associated with IDBFactory (open/deleteDatabase/getDatabaseNames) are not associated with transactions. - if (transaction) - transaction->registerRequest(request.get()); - return request.release(); + return adoptRef(*new IDBRequest(context, objectStore, transaction)); } -PassRefPtr<IDBRequest> IDBRequest::create(ScriptExecutionContext* context, PassRefPtr<IDBAny> source, IDBDatabaseBackend::TaskType taskType, IDBTransaction* transaction) +Ref<IDBRequest> IDBRequest::create(ScriptExecutionContext& context, IDBCursor& cursor, IDBTransaction& transaction) { - RefPtr<IDBRequest> request(adoptRef(new IDBRequest(context, source, taskType, transaction))); - request->suspendIfNeeded(); - // Requests associated with IDBFactory (open/deleteDatabase/getDatabaseNames) are not associated with transactions. - if (transaction) - transaction->registerRequest(request.get()); - return request.release(); + return adoptRef(*new IDBRequest(context, cursor, transaction)); } -IDBRequest::IDBRequest(ScriptExecutionContext* context, PassRefPtr<IDBAny> source, IDBDatabaseBackend::TaskType taskType, IDBTransaction* transaction) - : ActiveDOMObject(context) - , m_result(0) - , m_errorCode(0) - , m_contextStopped(false) - , m_transaction(transaction) - , m_readyState(PENDING) - , m_requestAborted(false) - , m_source(source) - , m_taskType(taskType) - , m_hasPendingActivity(true) - , m_cursorType(IndexedDB::CursorType::KeyAndValue) - , m_cursorDirection(IndexedDB::CursorDirection::Next) - , m_cursorFinished(false) - , m_pendingCursor(0) - , m_didFireUpgradeNeededEvent(false) - , m_preventPropagation(false) - , m_requestState(context) +Ref<IDBRequest> IDBRequest::create(ScriptExecutionContext& context, IDBIndex& index, IDBTransaction& transaction) { + return adoptRef(*new IDBRequest(context, index, transaction)); } -IDBRequest::~IDBRequest() +Ref<IDBRequest> IDBRequest::createObjectStoreGet(ScriptExecutionContext& context, IDBObjectStore& objectStore, IndexedDB::ObjectStoreRecordType type, IDBTransaction& transaction) { - ASSERT(m_readyState == DONE || m_readyState == EarlyDeath || !scriptExecutionContext()); + return adoptRef(*new IDBRequest(context, objectStore, type, transaction)); } -PassRefPtr<IDBAny> IDBRequest::result(ExceptionCode& ec) const +Ref<IDBRequest> IDBRequest::createIndexGet(ScriptExecutionContext& context, IDBIndex& index, IndexedDB::IndexRecordType requestedRecordType, IDBTransaction& transaction) { - if (m_readyState != DONE) { - ec = IDBDatabaseException::InvalidStateError; - return 0; - } - return m_result; + return adoptRef(*new IDBRequest(context, index, requestedRecordType, transaction)); } -PassRefPtr<DOMError> IDBRequest::error(ExceptionCode& ec) const +IDBRequest::IDBRequest(ScriptExecutionContext& context, IDBClient::IDBConnectionProxy& connectionProxy) + : IDBActiveDOMObject(&context) + , m_resourceIdentifier(connectionProxy) + , m_connectionProxy(connectionProxy) { - if (m_readyState != DONE) { - ec = IDBDatabaseException::InvalidStateError; - return 0; - } - return m_error; + suspendIfNeeded(); } -unsigned short IDBRequest::errorCode(ExceptionCode& ec) const +IDBRequest::IDBRequest(ScriptExecutionContext& context, IDBObjectStore& objectStore, IDBTransaction& transaction) + : IDBActiveDOMObject(&context) + , m_transaction(&transaction) + , m_resourceIdentifier(transaction.connectionProxy()) + , m_source(&objectStore) + , m_connectionProxy(transaction.database().connectionProxy()) { - if (m_readyState != DONE) { - ec = IDBDatabaseException::InvalidStateError; - return 0; - } - return m_errorCode; + suspendIfNeeded(); } -PassRefPtr<IDBAny> IDBRequest::source() const +IDBRequest::IDBRequest(ScriptExecutionContext& context, IDBCursor& cursor, IDBTransaction& transaction) + : IDBActiveDOMObject(&context) + , m_transaction(&transaction) + , m_resourceIdentifier(transaction.connectionProxy()) + , m_pendingCursor(&cursor) + , m_connectionProxy(transaction.database().connectionProxy()) { - return m_source; + suspendIfNeeded(); + + WTF::switchOn(cursor.source(), + [this] (const auto& value) { this->m_source = IDBRequest::Source { value }; } + ); + + cursor.setRequest(*this); } -PassRefPtr<IDBTransaction> IDBRequest::transaction() const +IDBRequest::IDBRequest(ScriptExecutionContext& context, IDBIndex& index, IDBTransaction& transaction) + : IDBActiveDOMObject(&context) + , m_transaction(&transaction) + , m_resourceIdentifier(transaction.connectionProxy()) + , m_source(&index) + , m_connectionProxy(transaction.database().connectionProxy()) { - return m_transaction; + suspendIfNeeded(); } -const String& IDBRequest::readyState() const +IDBRequest::IDBRequest(ScriptExecutionContext& context, IDBObjectStore& objectStore, IndexedDB::ObjectStoreRecordType type, IDBTransaction& transaction) + : IDBActiveDOMObject(&context) + , m_transaction(&transaction) + , m_resourceIdentifier(transaction.connectionProxy()) + , m_source(&objectStore) + , m_requestedObjectStoreRecordType(type) + , m_connectionProxy(transaction.database().connectionProxy()) { - ASSERT(m_readyState == PENDING || m_readyState == DONE); - DEFINE_STATIC_LOCAL(AtomicString, pending, ("pending", AtomicString::ConstructFromLiteral)); - DEFINE_STATIC_LOCAL(AtomicString, done, ("done", AtomicString::ConstructFromLiteral)); - - if (m_readyState == PENDING) - return pending; + suspendIfNeeded(); +} - return done; +IDBRequest::IDBRequest(ScriptExecutionContext& context, IDBIndex& index, IndexedDB::IndexRecordType requestedRecordType, IDBTransaction& transaction) + : IDBRequest(context, index, transaction) +{ + m_requestedIndexRecordType = requestedRecordType; } -void IDBRequest::markEarlyDeath() +IDBRequest::~IDBRequest() { - ASSERT(m_readyState == PENDING); - m_readyState = EarlyDeath; - if (m_transaction) - m_transaction->unregisterRequest(this); + ASSERT(currentThread() == originThreadID()); + + if (m_result) { + WTF::switchOn(m_result.value(), + [] (RefPtr<IDBCursor>& cursor) { cursor->clearRequest(); }, + [] (const auto&) { } + ); + } } -void IDBRequest::abort() +ExceptionOr<std::optional<IDBRequest::Result>> IDBRequest::result() const { - ASSERT(!m_requestAborted); - if (m_contextStopped || !scriptExecutionContext()) - return; - ASSERT(m_readyState == PENDING || m_readyState == DONE); - if (m_readyState == DONE) - return; + if (!isDone()) + return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to read the 'result' property from 'IDBRequest': The request has not finished.") }; - // Enqueued events may be the only reference to this object. - RefPtr<IDBRequest> self(this); + return std::optional<IDBRequest::Result> { m_result }; +} - EventQueue& eventQueue = scriptExecutionContext()->eventQueue(); - for (size_t i = 0; i < m_enqueuedEvents.size(); ++i) { - bool removed = eventQueue.cancelEvent(*m_enqueuedEvents[i]); - ASSERT_UNUSED(removed, removed); - } - m_enqueuedEvents.clear(); +ExceptionOr<DOMError*> IDBRequest::error() const +{ + ASSERT(currentThread() == originThreadID()); + + if (!isDone()) + return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to read the 'error' property from 'IDBRequest': The request has not finished.") }; - m_errorCode = 0; - m_error.clear(); - m_errorMessage = String(); - m_result.clear(); - onError(IDBDatabaseError::create(IDBDatabaseException::AbortError)); - m_requestAborted = true; + return m_domError.get(); } -void IDBRequest::setCursorDetails(IndexedDB::CursorType cursorType, IndexedDB::CursorDirection direction) +void IDBRequest::setSource(IDBCursor& cursor) { - ASSERT(m_readyState == PENDING); - ASSERT(!m_pendingCursor); - m_cursorType = cursorType; - m_cursorDirection = direction; + ASSERT(currentThread() == originThreadID()); + ASSERT(!m_cursorRequestNotifier); + + m_source = Source { &cursor }; + m_cursorRequestNotifier = std::make_unique<ScopeGuard>([this]() { + ASSERT(WTF::holds_alternative<RefPtr<IDBCursor>>(m_source.value())); + WTF::get<RefPtr<IDBCursor>>(m_source.value())->decrementOutstandingRequestCount(); + }); } -void IDBRequest::setPendingCursor(PassRefPtr<IDBCursor> cursor) +void IDBRequest::setVersionChangeTransaction(IDBTransaction& transaction) { - ASSERT(m_readyState == DONE); - ASSERT(scriptExecutionContext()); - ASSERT(m_transaction); - ASSERT(!m_pendingCursor); - ASSERT(cursor == getResultCursor()); + ASSERT(currentThread() == originThreadID()); + ASSERT(!m_transaction); + ASSERT(transaction.isVersionChange()); + ASSERT(!transaction.isFinishedOrFinishing()); - m_pendingCursor = cursor; - m_result.clear(); - m_readyState = PENDING; - m_errorCode = 0; - m_error.clear(); - m_errorMessage = String(); - m_transaction->registerRequest(this); + m_transaction = &transaction; } -PassRefPtr<IDBCursor> IDBRequest::getResultCursor() +RefPtr<WebCore::IDBTransaction> IDBRequest::transaction() const { - if (!m_result) + ASSERT(currentThread() == originThreadID()); + return m_shouldExposeTransactionToDOM ? m_transaction : nullptr; +} + +uint64_t IDBRequest::sourceObjectStoreIdentifier() const +{ + ASSERT(currentThread() == originThreadID()); + + if (!m_source) return 0; - if (m_result->type() == IDBAny::IDBCursorType) - return m_result->idbCursor(); - if (m_result->type() == IDBAny::IDBCursorWithValueType) - return m_result->idbCursorWithValue(); - return 0; + + return WTF::switchOn(m_source.value(), + [] (const RefPtr<IDBObjectStore>& objectStore) { return objectStore->info().identifier(); }, + [] (const RefPtr<IDBIndex>& index) { return index->info().objectStoreIdentifier(); }, + [] (const RefPtr<IDBCursor>&) { return 0; } + ); } -void IDBRequest::setResultCursor(PassRefPtr<IDBCursor> cursor, PassRefPtr<IDBKey> key, PassRefPtr<IDBKey> primaryKey, const Deprecated::ScriptValue& value) +uint64_t IDBRequest::sourceIndexIdentifier() const { - ASSERT(m_readyState == PENDING); - m_cursorKey = key; - m_cursorPrimaryKey = primaryKey; - m_cursorValue = value; + ASSERT(currentThread() == originThreadID()); - if (m_cursorType == IndexedDB::CursorType::KeyOnly) { - m_result = IDBAny::create(cursor); - return; - } + if (!m_source) + return 0; - m_result = IDBAny::create(IDBCursorWithValue::fromCursor(cursor)); + return WTF::switchOn(m_source.value(), + [] (const RefPtr<IDBObjectStore>&) -> uint64_t { return 0; }, + [] (const RefPtr<IDBIndex>& index) -> uint64_t { return index->info().identifier(); }, + [] (const RefPtr<IDBCursor>&) -> uint64_t { return 0; } + ); } -void IDBRequest::finishCursor() +IndexedDB::ObjectStoreRecordType IDBRequest::requestedObjectStoreRecordType() const { - m_cursorFinished = true; - if (m_readyState != PENDING) - m_hasPendingActivity = false; + ASSERT(currentThread() == originThreadID()); + + return m_requestedObjectStoreRecordType; } -bool IDBRequest::shouldEnqueueEvent() const +IndexedDB::IndexRecordType IDBRequest::requestedIndexRecordType() const { - if (m_contextStopped || !scriptExecutionContext()) - return false; - ASSERT(m_readyState == PENDING || m_readyState == DONE); - if (m_requestAborted) - return false; - ASSERT(m_readyState == PENDING); - ASSERT(!m_errorCode && m_errorMessage.isNull() && !m_error && !m_result); - return true; + ASSERT(currentThread() == originThreadID()); + ASSERT(m_source); + ASSERT(WTF::holds_alternative<RefPtr<IDBIndex>>(m_source.value())); + + return m_requestedIndexRecordType; } -void IDBRequest::onError(PassRefPtr<IDBDatabaseError> error) +EventTargetInterface IDBRequest::eventTargetInterface() const { - LOG(StorageAPI, "IDBRequest::onError()"); - if (!shouldEnqueueEvent()) - return; + ASSERT(currentThread() == originThreadID()); - m_errorCode = error->code(); - m_errorMessage = error->message(); - m_error = DOMError::create(IDBDatabaseException::getErrorName(error->idbCode())); - m_pendingCursor.clear(); - enqueueEvent(Event::create(eventNames().errorEvent, true, true)); + return IDBRequestEventTargetInterfaceType; } -static PassRefPtr<Event> createSuccessEvent() +const char* IDBRequest::activeDOMObjectName() const { - return Event::create(eventNames().successEvent, false, false); + ASSERT(currentThread() == originThreadID()); + + return "IDBRequest"; } -void IDBRequest::onSuccess(PassRefPtr<DOMStringList> domStringList) +bool IDBRequest::canSuspendForDocumentSuspension() const { - LOG(StorageAPI, "IDBRequest::onSuccess(DOMStringList)"); - if (!shouldEnqueueEvent()) - return; + ASSERT(currentThread() == originThreadID()); + return false; +} - m_result = IDBAny::create(domStringList); - enqueueEvent(createSuccessEvent()); +bool IDBRequest::hasPendingActivity() const +{ + ASSERT(currentThread() == originThreadID() || mayBeGCThread()); + return m_hasPendingActivity; } -void IDBRequest::onSuccess(PassRefPtr<IDBCursorBackend> backend, PassRefPtr<IDBKey> key, PassRefPtr<IDBKey> primaryKey, PassRefPtr<SharedBuffer> buffer) +void IDBRequest::stop() { - LOG(StorageAPI, "IDBRequest::onSuccess(IDBCursor)"); - if (!shouldEnqueueEvent()) - return; + ASSERT(currentThread() == originThreadID()); + ASSERT(!m_contextStopped); - DOMRequestState::Scope scope(m_requestState); - Deprecated::ScriptValue value = deserializeIDBValueBuffer(requestState(), buffer); - ASSERT(!m_pendingCursor); - RefPtr<IDBCursor> cursor; - switch (m_cursorType) { - case IndexedDB::CursorType::KeyOnly: - cursor = IDBCursor::create(backend, m_cursorDirection, this, m_source.get(), m_transaction.get()); - break; - case IndexedDB::CursorType::KeyAndValue: - cursor = IDBCursorWithValue::create(backend, m_cursorDirection, this, m_source.get(), m_transaction.get()); - break; - default: - ASSERT_NOT_REACHED(); - } - setResultCursor(cursor, key, primaryKey, value); + cancelForStop(); + + removeAllEventListeners(); - enqueueEvent(createSuccessEvent()); + m_contextStopped = true; } -void IDBRequest::onSuccess(PassRefPtr<IDBKey> idbKey) +void IDBRequest::cancelForStop() { - LOG(StorageAPI, "IDBRequest::onSuccess(IDBKey)"); - if (!shouldEnqueueEvent()) - return; - - if (idbKey && idbKey->isValid()) { - DOMRequestState::Scope scope(m_requestState); - m_result = IDBAny::create(idbKeyToScriptValue(requestState(), idbKey)); - } else - m_result = IDBAny::createInvalid(); - enqueueEvent(createSuccessEvent()); + // The base IDBRequest class has nothing additional to do here. } -void IDBRequest::onSuccess(PassRefPtr<SharedBuffer> valueBuffer) +void IDBRequest::enqueueEvent(Ref<Event>&& event) { - LOG(StorageAPI, "IDBRequest::onSuccess(SharedBuffer)"); - if (!shouldEnqueueEvent()) + ASSERT(currentThread() == originThreadID()); + if (!scriptExecutionContext() || m_contextStopped) return; - DOMRequestState::Scope scope(m_requestState); - Deprecated::ScriptValue value = deserializeIDBValueBuffer(requestState(), valueBuffer); - onSuccessInternal(value); + event->setTarget(this); + scriptExecutionContext()->eventQueue().enqueueEvent(WTFMove(event)); } -#ifndef NDEBUG -static PassRefPtr<IDBObjectStore> effectiveObjectStore(PassRefPtr<IDBAny> source) +bool IDBRequest::dispatchEvent(Event& event) { - if (source->type() == IDBAny::IDBObjectStoreType) - return source->idbObjectStore(); - if (source->type() == IDBAny::IDBIndexType) - return source->idbIndex()->objectStore(); + LOG(IndexedDB, "IDBRequest::dispatchEvent - %s (%p)", event.type().string().utf8().data(), this); + + ASSERT(currentThread() == originThreadID()); + ASSERT(m_hasPendingActivity); + ASSERT(!m_contextStopped); + + if (event.type() != eventNames().blockedEvent) + m_readyState = ReadyState::Done; + + Vector<RefPtr<EventTarget>> targets; + targets.append(this); + + if (&event == m_openDatabaseSuccessEvent) + m_openDatabaseSuccessEvent = nullptr; + else if (m_transaction && !m_transaction->isFinished()) { + targets.append(m_transaction); + targets.append(m_transaction->db()); + } + + m_hasPendingActivity = false; + + m_cursorRequestNotifier = nullptr; + + bool dontPreventDefault; + { + TransactionActivator activator(m_transaction.get()); + dontPreventDefault = IDBEventDispatcher::dispatch(event, targets); + } + + // IDBEventDispatcher::dispatch() might have set the pending activity flag back to true, suggesting the request will be reused. + // We might also re-use the request if this event was the upgradeneeded event for an IDBOpenDBRequest. + if (!m_hasPendingActivity) + m_hasPendingActivity = isOpenDBRequest() && (event.type() == eventNames().upgradeneededEvent || event.type() == eventNames().blockedEvent); + + // The request should only remain in the transaction's request list if it represents a pending cursor operation, or this is an open request that was blocked. + if (m_transaction && !m_pendingCursor && event.type() != eventNames().blockedEvent) + m_transaction->removeRequest(*this); - ASSERT_NOT_REACHED(); - return 0; + if (dontPreventDefault && event.type() == eventNames().errorEvent && m_transaction && !m_transaction->isFinishedOrFinishing()) { + ASSERT(m_domError); + m_transaction->abortDueToFailedRequest(*m_domError); + } + + if (m_transaction) + m_transaction->finishedDispatchEventForRequest(*this); + + return dontPreventDefault; } -#endif -void IDBRequest::onSuccess(PassRefPtr<SharedBuffer> valueBuffer, PassRefPtr<IDBKey> prpPrimaryKey, const IDBKeyPath& keyPath) +void IDBRequest::uncaughtExceptionInEventHandler() { - LOG(StorageAPI, "IDBRequest::onSuccess(SharedBuffer, IDBKey, IDBKeyPath)"); - if (!shouldEnqueueEvent()) - return; + LOG(IndexedDB, "IDBRequest::uncaughtExceptionInEventHandler"); -#ifndef NDEBUG - ASSERT(keyPath == effectiveObjectStore(m_source)->keyPath()); -#endif - DOMRequestState::Scope scope(m_requestState); - Deprecated::ScriptValue value = deserializeIDBValueBuffer(requestState(), valueBuffer); + ASSERT(currentThread() == originThreadID()); - RefPtr<IDBKey> primaryKey = prpPrimaryKey; -#ifndef NDEBUG - RefPtr<IDBKey> expectedKey = createIDBKeyFromScriptValueAndKeyPath(requestState(), value, keyPath); - ASSERT(!expectedKey || expectedKey->isEqual(primaryKey.get())); -#endif - bool injected = injectIDBKeyIntoScriptValue(requestState(), primaryKey, value, keyPath); - ASSERT_UNUSED(injected, injected); - onSuccessInternal(value); + if (m_transaction && m_idbError.code() != IDBDatabaseException::AbortError) + m_transaction->abortDueToFailedRequest(DOMError::create(IDBDatabaseException::getErrorName(IDBDatabaseException::AbortError), ASCIILiteral("IDBTransaction will abort due to uncaught exception in an event handler"))); } -void IDBRequest::onSuccess(int64_t value) +void IDBRequest::setResult(const IDBKeyData& keyData) { - LOG(StorageAPI, "IDBRequest::onSuccess(int64_t)"); - if (!shouldEnqueueEvent()) + ASSERT(currentThread() == originThreadID()); + + auto* context = scriptExecutionContext(); + if (!context) return; - return onSuccessInternal(SerializedScriptValue::numberValue(value)); -} -void IDBRequest::onSuccess() -{ - LOG(StorageAPI, "IDBRequest::onSuccess()"); - if (!shouldEnqueueEvent()) + auto* state = context->execState(); + if (!state) return; - return onSuccessInternal(SerializedScriptValue::undefinedValue()); + + // FIXME: This conversion should be done lazily, when script needs the JSValues, so that global object + // of the IDBRequest wrapper can be used, rather than the lexicalGlobalObject. + VM& vm = context->vm(); + JSLockHolder lock(vm); + m_result = Result { JSC::Strong<JSC::Unknown> { vm, toJS<IDLIDBKeyData>(*state, *jsCast<JSDOMGlobalObject*>(state->lexicalGlobalObject()), keyData) } }; } -void IDBRequest::onSuccessInternal(PassRefPtr<SerializedScriptValue> value) +void IDBRequest::setResult(const Vector<IDBKeyData>& keyDatas) { - ASSERT(!m_contextStopped); - DOMRequestState::Scope scope(m_requestState); - return onSuccessInternal(deserializeIDBValue(requestState(), value)); + ASSERT(currentThread() == originThreadID()); + + auto* context = scriptExecutionContext(); + if (!context) + return; + + auto* state = context->execState(); + if (!state) + return; + + // FIXME: This conversion should be done lazily, when script needs the JSValues, so that global object + // of the IDBRequest wrapper can be used, rather than the lexicalGlobalObject. + VM& vm = context->vm(); + JSLockHolder lock(vm); + m_result = Result { JSC::Strong<JSC::Unknown> { vm, toJS<IDLSequence<IDLIDBKeyData>>(*state, *jsCast<JSDOMGlobalObject*>(state->lexicalGlobalObject()), keyDatas) } }; } -void IDBRequest::onSuccessInternal(const Deprecated::ScriptValue& value) +void IDBRequest::setResult(const Vector<IDBValue>& values) { - m_result = IDBAny::create(value); - if (m_pendingCursor) { - m_pendingCursor->close(); - m_pendingCursor.clear(); - } - enqueueEvent(createSuccessEvent()); + ASSERT(currentThread() == originThreadID()); + + auto* context = scriptExecutionContext(); + if (!context) + return; + + auto* state = context->execState(); + if (!state) + return; + + // FIXME: This conversion should be done lazily, when script needs the JSValues, so that global object + // of the IDBRequest wrapper can be used, rather than the lexicalGlobalObject. + VM& vm = context->vm(); + JSLockHolder lock(vm); + m_result = Result { JSC::Strong<JSC::Unknown> { vm, toJS<IDLSequence<IDLIDBValue>>(*state, *jsCast<JSDOMGlobalObject*>(state->lexicalGlobalObject()), values) } }; } -void IDBRequest::onSuccess(PassRefPtr<IDBKey> key, PassRefPtr<IDBKey> primaryKey, PassRefPtr<SharedBuffer> buffer) +void IDBRequest::setResult(uint64_t number) { - LOG(StorageAPI, "IDBRequest::onSuccess(key, primaryKey, value)"); - if (!shouldEnqueueEvent()) + ASSERT(currentThread() == originThreadID()); + + auto* context = scriptExecutionContext(); + if (!context) return; - DOMRequestState::Scope scope(m_requestState); - Deprecated::ScriptValue value = deserializeIDBValueBuffer(requestState(), buffer); - ASSERT(m_pendingCursor); - setResultCursor(m_pendingCursor.release(), key, primaryKey, value); - enqueueEvent(createSuccessEvent()); + m_result = Result { JSC::Strong<JSC::Unknown> { context->vm(), toJS<IDLUnrestrictedDouble>(number) } }; } -bool IDBRequest::hasPendingActivity() const +void IDBRequest::setResultToStructuredClone(const IDBValue& value) { - // 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 m_hasPendingActivity && !m_contextStopped; + ASSERT(currentThread() == originThreadID()); + + LOG(IndexedDB, "IDBRequest::setResultToStructuredClone"); + + auto* context = scriptExecutionContext(); + if (!context) + return; + + auto* state = context->execState(); + if (!state) + return; + + // FIXME: This conversion should be done lazily, when script needs the JSValues, so that global object + // of the IDBRequest wrapper can be used, rather than the lexicalGlobalObject. + VM& vm = context->vm(); + JSLockHolder lock(vm); + m_result = Result { JSC::Strong<JSC::Unknown> { vm, toJS<IDLIDBValue>(*state, *jsCast<JSDOMGlobalObject*>(state->lexicalGlobalObject()), value) } }; } -void IDBRequest::stop() +void IDBRequest::setResultToUndefined() { - if (m_contextStopped) + ASSERT(currentThread() == originThreadID()); + + auto* context = scriptExecutionContext(); + if (!context) return; - m_contextStopped = true; - m_requestState.clear(); - if (m_readyState == PENDING) - markEarlyDeath(); + m_result = Result { JSC::Strong<JSC::Unknown> { context->vm(), JSC::jsUndefined() } }; } -EventTargetInterface IDBRequest::eventTargetInterface() const +IDBCursor* IDBRequest::resultCursor() { - return IDBRequestEventTargetInterfaceType; + ASSERT(currentThread() == originThreadID()); + + if (!m_result) + return nullptr; + + return WTF::switchOn(m_result.value(), + [] (const RefPtr<IDBCursor>& cursor) -> IDBCursor* { return cursor.get(); }, + [] (const auto&) -> IDBCursor* { return nullptr; } + ); } -bool IDBRequest::dispatchEvent(PassRefPtr<Event> event) +void IDBRequest::willIterateCursor(IDBCursor& cursor) { - LOG(StorageAPI, "IDBRequest::dispatchEvent"); - ASSERT(m_readyState == PENDING); - ASSERT(!m_contextStopped); - ASSERT(m_hasPendingActivity); - ASSERT(m_enqueuedEvents.size()); + ASSERT(currentThread() == originThreadID()); + ASSERT(isDone()); ASSERT(scriptExecutionContext()); - ASSERT(event->target() == this); - ASSERT_WITH_MESSAGE(m_readyState < DONE, "When dispatching event %s, m_readyState < DONE(%d), was %d", event->type().string().utf8().data(), DONE, m_readyState); - - DOMRequestState::Scope scope(m_requestState); + ASSERT(m_transaction); + ASSERT(!m_pendingCursor); + ASSERT(&cursor == resultCursor()); + ASSERT(!m_cursorRequestNotifier); - if (event->type() != eventNames().blockedEvent) - m_readyState = DONE; + m_pendingCursor = &cursor; + m_hasPendingActivity = true; + m_result = std::nullopt; + m_readyState = ReadyState::Pending; + m_domError = nullptr; + m_idbError = { }; - for (size_t i = 0; i < m_enqueuedEvents.size(); ++i) { - if (m_enqueuedEvents[i].get() == event.get()) - m_enqueuedEvents.remove(i); - } + m_cursorRequestNotifier = std::make_unique<ScopeGuard>([this]() { + m_pendingCursor->decrementOutstandingRequestCount(); + }); +} - Vector<RefPtr<EventTarget>> targets; - targets.append(this); - if (m_transaction && !m_preventPropagation) { - targets.append(m_transaction); - // If there ever are events that are associated with a database but - // that do not have a transaction, then this will not work and we need - // this object to actually hold a reference to the database (to ensure - // it stays alive). - targets.append(m_transaction->db()); - } +void IDBRequest::didOpenOrIterateCursor(const IDBResultData& resultData) +{ + ASSERT(currentThread() == originThreadID()); + ASSERT(m_pendingCursor); - // Cursor properties should not updated until the success event is being dispatched. - RefPtr<IDBCursor> cursorToNotify; - if (event->type() == eventNames().successEvent) { - cursorToNotify = getResultCursor(); - if (cursorToNotify) { - cursorToNotify->setValueReady(requestState(), m_cursorKey.release(), m_cursorPrimaryKey.release(), m_cursorValue); - m_cursorValue.clear(); - } - } + m_result = std::nullopt; - if (event->type() == eventNames().upgradeneededEvent) { - ASSERT(!m_didFireUpgradeNeededEvent); - m_didFireUpgradeNeededEvent = true; + if (resultData.type() == IDBResultType::IterateCursorSuccess || resultData.type() == IDBResultType::OpenCursorSuccess) { + m_pendingCursor->setGetResult(*this, resultData.getResult()); + if (resultData.getResult().isDefined()) + m_result = Result { m_pendingCursor }; } - // FIXME: When we allow custom event dispatching, this will probably need to change. - ASSERT_WITH_MESSAGE(event->type() == eventNames().successEvent || event->type() == eventNames().errorEvent || event->type() == eventNames().blockedEvent || event->type() == eventNames().upgradeneededEvent, "event type was %s", event->type().string().utf8().data()); - const bool setTransactionActive = m_transaction && (event->type() == eventNames().successEvent || event->type() == eventNames().upgradeneededEvent || (event->type() == eventNames().errorEvent && m_errorCode != IDBDatabaseException::AbortError)); - - if (setTransactionActive) - m_transaction->setActive(true); + m_cursorRequestNotifier = nullptr; + m_pendingCursor = nullptr; - bool dontPreventDefault = IDBEventDispatcher::dispatch(event.get(), targets); + completeRequestAndDispatchEvent(resultData); +} - if (m_transaction) { - if (m_readyState == DONE) - m_transaction->unregisterRequest(this); +void IDBRequest::completeRequestAndDispatchEvent(const IDBResultData& resultData) +{ + ASSERT(currentThread() == originThreadID()); - // 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 (event->type() == eventNames().errorEvent && dontPreventDefault && !m_requestAborted) { - m_transaction->setError(m_error, m_errorMessage); - m_transaction->abort(IGNORE_EXCEPTION); - } + m_readyState = ReadyState::Done; - // If this was the last request in the transaction's list, it may commit here. - if (setTransactionActive) - m_transaction->setActive(false); - } + m_idbError = resultData.error(); + if (!m_idbError.isNull()) + onError(); + else + onSuccess(); +} - if (cursorToNotify) - cursorToNotify->postSuccessHandlerCallback(); +void IDBRequest::onError() +{ + LOG(IndexedDB, "IDBRequest::onError"); - if (m_readyState == DONE && (!cursorToNotify || m_cursorFinished) && event->type() != eventNames().upgradeneededEvent) - m_hasPendingActivity = false; + ASSERT(currentThread() == originThreadID()); + ASSERT(!m_idbError.isNull()); - return dontPreventDefault; + m_domError = DOMError::create(m_idbError.name(), m_idbError.message()); + enqueueEvent(Event::create(eventNames().errorEvent, true, true)); } -void IDBRequest::uncaughtExceptionInEventHandler() +void IDBRequest::onSuccess() { - if (m_transaction && !m_requestAborted) { - m_transaction->setError(DOMError::create(IDBDatabaseException::getErrorName(IDBDatabaseException::AbortError)), "Uncaught exception in event handler."); - m_transaction->abort(IGNORE_EXCEPTION); - } -} + LOG(IndexedDB, "IDBRequest::onSuccess"); + ASSERT(currentThread() == originThreadID()); -void IDBRequest::transactionDidFinishAndDispatch() -{ - ASSERT(m_transaction); - ASSERT(m_transaction->isVersionChange()); - ASSERT(m_readyState == DONE); - ASSERT(scriptExecutionContext()); - m_transaction.clear(); - m_readyState = PENDING; + enqueueEvent(Event::create(eventNames().successEvent, false, false)); } -void IDBRequest::enqueueEvent(PassRefPtr<Event> event) +void IDBRequest::setResult(Ref<IDBDatabase>&& database) { - ASSERT(m_readyState == PENDING || m_readyState == DONE); - - if (m_contextStopped || !scriptExecutionContext()) - return; - - ASSERT_WITH_MESSAGE(m_readyState == PENDING || m_didFireUpgradeNeededEvent, "When queueing event %s, m_readyState was %d", event->type().string().utf8().data(), m_readyState); - - event->setTarget(this); + ASSERT(currentThread() == originThreadID()); - if (scriptExecutionContext()->eventQueue().enqueueEvent(event.get())) - m_enqueuedEvents.append(event); + m_result = Result { RefPtr<IDBDatabase> { WTFMove(database) } }; } } // namespace WebCore -#endif +#endif // ENABLE(INDEXED_DATABASE) |