| /* |
| * 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 |