blob: 07bc48ab4cd27a47e59d6b039639198cf22c6885 [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 "modules/indexeddb/IDBRequest.h"
#include "bindings/core/v8/ExceptionState.h"
#include "bindings/core/v8/ExceptionStatePlaceholder.h"
#include "bindings/modules/v8/IDBBindingUtilities.h"
#include "core/dom/ExecutionContext.h"
#include "core/events/EventQueue.h"
#include "modules/IndexedDBNames.h"
#include "modules/indexeddb/IDBCursorWithValue.h"
#include "modules/indexeddb/IDBDatabase.h"
#include "modules/indexeddb/IDBEventDispatcher.h"
#include "modules/indexeddb/IDBTracing.h"
#include "platform/SharedBuffer.h"
#include "public/platform/WebBlobInfo.h"
using blink::WebIDBCursor;
namespace blink {
IDBRequest* IDBRequest::create(ScriptState* scriptState, IDBAny* source, IDBTransaction* transaction)
{
IDBRequest* request = adoptRefCountedGarbageCollectedWillBeNoop(new IDBRequest(scriptState, source, transaction));
request->suspendIfNeeded();
// Requests associated with IDBFactory (open/deleteDatabase/getDatabaseNames) are not associated with transactions.
if (transaction)
transaction->registerRequest(request);
return request;
}
IDBRequest::IDBRequest(ScriptState* scriptState, IDBAny* source, IDBTransaction* transaction)
: ActiveDOMObject(scriptState->executionContext())
, m_contextStopped(false)
, m_transaction(transaction)
, m_readyState(PENDING)
, m_requestAborted(false)
, m_scriptState(scriptState)
, m_source(source)
, m_hasPendingActivity(true)
, m_cursorType(IndexedDB::CursorKeyAndValue)
, m_cursorDirection(WebIDBCursorDirectionNext)
, m_pendingCursor(nullptr)
, m_didFireUpgradeNeededEvent(false)
, m_preventPropagation(false)
, m_resultDirty(true)
{
ScriptWrappable::init(this);
}
IDBRequest::~IDBRequest()
{
ASSERT(m_readyState == DONE || m_readyState == EarlyDeath || !executionContext());
handleBlobAcks();
}
void IDBRequest::trace(Visitor* visitor)
{
visitor->trace(m_transaction);
visitor->trace(m_source);
visitor->trace(m_result);
visitor->trace(m_error);
#if ENABLE(OILPAN)
visitor->trace(m_enqueuedEvents);
#endif
visitor->trace(m_pendingCursor);
visitor->trace(m_cursorKey);
visitor->trace(m_cursorPrimaryKey);
EventTargetWithInlineData::trace(visitor);
}
ScriptValue IDBRequest::result(ExceptionState& exceptionState)
{
if (m_readyState != DONE) {
exceptionState.throwDOMException(InvalidStateError, IDBDatabase::requestNotFinishedErrorMessage);
return ScriptValue();
}
if (m_contextStopped || !executionContext())
return ScriptValue();
m_resultDirty = false;
ScriptValue value = idbAnyToScriptValue(m_scriptState.get(), m_result);
handleBlobAcks();
return value;
}
PassRefPtrWillBeRawPtr<DOMError> IDBRequest::error(ExceptionState& exceptionState) const
{
if (m_readyState != DONE) {
exceptionState.throwDOMException(InvalidStateError, IDBDatabase::requestNotFinishedErrorMessage);
return nullptr;
}
return m_error;
}
ScriptValue IDBRequest::source() const
{
if (m_contextStopped || !executionContext())
return ScriptValue();
return idbAnyToScriptValue(m_scriptState.get(), m_source);
}
const String& IDBRequest::readyState() const
{
ASSERT(m_readyState == PENDING || m_readyState == DONE);
if (m_readyState == PENDING)
return IndexedDBNames::pending;
return IndexedDBNames::done;
}
void IDBRequest::abort()
{
ASSERT(!m_requestAborted);
if (m_contextStopped || !executionContext())
return;
ASSERT(m_readyState == PENDING || m_readyState == DONE);
if (m_readyState == DONE)
return;
EventQueue* eventQueue = executionContext()->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_error.clear();
m_result.clear();
onError(DOMError::create(AbortError, "The transaction was aborted, so the request cannot be fulfilled."));
m_requestAborted = true;
}
void IDBRequest::setCursorDetails(IndexedDB::CursorType cursorType, WebIDBCursorDirection direction)
{
ASSERT(m_readyState == PENDING);
ASSERT(!m_pendingCursor);
m_cursorType = cursorType;
m_cursorDirection = direction;
}
void IDBRequest::setPendingCursor(IDBCursor* cursor)
{
ASSERT(m_readyState == DONE);
ASSERT(executionContext());
ASSERT(m_transaction);
ASSERT(!m_pendingCursor);
ASSERT(cursor == getResultCursor());
m_hasPendingActivity = true;
m_pendingCursor = cursor;
setResult(0);
m_readyState = PENDING;
m_error.clear();
m_transaction->registerRequest(this);
}
IDBCursor* IDBRequest::getResultCursor() const
{
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(IDBCursor* cursor, IDBKey* key, IDBKey* primaryKey, PassRefPtr<SharedBuffer> value, PassOwnPtr<Vector<WebBlobInfo> > blobInfo)
{
ASSERT(m_readyState == PENDING);
m_cursorKey = key;
m_cursorPrimaryKey = primaryKey;
m_cursorValue = value;
ASSERT(!m_blobInfo.get());
m_blobInfo = blobInfo;
onSuccessInternal(IDBAny::create(cursor));
}
void IDBRequest::handleBlobAcks()
{
if (m_blobInfo.get() && m_blobInfo->size()) {
m_transaction->db()->ackReceivedBlobs(m_blobInfo.get());
m_blobInfo.clear();
}
}
bool IDBRequest::shouldEnqueueEvent() const
{
if (m_contextStopped || !executionContext())
return false;
ASSERT(m_readyState == PENDING || m_readyState == DONE);
if (m_requestAborted)
return false;
ASSERT(m_readyState == PENDING);
ASSERT(!m_error && !m_result);
return true;
}
void IDBRequest::onError(PassRefPtrWillBeRawPtr<DOMError> error)
{
IDB_TRACE("IDBRequest::onError()");
if (!shouldEnqueueEvent())
return;
m_error = error;
setResult(IDBAny::createUndefined());
m_pendingCursor.clear();
enqueueEvent(Event::createCancelableBubble(EventTypeNames::error));
}
void IDBRequest::onSuccess(const Vector<String>& stringList)
{
IDB_TRACE("IDBRequest::onSuccess(StringList)");
if (!shouldEnqueueEvent())
return;
RefPtrWillBeRawPtr<DOMStringList> domStringList = DOMStringList::create();
for (size_t i = 0; i < stringList.size(); ++i)
domStringList->append(stringList[i]);
onSuccessInternal(IDBAny::create(domStringList.release()));
}
void IDBRequest::onSuccess(PassOwnPtr<WebIDBCursor> backend, IDBKey* key, IDBKey* primaryKey, PassRefPtr<SharedBuffer> value, PassOwnPtr<Vector<WebBlobInfo> > blobInfo)
{
IDB_TRACE("IDBRequest::onSuccess(IDBCursor)");
if (!shouldEnqueueEvent())
return;
ASSERT(!m_pendingCursor);
IDBCursor* cursor = 0;
switch (m_cursorType) {
case IndexedDB::CursorKeyOnly:
cursor = IDBCursor::create(backend, m_cursorDirection, this, m_source.get(), m_transaction.get());
break;
case IndexedDB::CursorKeyAndValue:
cursor = IDBCursorWithValue::create(backend, m_cursorDirection, this, m_source.get(), m_transaction.get());
break;
default:
ASSERT_NOT_REACHED();
}
setResultCursor(cursor, key, primaryKey, value, blobInfo);
}
void IDBRequest::onSuccess(IDBKey* idbKey)
{
IDB_TRACE("IDBRequest::onSuccess(IDBKey)");
if (!shouldEnqueueEvent())
return;
if (idbKey && idbKey->isValid())
onSuccessInternal(IDBAny::create(idbKey));
else
onSuccessInternal(IDBAny::createUndefined());
}
void IDBRequest::onSuccess(PassRefPtr<SharedBuffer> valueBuffer, PassOwnPtr<Vector<WebBlobInfo> > blobInfo)
{
IDB_TRACE("IDBRequest::onSuccess(SharedBuffer)");
if (!shouldEnqueueEvent())
return;
if (m_pendingCursor) {
// Value should be null, signifying the end of the cursor's range.
ASSERT(!valueBuffer.get());
ASSERT(!blobInfo->size());
m_pendingCursor->close();
m_pendingCursor.clear();
}
ASSERT(!m_blobInfo.get());
m_blobInfo = blobInfo;
onSuccessInternal(IDBAny::create(valueBuffer, m_blobInfo.get()));
}
#if ENABLE(ASSERT)
static IDBObjectStore* effectiveObjectStore(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<SharedBuffer> prpValueBuffer, PassOwnPtr<Vector<WebBlobInfo> > blobInfo, IDBKey* prpPrimaryKey, const IDBKeyPath& keyPath)
{
IDB_TRACE("IDBRequest::onSuccess(SharedBuffer, IDBKey, IDBKeyPath)");
if (!shouldEnqueueEvent())
return;
ASSERT(keyPath == effectiveObjectStore(m_source)->metadata().keyPath);
RefPtr<SharedBuffer> valueBuffer = prpValueBuffer;
IDBKey* primaryKey = prpPrimaryKey;
ASSERT(!m_blobInfo.get());
m_blobInfo = blobInfo;
#if ENABLE(ASSERT)
assertPrimaryKeyValidOrInjectable(m_scriptState.get(), valueBuffer, m_blobInfo.get(), primaryKey, keyPath);
#endif
onSuccessInternal(IDBAny::create(valueBuffer, m_blobInfo.get(), primaryKey, keyPath));
}
void IDBRequest::onSuccess(int64_t value)
{
IDB_TRACE("IDBRequest::onSuccess(int64_t)");
if (!shouldEnqueueEvent())
return;
onSuccessInternal(IDBAny::create(value));
}
void IDBRequest::onSuccess()
{
IDB_TRACE("IDBRequest::onSuccess()");
if (!shouldEnqueueEvent())
return;
onSuccessInternal(IDBAny::createUndefined());
}
void IDBRequest::onSuccessInternal(IDBAny* result)
{
ASSERT(!m_contextStopped);
ASSERT(!m_pendingCursor);
setResult(result);
enqueueEvent(Event::create(EventTypeNames::success));
}
void IDBRequest::setResult(IDBAny* result)
{
m_result = result;
m_resultDirty = true;
}
void IDBRequest::onSuccess(IDBKey* key, IDBKey* primaryKey, PassRefPtr<SharedBuffer> value, PassOwnPtr<Vector<WebBlobInfo> > blobInfo)
{
IDB_TRACE("IDBRequest::onSuccess(key, primaryKey, value)");
if (!shouldEnqueueEvent())
return;
ASSERT(m_pendingCursor);
setResultCursor(m_pendingCursor.release(), key, primaryKey, value, blobInfo);
}
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_hasPendingActivity && !m_contextStopped;
}
void IDBRequest::stop()
{
if (m_contextStopped)
return;
m_contextStopped = true;
if (m_readyState == PENDING) {
m_readyState = EarlyDeath;
if (m_transaction) {
m_transaction->unregisterRequest(this);
m_transaction.clear();
}
}
m_enqueuedEvents.clear();
if (m_source)
m_source->contextWillBeDestroyed();
if (m_result)
m_result->contextWillBeDestroyed();
if (m_pendingCursor)
m_pendingCursor->contextWillBeDestroyed();
}
const AtomicString& IDBRequest::interfaceName() const
{
return EventTargetNames::IDBRequest;
}
ExecutionContext* IDBRequest::executionContext() const
{
return ActiveDOMObject::executionContext();
}
bool IDBRequest::dispatchEvent(PassRefPtrWillBeRawPtr<Event> event)
{
IDB_TRACE("IDBRequest::dispatchEvent");
if (m_contextStopped || !executionContext())
return false;
ASSERT(m_readyState == PENDING);
ASSERT(m_hasPendingActivity);
ASSERT(m_enqueuedEvents.size());
ASSERT(event->target() == this);
ScriptState::Scope scope(m_scriptState.get());
if (event->type() != EventTypeNames::blocked)
m_readyState = DONE;
dequeueEvent(event.get());
WillBeHeapVector<RefPtrWillBeMember<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());
}
// Cursor properties should not be updated until the success event is being dispatched.
IDBCursor* cursorToNotify = 0;
if (event->type() == EventTypeNames::success) {
cursorToNotify = getResultCursor();
if (cursorToNotify)
cursorToNotify->setValueReady(m_cursorKey.release(), m_cursorPrimaryKey.release(), m_cursorValue.release(), m_blobInfo.release());
}
if (event->type() == EventTypeNames::upgradeneeded) {
ASSERT(!m_didFireUpgradeNeededEvent);
m_didFireUpgradeNeededEvent = true;
}
// FIXME: When we allow custom event dispatching, this will probably need to change.
ASSERT_WITH_MESSAGE(event->type() == EventTypeNames::success || event->type() == EventTypeNames::error || event->type() == EventTypeNames::blocked || event->type() == EventTypeNames::upgradeneeded, "event type was %s", event->type().utf8().data());
const bool setTransactionActive = m_transaction && (event->type() == EventTypeNames::success || event->type() == EventTypeNames::upgradeneeded || (event->type() == EventTypeNames::error && !m_requestAborted));
if (setTransactionActive)
m_transaction->setActive(true);
bool dontPreventDefault = IDBEventDispatcher::dispatch(event.get(), targets);
if (m_transaction) {
if (m_readyState == DONE)
m_transaction->unregisterRequest(this);
// 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() == EventTypeNames::error && dontPreventDefault && !m_requestAborted) {
m_transaction->setError(m_error);
m_transaction->abort(IGNORE_EXCEPTION);
}
// If this was the last request in the transaction's list, it may commit here.
if (setTransactionActive)
m_transaction->setActive(false);
}
if (cursorToNotify)
cursorToNotify->postSuccessHandlerCallback();
// An upgradeneeded event will always be followed by a success or error event, so must
// be kept alive.
if (m_readyState == DONE && event->type() != EventTypeNames::upgradeneeded)
m_hasPendingActivity = false;
return dontPreventDefault;
}
void IDBRequest::uncaughtExceptionInEventHandler()
{
if (m_transaction && !m_requestAborted) {
m_transaction->setError(DOMError::create(AbortError, "Uncaught exception in event handler."));
m_transaction->abort(IGNORE_EXCEPTION);
}
}
void IDBRequest::transactionDidFinishAndDispatch()
{
ASSERT(m_transaction);
ASSERT(m_transaction->isVersionChange());
ASSERT(m_didFireUpgradeNeededEvent);
ASSERT(m_readyState == DONE);
ASSERT(executionContext());
m_transaction.clear();
if (m_contextStopped)
return;
m_readyState = PENDING;
}
void IDBRequest::enqueueEvent(PassRefPtrWillBeRawPtr<Event> event)
{
ASSERT(m_readyState == PENDING || m_readyState == DONE);
if (m_contextStopped || !executionContext())
return;
ASSERT_WITH_MESSAGE(m_readyState == PENDING || m_didFireUpgradeNeededEvent, "When queueing event %s, m_readyState was %d", event->type().utf8().data(), m_readyState);
EventQueue* eventQueue = executionContext()->eventQueue();
event->setTarget(this);
// Keep track of enqueued events in case we need to abort prior to dispatch,
// in which case these must be cancelled. If the events not dispatched for
// other reasons they must be removed from this list via dequeueEvent().
if (eventQueue->enqueueEvent(event.get()))
m_enqueuedEvents.append(event);
}
void IDBRequest::dequeueEvent(Event* event)
{
for (size_t i = 0; i < m_enqueuedEvents.size(); ++i) {
if (m_enqueuedEvents[i].get() == event)
m_enqueuedEvents.remove(i);
}
}
} // namespace blink