/* * 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/IDBPendingTransactionMonitor.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) { } IDBRequest::~IDBRequest() { ASSERT(m_readyState == DONE || m_readyState == EarlyDeath || !executionContext()); ASSERT(!m_blobInfo || m_blobInfo->size() == 0); } void IDBRequest::dispose() { 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 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 value, PassOwnPtr > blobInfo) { ASSERT(m_readyState == PENDING); m_cursorKey = key; m_cursorPrimaryKey = primaryKey; m_cursorValue = value; setBlobInfo(blobInfo); onSuccessInternal(IDBAny::create(cursor)); } void IDBRequest::setBlobInfo(PassOwnPtr> blobInfo) { ASSERT(!m_blobInfo); m_blobInfo = blobInfo; if (m_blobInfo && m_blobInfo->size() > 0) V8PerIsolateData::from(scriptState()->isolate())->ensureIDBPendingTransactionMonitor()->registerRequest(*this); } void IDBRequest::handleBlobAcks() { if (m_blobInfo.get() && m_blobInfo->size()) { m_transaction->db()->ackReceivedBlobs(m_blobInfo.get()); m_blobInfo.clear(); V8PerIsolateData::from(scriptState()->isolate())->ensureIDBPendingTransactionMonitor()->unregisterRequest(*this); } } 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 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& stringList) { IDB_TRACE("IDBRequest::onSuccess(StringList)"); if (!shouldEnqueueEvent()) return; RefPtrWillBeRawPtr 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 backend, IDBKey* key, IDBKey* primaryKey, PassRefPtr value, PassOwnPtr > 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 valueBuffer, PassOwnPtr > 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(); } setBlobInfo(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 prpValueBuffer, PassOwnPtr > blobInfo, IDBKey* prpPrimaryKey, const IDBKeyPath& keyPath) { IDB_TRACE("IDBRequest::onSuccess(SharedBuffer, IDBKey, IDBKeyPath)"); if (!shouldEnqueueEvent()) return; ASSERT(keyPath == effectiveObjectStore(m_source)->metadata().keyPath); RefPtr valueBuffer = prpValueBuffer; IDBKey* primaryKey = prpPrimaryKey; setBlobInfo(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 value, PassOwnPtr > 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) { 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 > 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) { if (m_blobInfo && m_blobInfo->size() > 0) V8PerIsolateData::from(scriptState()->isolate())->ensureIDBPendingTransactionMonitor()->unregisterRequest(*this); 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) { 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