blob: fa1c66734d4efb6e9b8f930dc4510997684ee44b [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 "config.h"
#include "IDBRequest.h"
#if ENABLE(INDEXED_DATABASE)
#include "EventException.h"
#include "EventListener.h"
#include "EventNames.h"
#include "EventQueue.h"
#include "IDBBindingUtilities.h"
#include "IDBCursorWithValue.h"
#include "IDBDatabase.h"
#include "IDBEventDispatcher.h"
#include "IDBTracing.h"
#include "IDBTransaction.h"
namespace WebCore {
PassRefPtr<IDBRequest> IDBRequest::create(ScriptExecutionContext* context, PassRefPtr<IDBAny> source, IDBTransaction* transaction)
{
RefPtr<IDBRequest> request(adoptRef(new IDBRequest(context, source, IDBTransactionBackendInterface::NormalTask, transaction)));
request->suspendIfNeeded();
return request.release();
}
PassRefPtr<IDBRequest> IDBRequest::create(ScriptExecutionContext* context, PassRefPtr<IDBAny> source, IDBTransactionBackendInterface::TaskType taskType, IDBTransaction* transaction)
{
RefPtr<IDBRequest> request(adoptRef(new IDBRequest(context, source, taskType, transaction)));
request->suspendIfNeeded();
return request.release();
}
IDBRequest::IDBRequest(ScriptExecutionContext* context, PassRefPtr<IDBAny> source, IDBTransactionBackendInterface::TaskType taskType, IDBTransaction* transaction)
: ActiveDOMObject(context, this)
, m_errorCode(0)
, m_source(source)
, m_taskType(taskType)
, m_transaction(transaction)
, m_readyState(PENDING)
, m_requestAborted(false)
, m_contextStopped(false)
, m_cursorType(IDBCursorBackendInterface::InvalidCursorType)
, m_cursorDirection(IDBCursor::NEXT)
, m_pendingCursor(0)
{
if (m_transaction) {
m_transaction->registerRequest(this);
}
}
IDBRequest::~IDBRequest()
{
ASSERT(m_readyState == DONE || m_readyState == EarlyDeath || !scriptExecutionContext());
}
PassRefPtr<IDBAny> IDBRequest::result(ExceptionCode& ec) const
{
if (m_readyState != DONE) {
ec = IDBDatabaseException::IDB_INVALID_STATE_ERR;
return 0;
}
return m_result;
}
PassRefPtr<DOMError> IDBRequest::error(ExceptionCode& ec) const
{
if (m_readyState != DONE) {
ec = IDBDatabaseException::IDB_INVALID_STATE_ERR;
return 0;
}
return m_error;
}
unsigned short IDBRequest::errorCode(ExceptionCode& ec) const
{
if (m_readyState != DONE) {
ec = IDBDatabaseException::IDB_INVALID_STATE_ERR;
return 0;
}
return m_errorCode;
}
String IDBRequest::webkitErrorMessage(ExceptionCode& ec) const
{
if (m_readyState != DONE) {
ec = IDBDatabaseException::IDB_INVALID_STATE_ERR;
return String();
}
return m_errorMessage;
}
PassRefPtr<IDBAny> IDBRequest::source() const
{
return m_source;
}
PassRefPtr<IDBTransaction> IDBRequest::transaction() const
{
return m_transaction;
}
const String& IDBRequest::readyState() const
{
ASSERT(m_readyState == PENDING || m_readyState == DONE);
DEFINE_STATIC_LOCAL(AtomicString, pending, ("pending"));
DEFINE_STATIC_LOCAL(AtomicString, done, ("done"));
if (m_readyState == PENDING)
return pending;
return done;
}
void IDBRequest::markEarlyDeath()
{
ASSERT(m_readyState == PENDING);
m_readyState = EarlyDeath;
if (m_transaction)
m_transaction->unregisterRequest(this);
}
void IDBRequest::abort()
{
ASSERT(!m_requestAborted);
if (m_contextStopped || !scriptExecutionContext())
return;
ASSERT(m_readyState == PENDING || m_readyState == DONE);
if (m_readyState == DONE)
return;
EventQueue* eventQueue = scriptExecutionContext()->eventQueue();
for (size_t i = 0; i < m_enqueuedEvents.size(); ++i) {
bool removed = eventQueue->cancelEvent(m_enqueuedEvents[i].get());
ASSERT_UNUSED(removed, removed);
}
m_enqueuedEvents.clear();
m_errorCode = 0;
m_error.clear();
m_errorMessage = String();
m_result.clear();
onError(IDBDatabaseError::create(IDBDatabaseException::IDB_ABORT_ERR, "The transaction was aborted, so the request cannot be fulfilled."));
m_requestAborted = true;
}
void IDBRequest::setCursorDetails(IDBCursorBackendInterface::CursorType cursorType, IDBCursor::Direction direction)
{
ASSERT(m_readyState == PENDING);
ASSERT(m_cursorType == IDBCursorBackendInterface::InvalidCursorType);
m_cursorType = cursorType;
m_cursorDirection = direction;
}
void IDBRequest::setPendingCursor(PassRefPtr<IDBCursor> cursor)
{
ASSERT(m_readyState == DONE);
ASSERT(scriptExecutionContext());
ASSERT(m_transaction);
ASSERT(!m_pendingCursor);
ASSERT(cursor == getResultCursor());
m_pendingCursor = cursor;
m_result.clear();
m_readyState = PENDING;
m_errorCode = 0;
m_error.clear();
m_errorMessage = String();
m_transaction->registerRequest(this);
}
PassRefPtr<IDBCursor> IDBRequest::getResultCursor()
{
if (!m_result)
return 0;
if (m_result->type() == IDBAny::IDBCursorType)
return m_result->idbCursor();
if (m_result->type() == IDBAny::IDBCursorWithValueType)
return m_result->idbCursorWithValue();
return 0;
}
void IDBRequest::setResultCursor(PassRefPtr<IDBCursor> cursor, PassRefPtr<IDBKey> key, PassRefPtr<IDBKey> primaryKey, PassRefPtr<SerializedScriptValue> value)
{
ASSERT(m_readyState == PENDING);
m_cursorKey = key;
m_cursorPrimaryKey = primaryKey;
m_cursorValue = value;
if (m_cursorType == IDBCursorBackendInterface::IndexKeyCursor) {
m_result = IDBAny::create(cursor);
return;
}
m_result = IDBAny::create(IDBCursorWithValue::fromCursor(cursor));
}
bool IDBRequest::shouldEnqueueEvent() 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;
}
void IDBRequest::onError(PassRefPtr<IDBDatabaseError> error)
{
IDB_TRACE("IDBRequest::onError()");
if (!shouldEnqueueEvent())
return;
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));
}
static PassRefPtr<Event> createSuccessEvent()
{
return Event::create(eventNames().successEvent, false, false);
}
void IDBRequest::onSuccess(PassRefPtr<DOMStringList> domStringList)
{
IDB_TRACE("IDBRequest::onSuccess(DOMStringList)");
if (!shouldEnqueueEvent())
return;
m_result = IDBAny::create(domStringList);
enqueueEvent(createSuccessEvent());
}
void IDBRequest::onSuccess(PassRefPtr<IDBCursorBackendInterface> backend, PassRefPtr<IDBKey> key, PassRefPtr<IDBKey> primaryKey, PassRefPtr<SerializedScriptValue> value)
{
IDB_TRACE("IDBRequest::onSuccess(IDBCursor)");
if (!shouldEnqueueEvent())
return;
ASSERT(m_cursorType != IDBCursorBackendInterface::InvalidCursorType);
RefPtr<IDBCursor> cursor;
if (m_cursorType == IDBCursorBackendInterface::IndexKeyCursor)
cursor = IDBCursor::create(backend, m_cursorDirection, this, m_source.get(), m_transaction.get());
else
cursor = IDBCursorWithValue::create(backend, m_cursorDirection, this, m_source.get(), m_transaction.get());
setResultCursor(cursor, key, primaryKey, value);
enqueueEvent(createSuccessEvent());
}
void IDBRequest::onSuccess(PassRefPtr<IDBDatabaseBackendInterface> backend)
{
IDB_TRACE("IDBRequest::onSuccess(IDBDatabase)");
if (!shouldEnqueueEvent())
return;
RefPtr<IDBDatabase> idbDatabase = IDBDatabase::create(scriptExecutionContext(), backend);
idbDatabase->registerFrontendCallbacks();
m_result = IDBAny::create(idbDatabase.release());
enqueueEvent(createSuccessEvent());
}
void IDBRequest::onSuccess(PassRefPtr<IDBKey> idbKey)
{
IDB_TRACE("IDBRequest::onSuccess(IDBKey)");
if (!shouldEnqueueEvent())
return;
if (idbKey && idbKey->isValid())
m_result = IDBAny::create(idbKey);
else
m_result = IDBAny::create(SerializedScriptValue::undefinedValue());
enqueueEvent(createSuccessEvent());
}
void IDBRequest::onSuccess(PassRefPtr<IDBTransactionBackendInterface> prpBackend)
{
IDB_TRACE("IDBRequest::onSuccess(IDBTransaction)");
RefPtr<IDBTransactionBackendInterface> backend = prpBackend;
if (m_contextStopped || !scriptExecutionContext()) {
// Should only be null in tests.
if (backend.get())
backend->abort();
return;
}
if (!shouldEnqueueEvent())
return;
RefPtr<IDBTransaction> frontend = IDBTransaction::create(scriptExecutionContext(), backend, IDBTransaction::VERSION_CHANGE, m_source->idbDatabase().get());
backend->setCallbacks(frontend.get());
m_transaction = frontend;
ASSERT(m_source->type() == IDBAny::IDBDatabaseType);
ASSERT(m_transaction->isVersionChange());
m_result = IDBAny::create(frontend.release());
enqueueEvent(createSuccessEvent());
}
void IDBRequest::onSuccess(PassRefPtr<SerializedScriptValue> serializedScriptValue)
{
IDB_TRACE("IDBRequest::onSuccess(SerializedScriptValue)");
if (!shouldEnqueueEvent())
return;
m_result = IDBAny::create(serializedScriptValue);
m_pendingCursor.clear();
enqueueEvent(createSuccessEvent());
}
#ifndef NDEBUG
static PassRefPtr<IDBObjectStore> effectiveObjectStore(PassRefPtr<IDBAny> source)
{
if (source->type() == IDBAny::IDBObjectStoreType)
return source->idbObjectStore();
if (source->type() == IDBAny::IDBIndexType)
return source->idbIndex()->objectStore();
ASSERT_NOT_REACHED();
return 0;
}
#endif
void IDBRequest::onSuccess(PassRefPtr<SerializedScriptValue> prpSerializedScriptValue, PassRefPtr<IDBKey> prpPrimaryKey, const IDBKeyPath& keyPath)
{
IDB_TRACE("IDBRequest::onSuccess(SerializedScriptValue, IDBKey, IDBKeyPath)");
if (!shouldEnqueueEvent())
return;
#ifndef NDEBUG
ASSERT(keyPath == effectiveObjectStore(m_source)->keyPath());
#endif
RefPtr<SerializedScriptValue> value = prpSerializedScriptValue;
RefPtr<IDBKey> primaryKey = prpPrimaryKey;
#ifndef NDEBUG
RefPtr<IDBKey> expectedKey = createIDBKeyFromSerializedValueAndKeyPath(value, keyPath);
ASSERT(!expectedKey || expectedKey->isEqual(primaryKey.get()));
#endif
RefPtr<SerializedScriptValue> valueAfterInjection = injectIDBKeyIntoSerializedValue(primaryKey, value, keyPath);
ASSERT(valueAfterInjection);
if (!valueAfterInjection) {
// Checks in put() ensure this should only happen if I/O error occurs.
onError(IDBDatabaseError::create(IDBDatabaseException::UNKNOWN_ERR, "Internal error inserting generated key into the object."));
return;
}
value = valueAfterInjection;
onSuccess(value.release());
}
void IDBRequest::onSuccess(PassRefPtr<IDBKey> key, PassRefPtr<IDBKey> primaryKey, PassRefPtr<SerializedScriptValue> value)
{
IDB_TRACE("IDBRequest::onSuccess(key, primaryKey, value)");
if (!shouldEnqueueEvent())
return;
ASSERT(m_pendingCursor);
setResultCursor(m_pendingCursor.release(), key, primaryKey, value);
enqueueEvent(createSuccessEvent());
}
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 m_readyState == PENDING || ActiveDOMObject::hasPendingActivity();
}
void IDBRequest::stop()
{
ActiveDOMObject::stop();
if (m_contextStopped)
return;
m_contextStopped = true;
if (m_readyState == PENDING)
markEarlyDeath();
}
void IDBRequest::onBlocked()
{
ASSERT_NOT_REACHED();
}
const AtomicString& IDBRequest::interfaceName() const
{
return eventNames().interfaceForIDBRequest;
}
ScriptExecutionContext* IDBRequest::scriptExecutionContext() const
{
return ActiveDOMObject::scriptExecutionContext();
}
bool IDBRequest::dispatchEvent(PassRefPtr<Event> event)
{
IDB_TRACE("IDBRequest::dispatchEvent");
ASSERT(m_readyState == PENDING);
ASSERT(!m_contextStopped);
ASSERT(m_enqueuedEvents.size());
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);
if (event->type() != eventNames().blockedEvent)
m_readyState = DONE;
for (size_t i = 0; i < m_enqueuedEvents.size(); ++i) {
if (m_enqueuedEvents[i].get() == event.get())
m_enqueuedEvents.remove(i);
}
Vector<RefPtr<EventTarget> > targets;
targets.append(this);
if (m_transaction) {
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());
}
// 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(m_cursorKey.release(), m_cursorPrimaryKey.release(), m_cursorValue.release());
}
// 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 was %s", event->type().string().utf8().data());
const bool setTransactionActive = m_transaction && (event->type() == eventNames().successEvent || (event->type() == eventNames().errorEvent && m_errorCode != IDBDatabaseException::IDB_ABORT_ERR));
if (setTransactionActive)
m_transaction->setActive(true);
bool dontPreventDefault = IDBEventDispatcher::dispatch(event.get(), targets);
if (setTransactionActive)
m_transaction->setActive(false);
if (cursorToNotify)
cursorToNotify->postSuccessHandlerCallback();
if (m_transaction) {
if (event->type() == eventNames().errorEvent && dontPreventDefault && !m_requestAborted) {
m_transaction->setError(m_error);
ExceptionCode unused;
m_transaction->abort(unused);
}
if (event->type() != eventNames().blockedEvent)
m_transaction->backend()->didCompleteTaskEvents();
if (m_readyState == DONE)
m_transaction->unregisterRequest(this);
}
return dontPreventDefault;
}
void IDBRequest::uncaughtExceptionInEventHandler()
{
if (m_transaction && !m_requestAborted) {
m_transaction->setError(DOMError::create(IDBDatabaseException::getErrorName(IDBDatabaseException::IDB_ABORT_ERR)));
ExceptionCode unused;
m_transaction->abort(unused);
}
}
void IDBRequest::enqueueEvent(PassRefPtr<Event> event)
{
ASSERT(m_readyState == PENDING || m_readyState == DONE);
if (m_contextStopped || !scriptExecutionContext())
return;
ASSERT_WITH_MESSAGE(m_readyState == PENDING, "When queueing event %s, m_readyState was %d", event->type().string().utf8().data(), m_readyState);
EventQueue* eventQueue = scriptExecutionContext()->eventQueue();
event->setTarget(this);
if (eventQueue->enqueueEvent(event.get()))
m_enqueuedEvents.append(event);
}
EventTargetData* IDBRequest::eventTargetData()
{
return &m_eventTargetData;
}
EventTargetData* IDBRequest::ensureEventTargetData()
{
return &m_eventTargetData;
}
} // namespace WebCore
#endif