IDBRequest.cpp   [plain text]


/*
 * Copyright (C) 2015, 2016 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.
 *
 * 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"
#include "IDBRequest.h"

#if ENABLE(INDEXED_DATABASE)

#include "DOMError.h"
#include "Event.h"
#include "EventNames.h"
#include "EventQueue.h"
#include "IDBBindingUtilities.h"
#include "IDBConnectionProxy.h"
#include "IDBCursor.h"
#include "IDBDatabase.h"
#include "IDBDatabaseException.h"
#include "IDBEventDispatcher.h"
#include "IDBIndex.h"
#include "IDBKeyData.h"
#include "IDBObjectStore.h"
#include "IDBResultData.h"
#include "Logging.h"
#include "ScopeGuard.h"
#include "ScriptExecutionContext.h"
#include "ThreadSafeDataBuffer.h"
#include <wtf/NeverDestroyed.h>

namespace WebCore {

Ref<IDBRequest> IDBRequest::create(ScriptExecutionContext& context, IDBObjectStore& objectStore, IDBTransaction& transaction)
{
    return adoptRef(*new IDBRequest(context, objectStore, transaction));
}

Ref<IDBRequest> IDBRequest::create(ScriptExecutionContext& context, IDBCursor& cursor, IDBTransaction& transaction)
{
    return adoptRef(*new IDBRequest(context, cursor, transaction));
}

Ref<IDBRequest> IDBRequest::createCount(ScriptExecutionContext& context, IDBIndex& index, IDBTransaction& transaction)
{
    return adoptRef(*new IDBRequest(context, index, transaction));
}

Ref<IDBRequest> IDBRequest::createGet(ScriptExecutionContext& context, IDBIndex& index, IndexedDB::IndexRecordType requestedRecordType, IDBTransaction& transaction)
{
    return adoptRef(*new IDBRequest(context, index, requestedRecordType, transaction));
}

IDBRequest::IDBRequest(ScriptExecutionContext& context, IDBClient::IDBConnectionProxy& connectionProxy)
    : IDBActiveDOMObject(&context)
    , m_resourceIdentifier(connectionProxy.serverConnectionIdentifier())
    , m_connectionProxy(connectionProxy)
{
    suspendIfNeeded();
}

IDBRequest::IDBRequest(ScriptExecutionContext& context, IDBObjectStore& objectStore, IDBTransaction& transaction)
    : IDBActiveDOMObject(&context)
    , m_transaction(&transaction)
    , m_resourceIdentifier(transaction.connectionProxy())
    , m_objectStoreSource(&objectStore)
    , m_connectionProxy(transaction.database().connectionProxy())
{
    suspendIfNeeded();
}

IDBRequest::IDBRequest(ScriptExecutionContext& context, IDBCursor& cursor, IDBTransaction& transaction)
    : IDBActiveDOMObject(&context)
    , m_transaction(&transaction)
    , m_resourceIdentifier(transaction.connectionProxy())
    , m_objectStoreSource(cursor.objectStore())
    , m_indexSource(cursor.index())
    , m_pendingCursor(&cursor)
    , m_connectionProxy(transaction.database().connectionProxy())
{
    suspendIfNeeded();

    cursor.setRequest(*this);
}

IDBRequest::IDBRequest(ScriptExecutionContext& context, IDBIndex& index, IDBTransaction& transaction)
    : IDBActiveDOMObject(&context)
    , m_transaction(&transaction)
    , m_resourceIdentifier(transaction.connectionProxy())
    , m_indexSource(&index)
    , m_connectionProxy(transaction.database().connectionProxy())
{
    suspendIfNeeded();
}

IDBRequest::IDBRequest(ScriptExecutionContext& context, IDBIndex& index, IndexedDB::IndexRecordType requestedRecordType, IDBTransaction& transaction)
    : IDBRequest(context, index, transaction)
{
    m_requestedIndexRecordType = requestedRecordType;
}

IDBRequest::~IDBRequest()
{
    ASSERT(currentThread() == originThreadID());

    if (m_cursorResult)
        m_cursorResult->clearRequest();
}

unsigned short IDBRequest::errorCode(ExceptionCode&) const
{
    ASSERT(currentThread() == originThreadID());

    return 0;
}

RefPtr<DOMError> IDBRequest::error(ExceptionCodeWithMessage& ec) const
{
    ASSERT(currentThread() == originThreadID());

    if (m_isDone)
        return m_domError;

    ec.code = IDBDatabaseException::InvalidStateError;
    ec.message = ASCIILiteral("Failed to read the 'error' property from 'IDBRequest': The request has not finished.");
    return nullptr;
}

void IDBRequest::setSource(IDBCursor& cursor)
{
    ASSERT(currentThread() == originThreadID());
    ASSERT(!m_cursorRequestNotifier);

    m_objectStoreSource = nullptr;
    m_indexSource = nullptr;
    m_cursorSource = &cursor;
    m_cursorRequestNotifier = std::make_unique<ScopeGuard>([this]() {
        ASSERT(m_cursorSource);
        m_cursorSource->decrementOutstandingRequestCount();
    });
}

void IDBRequest::setVersionChangeTransaction(IDBTransaction& transaction)
{
    ASSERT(currentThread() == originThreadID());
    ASSERT(!m_transaction);
    ASSERT(transaction.isVersionChange());
    ASSERT(!transaction.isFinishedOrFinishing());

    m_transaction = &transaction;
}

RefPtr<WebCore::IDBTransaction> IDBRequest::transaction() const
{
    ASSERT(currentThread() == originThreadID());
    return m_shouldExposeTransactionToDOM ? m_transaction : nullptr;
}

const String& IDBRequest::readyState() const
{
    ASSERT(currentThread() == originThreadID());

    static NeverDestroyed<String> pendingString(ASCIILiteral("pending"));
    static NeverDestroyed<String> doneString(ASCIILiteral("done"));
    return m_isDone ? doneString : pendingString;
}

uint64_t IDBRequest::sourceObjectStoreIdentifier() const
{
    ASSERT(currentThread() == originThreadID());

    if (m_objectStoreSource)
        return m_objectStoreSource->info().identifier();
    if (m_indexSource)
        return m_indexSource->info().objectStoreIdentifier();
    return 0;
}

uint64_t IDBRequest::sourceIndexIdentifier() const
{
    ASSERT(currentThread() == originThreadID());

    if (!m_indexSource)
        return 0;
    return m_indexSource->info().identifier();
}

IndexedDB::IndexRecordType IDBRequest::requestedIndexRecordType() const
{
    ASSERT(currentThread() == originThreadID());
    ASSERT(m_indexSource);

    return m_requestedIndexRecordType;
}

EventTargetInterface IDBRequest::eventTargetInterface() const
{
    ASSERT(currentThread() == originThreadID());

    return IDBRequestEventTargetInterfaceType;
}

const char* IDBRequest::activeDOMObjectName() const
{
    ASSERT(currentThread() == originThreadID());

    return "IDBRequest";
}

bool IDBRequest::canSuspendForDocumentSuspension() const
{
    ASSERT(currentThread() == originThreadID());
    return false;
}

bool IDBRequest::hasPendingActivity() const
{
    ASSERT(currentThread() == originThreadID());
    return m_hasPendingActivity;
}

void IDBRequest::stop()
{
    ASSERT(currentThread() == originThreadID());
    ASSERT(!m_contextStopped);

    cancelForStop();

    removeAllEventListeners();

    m_contextStopped = true;
}

void IDBRequest::cancelForStop()
{
    // The base IDBRequest class has nothing additional to do here.
}

void IDBRequest::enqueueEvent(Ref<Event>&& event)
{
    ASSERT(currentThread() == originThreadID());
    if (!scriptExecutionContext() || m_contextStopped)
        return;

    event->setTarget(this);
    scriptExecutionContext()->eventQueue().enqueueEvent(WTFMove(event));
}

bool IDBRequest::dispatchEvent(Event& event)
{
    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_isDone = true;

    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);

    if (dontPreventDefault && event.type() == eventNames().errorEvent && m_transaction && !m_transaction->isFinishedOrFinishing()) {
        ASSERT(m_domError);
        m_transaction->abortDueToFailedRequest(*m_domError);
    }

    return dontPreventDefault;
}

void IDBRequest::uncaughtExceptionInEventHandler()
{
    LOG(IndexedDB, "IDBRequest::uncaughtExceptionInEventHandler");

    ASSERT(currentThread() == originThreadID());

    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::setResult(const IDBKeyData& keyData)
{
    ASSERT(currentThread() == originThreadID());

    auto* context = scriptExecutionContext();
    if (!context)
        return;

    auto* exec = context->execState();
    if (!exec)
        return;

    clearResult();
    m_scriptResult = { context->vm(), idbKeyDataToScriptValue(*exec, keyData) };
}

void IDBRequest::setResult(uint64_t number)
{
    ASSERT(currentThread() == originThreadID());

    auto* context = scriptExecutionContext();
    if (!context)
        return;

    clearResult();
    m_scriptResult = { context->vm(), JSC::jsNumber(number) };
}

void IDBRequest::setResultToStructuredClone(const IDBValue& value)
{
    ASSERT(currentThread() == originThreadID());

    LOG(IndexedDB, "IDBRequest::setResultToStructuredClone");

    auto* context = scriptExecutionContext();
    if (!context)
        return;

    auto* exec = context->execState();
    if (!exec)
        return;

    clearResult();
    m_scriptResult = { context->vm(), deserializeIDBValueToJSValue(*exec, value) };
}

void IDBRequest::clearResult()
{
    ASSERT(currentThread() == originThreadID());

    m_scriptResult = { };
    m_cursorResult = nullptr;
    m_databaseResult = nullptr;
}

void IDBRequest::setResultToUndefined()
{
    ASSERT(currentThread() == originThreadID());

    auto* context = scriptExecutionContext();
    if (!context)
        return;

    clearResult();
    m_scriptResult = { context->vm(), JSC::jsUndefined() };
}

IDBCursor* IDBRequest::resultCursor()
{
    ASSERT(currentThread() == originThreadID());

    return m_cursorResult.get();
}

void IDBRequest::willIterateCursor(IDBCursor& cursor)
{
    ASSERT(currentThread() == originThreadID());
    ASSERT(m_isDone);
    ASSERT(scriptExecutionContext());
    ASSERT(m_transaction);
    ASSERT(!m_pendingCursor);
    ASSERT(&cursor == resultCursor());
    ASSERT(!m_cursorRequestNotifier);

    m_pendingCursor = &cursor;
    m_hasPendingActivity = true;
    clearResult();
    m_isDone = false;
    m_domError = nullptr;
    m_idbError = { };

    m_cursorRequestNotifier = std::make_unique<ScopeGuard>([this]() {
        m_pendingCursor->decrementOutstandingRequestCount();
    });
}

void IDBRequest::didOpenOrIterateCursor(const IDBResultData& resultData)
{
    ASSERT(currentThread() == originThreadID());
    ASSERT(m_pendingCursor);

    clearResult();

    if (resultData.type() == IDBResultType::IterateCursorSuccess || resultData.type() == IDBResultType::OpenCursorSuccess) {
        m_pendingCursor->setGetResult(*this, resultData.getResult());
        if (resultData.getResult().isDefined())
            m_cursorResult = m_pendingCursor;
    }

    m_cursorRequestNotifier = nullptr;
    m_pendingCursor = nullptr;

    requestCompleted(resultData);
}

void IDBRequest::requestCompleted(const IDBResultData& resultData)
{
    ASSERT(currentThread() == originThreadID());

    m_isDone = true;

    m_idbError = resultData.error();
    if (!m_idbError.isNull())
        onError();
    else
        onSuccess();
}

void IDBRequest::onError()
{
    LOG(IndexedDB, "IDBRequest::onError");

    ASSERT(currentThread() == originThreadID());
    ASSERT(!m_idbError.isNull());

    m_domError = DOMError::create(m_idbError.name(), m_idbError.message());
    enqueueEvent(Event::create(eventNames().errorEvent, true, true));
}

void IDBRequest::onSuccess()
{
    LOG(IndexedDB, "IDBRequest::onSuccess");
    ASSERT(currentThread() == originThreadID());

    enqueueEvent(Event::create(eventNames().successEvent, false, false));
}

void IDBRequest::setResult(Ref<IDBDatabase>&& database)
{
    ASSERT(currentThread() == originThreadID());

    clearResult();
    m_databaseResult = WTFMove(database);
}

} // namespace WebCore

#endif // ENABLE(INDEXED_DATABASE)