Home | History | Annotate | Download | only in indexeddb
      1 /*
      2  * Copyright (C) 2010 Google Inc. All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions
      6  * are met:
      7  *
      8  * 1.  Redistributions of source code must retain the above copyright
      9  *     notice, this list of conditions and the following disclaimer.
     10  * 2.  Redistributions in binary form must reproduce the above copyright
     11  *     notice, this list of conditions and the following disclaimer in the
     12  *     documentation and/or other materials provided with the distribution.
     13  *
     14  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
     15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
     16  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     17  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
     18  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
     19  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     20  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
     21  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
     23  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     24  */
     25 
     26 #include "config.h"
     27 #include "modules/indexeddb/IDBDatabase.h"
     28 
     29 #include "bindings/core/v8/ExceptionState.h"
     30 #include "bindings/core/v8/ExceptionStatePlaceholder.h"
     31 #include "bindings/core/v8/Nullable.h"
     32 #include "bindings/core/v8/SerializedScriptValue.h"
     33 #include "bindings/modules/v8/IDBBindingUtilities.h"
     34 #include "core/dom/ExecutionContext.h"
     35 #include "core/events/EventQueue.h"
     36 #include "core/inspector/ScriptCallStack.h"
     37 #include "modules/indexeddb/IDBAny.h"
     38 #include "modules/indexeddb/IDBEventDispatcher.h"
     39 #include "modules/indexeddb/IDBHistograms.h"
     40 #include "modules/indexeddb/IDBIndex.h"
     41 #include "modules/indexeddb/IDBKeyPath.h"
     42 #include "modules/indexeddb/IDBTracing.h"
     43 #include "modules/indexeddb/IDBVersionChangeEvent.h"
     44 #include "modules/indexeddb/WebIDBDatabaseCallbacksImpl.h"
     45 #include "public/platform/Platform.h"
     46 #include "public/platform/WebIDBKeyPath.h"
     47 #include "public/platform/WebIDBTypes.h"
     48 #include "wtf/Atomics.h"
     49 #include <limits>
     50 
     51 using blink::WebIDBDatabase;
     52 
     53 namespace blink {
     54 
     55 const char IDBDatabase::indexDeletedErrorMessage[] = "The index or its object store has been deleted.";
     56 const char IDBDatabase::isKeyCursorErrorMessage[] = "The cursor is a key cursor.";
     57 const char IDBDatabase::noKeyOrKeyRangeErrorMessage[] = "No key or key range specified.";
     58 const char IDBDatabase::noSuchIndexErrorMessage[] = "The specified index was not found.";
     59 const char IDBDatabase::noSuchObjectStoreErrorMessage[] = "The specified object store was not found.";
     60 const char IDBDatabase::noValueErrorMessage[] = "The cursor is being iterated or has iterated past its end.";
     61 const char IDBDatabase::notValidKeyErrorMessage[] = "The parameter is not a valid key.";
     62 const char IDBDatabase::notVersionChangeTransactionErrorMessage[] = "The database is not running a version change transaction.";
     63 const char IDBDatabase::objectStoreDeletedErrorMessage[] = "The object store has been deleted.";
     64 const char IDBDatabase::requestNotFinishedErrorMessage[] = "The request has not finished.";
     65 const char IDBDatabase::sourceDeletedErrorMessage[] = "The cursor's source or effective object store has been deleted.";
     66 const char IDBDatabase::transactionInactiveErrorMessage[] = "The transaction is not active.";
     67 const char IDBDatabase::transactionFinishedErrorMessage[] = "The transaction has finished.";
     68 const char IDBDatabase::transactionReadOnlyErrorMessage[] = "The transaction is read-only.";
     69 const char IDBDatabase::databaseClosedErrorMessage[] = "The database connection is closed.";
     70 
     71 IDBDatabase* IDBDatabase::create(ExecutionContext* context, PassOwnPtr<WebIDBDatabase> database, IDBDatabaseCallbacks* callbacks)
     72 {
     73     IDBDatabase* idbDatabase = adoptRefCountedGarbageCollectedWillBeNoop(new IDBDatabase(context, database, callbacks));
     74     idbDatabase->suspendIfNeeded();
     75     return idbDatabase;
     76 }
     77 
     78 IDBDatabase::IDBDatabase(ExecutionContext* context, PassOwnPtr<WebIDBDatabase> backend, IDBDatabaseCallbacks* callbacks)
     79     : ActiveDOMObject(context)
     80     , m_backend(backend)
     81     , m_closePending(false)
     82     , m_contextStopped(false)
     83     , m_databaseCallbacks(callbacks)
     84 {
     85     m_databaseCallbacks->connect(this);
     86 }
     87 
     88 IDBDatabase::~IDBDatabase()
     89 {
     90     if (!m_closePending && m_backend)
     91         m_backend->close();
     92 }
     93 
     94 void IDBDatabase::trace(Visitor* visitor)
     95 {
     96     visitor->trace(m_versionChangeTransaction);
     97     visitor->trace(m_transactions);
     98 #if ENABLE(OILPAN)
     99     visitor->trace(m_enqueuedEvents);
    100 #endif
    101     visitor->trace(m_databaseCallbacks);
    102     EventTargetWithInlineData::trace(visitor);
    103 }
    104 
    105 int64_t IDBDatabase::nextTransactionId()
    106 {
    107     // Only keep a 32-bit counter to allow ports to use the other 32
    108     // bits of the id.
    109     AtomicallyInitializedStatic(int, currentTransactionId = 0);
    110     return atomicIncrement(&currentTransactionId);
    111 }
    112 
    113 void IDBDatabase::ackReceivedBlobs(const Vector<WebBlobInfo>* blobInfo)
    114 {
    115     ASSERT(blobInfo);
    116     if (!blobInfo->size() || !m_backend)
    117         return;
    118     Vector<WebBlobInfo>::const_iterator iter;
    119     Vector<String> uuids;
    120     uuids.reserveCapacity(blobInfo->size());
    121     for (iter = blobInfo->begin(); iter != blobInfo->end(); ++iter)
    122         uuids.append(iter->uuid());
    123     m_backend->ackReceivedBlobs(uuids);
    124 }
    125 
    126 void IDBDatabase::indexCreated(int64_t objectStoreId, const IDBIndexMetadata& metadata)
    127 {
    128     IDBDatabaseMetadata::ObjectStoreMap::iterator it = m_metadata.objectStores.find(objectStoreId);
    129     ASSERT_WITH_SECURITY_IMPLICATION(it != m_metadata.objectStores.end());
    130     it->value.indexes.set(metadata.id, metadata);
    131 }
    132 
    133 void IDBDatabase::indexDeleted(int64_t objectStoreId, int64_t indexId)
    134 {
    135     IDBDatabaseMetadata::ObjectStoreMap::iterator it = m_metadata.objectStores.find(objectStoreId);
    136     ASSERT_WITH_SECURITY_IMPLICATION(it != m_metadata.objectStores.end());
    137     it->value.indexes.remove(indexId);
    138 }
    139 
    140 void IDBDatabase::transactionCreated(IDBTransaction* transaction)
    141 {
    142     ASSERT(transaction);
    143     ASSERT(!m_transactions.contains(transaction->id()));
    144     m_transactions.add(transaction->id(), transaction);
    145 
    146     if (transaction->isVersionChange()) {
    147         ASSERT(!m_versionChangeTransaction);
    148         m_versionChangeTransaction = transaction;
    149     }
    150 }
    151 
    152 void IDBDatabase::transactionFinished(const IDBTransaction* transaction)
    153 {
    154     ASSERT(transaction);
    155     ASSERT(m_transactions.contains(transaction->id()));
    156     ASSERT(m_transactions.get(transaction->id()) == transaction);
    157     m_transactions.remove(transaction->id());
    158 
    159     if (transaction->isVersionChange()) {
    160         ASSERT(m_versionChangeTransaction == transaction);
    161         m_versionChangeTransaction = nullptr;
    162     }
    163 
    164     if (m_closePending && m_transactions.isEmpty())
    165         closeConnection();
    166 }
    167 
    168 void IDBDatabase::onAbort(int64_t transactionId, PassRefPtrWillBeRawPtr<DOMError> error)
    169 {
    170     ASSERT(m_transactions.contains(transactionId));
    171     m_transactions.get(transactionId)->onAbort(error);
    172 }
    173 
    174 void IDBDatabase::onComplete(int64_t transactionId)
    175 {
    176     ASSERT(m_transactions.contains(transactionId));
    177     m_transactions.get(transactionId)->onComplete();
    178 }
    179 
    180 PassRefPtrWillBeRawPtr<DOMStringList> IDBDatabase::objectStoreNames() const
    181 {
    182     RefPtrWillBeRawPtr<DOMStringList> objectStoreNames = DOMStringList::create();
    183     for (IDBDatabaseMetadata::ObjectStoreMap::const_iterator it = m_metadata.objectStores.begin(); it != m_metadata.objectStores.end(); ++it)
    184         objectStoreNames->append(it->value.name);
    185     objectStoreNames->sort();
    186     return objectStoreNames.release();
    187 }
    188 
    189 ScriptValue IDBDatabase::version(ScriptState* scriptState) const
    190 {
    191     int64_t intVersion = m_metadata.intVersion;
    192     if (intVersion == IDBDatabaseMetadata::NoIntVersion)
    193         return idbAnyToScriptValue(scriptState, IDBAny::createString(m_metadata.version));
    194 
    195     return idbAnyToScriptValue(scriptState, IDBAny::create(intVersion));
    196 }
    197 
    198 IDBObjectStore* IDBDatabase::createObjectStore(const String& name, const Dictionary& options, ExceptionState& exceptionState)
    199 {
    200     IDBKeyPath keyPath;
    201     bool autoIncrement = false;
    202     if (!options.isUndefinedOrNull()) {
    203         String keyPathString;
    204         Vector<String> keyPathArray;
    205         if (DictionaryHelper::get(options, "keyPath", keyPathArray))
    206             keyPath = IDBKeyPath(keyPathArray);
    207         else if (options.getWithUndefinedOrNullCheck("keyPath", keyPathString))
    208             keyPath = IDBKeyPath(keyPathString);
    209 
    210         DictionaryHelper::get(options, "autoIncrement", autoIncrement);
    211     }
    212 
    213     return createObjectStore(name, keyPath, autoIncrement, exceptionState);
    214 }
    215 
    216 IDBObjectStore* IDBDatabase::createObjectStore(const String& name, const IDBKeyPath& keyPath, bool autoIncrement, ExceptionState& exceptionState)
    217 {
    218     IDB_TRACE("IDBDatabase::createObjectStore");
    219     Platform::current()->histogramEnumeration("WebCore.IndexedDB.FrontEndAPICalls", IDBCreateObjectStoreCall, IDBMethodsMax);
    220     if (!m_versionChangeTransaction) {
    221         exceptionState.throwDOMException(InvalidStateError, IDBDatabase::notVersionChangeTransactionErrorMessage);
    222         return 0;
    223     }
    224     if (m_versionChangeTransaction->isFinished() || m_versionChangeTransaction->isFinishing()) {
    225         exceptionState.throwDOMException(TransactionInactiveError, IDBDatabase::transactionFinishedErrorMessage);
    226         return 0;
    227     }
    228     if (!m_versionChangeTransaction->isActive()) {
    229         exceptionState.throwDOMException(TransactionInactiveError, IDBDatabase::transactionInactiveErrorMessage);
    230         return 0;
    231     }
    232 
    233     if (containsObjectStore(name)) {
    234         exceptionState.throwDOMException(ConstraintError, "An object store with the specified name already exists.");
    235         return 0;
    236     }
    237 
    238     if (!keyPath.isNull() && !keyPath.isValid()) {
    239         exceptionState.throwDOMException(SyntaxError, "The keyPath option is not a valid key path.");
    240         return 0;
    241     }
    242 
    243     if (autoIncrement && ((keyPath.type() == IDBKeyPath::StringType && keyPath.string().isEmpty()) || keyPath.type() == IDBKeyPath::ArrayType)) {
    244         exceptionState.throwDOMException(InvalidAccessError, "The autoIncrement option was set but the keyPath option was empty or an array.");
    245         return 0;
    246     }
    247 
    248     if (!m_backend) {
    249         exceptionState.throwDOMException(InvalidStateError, IDBDatabase::databaseClosedErrorMessage);
    250         return 0;
    251     }
    252 
    253     int64_t objectStoreId = m_metadata.maxObjectStoreId + 1;
    254     m_backend->createObjectStore(m_versionChangeTransaction->id(), objectStoreId, name, keyPath, autoIncrement);
    255 
    256     IDBObjectStoreMetadata metadata(name, objectStoreId, keyPath, autoIncrement, WebIDBDatabase::minimumIndexId);
    257     IDBObjectStore* objectStore = IDBObjectStore::create(metadata, m_versionChangeTransaction.get());
    258     m_metadata.objectStores.set(metadata.id, metadata);
    259     ++m_metadata.maxObjectStoreId;
    260 
    261     m_versionChangeTransaction->objectStoreCreated(name, objectStore);
    262     return objectStore;
    263 }
    264 
    265 void IDBDatabase::deleteObjectStore(const String& name, ExceptionState& exceptionState)
    266 {
    267     IDB_TRACE("IDBDatabase::deleteObjectStore");
    268     Platform::current()->histogramEnumeration("WebCore.IndexedDB.FrontEndAPICalls", IDBDeleteObjectStoreCall, IDBMethodsMax);
    269     if (!m_versionChangeTransaction) {
    270         exceptionState.throwDOMException(InvalidStateError, IDBDatabase::notVersionChangeTransactionErrorMessage);
    271         return;
    272     }
    273     if (m_versionChangeTransaction->isFinished() || m_versionChangeTransaction->isFinishing()) {
    274         exceptionState.throwDOMException(TransactionInactiveError, IDBDatabase::transactionFinishedErrorMessage);
    275         return;
    276     }
    277     if (!m_versionChangeTransaction->isActive()) {
    278         exceptionState.throwDOMException(TransactionInactiveError, IDBDatabase::transactionInactiveErrorMessage);
    279         return;
    280     }
    281 
    282     int64_t objectStoreId = findObjectStoreId(name);
    283     if (objectStoreId == IDBObjectStoreMetadata::InvalidId) {
    284         exceptionState.throwDOMException(NotFoundError, "The specified object store was not found.");
    285         return;
    286     }
    287 
    288     if (!m_backend) {
    289         exceptionState.throwDOMException(InvalidStateError, IDBDatabase::databaseClosedErrorMessage);
    290         return;
    291     }
    292 
    293     m_backend->deleteObjectStore(m_versionChangeTransaction->id(), objectStoreId);
    294     m_versionChangeTransaction->objectStoreDeleted(name);
    295     m_metadata.objectStores.remove(objectStoreId);
    296 }
    297 
    298 IDBTransaction* IDBDatabase::transaction(ScriptState* scriptState, const Vector<String>& scope, const String& modeString, ExceptionState& exceptionState)
    299 {
    300     IDB_TRACE("IDBDatabase::transaction");
    301     Platform::current()->histogramEnumeration("WebCore.IndexedDB.FrontEndAPICalls", IDBTransactionCall, IDBMethodsMax);
    302     if (!scope.size()) {
    303         exceptionState.throwDOMException(InvalidAccessError, "The storeNames parameter was empty.");
    304         return 0;
    305     }
    306 
    307     WebIDBTransactionMode mode = IDBTransaction::stringToMode(modeString, exceptionState);
    308     if (exceptionState.hadException())
    309         return 0;
    310 
    311     if (m_versionChangeTransaction) {
    312         exceptionState.throwDOMException(InvalidStateError, "A version change transaction is running.");
    313         return 0;
    314     }
    315 
    316     if (m_closePending) {
    317         exceptionState.throwDOMException(InvalidStateError, "The database connection is closing.");
    318         return 0;
    319     }
    320 
    321     Vector<int64_t> objectStoreIds;
    322     for (size_t i = 0; i < scope.size(); ++i) {
    323         int64_t objectStoreId = findObjectStoreId(scope[i]);
    324         if (objectStoreId == IDBObjectStoreMetadata::InvalidId) {
    325             exceptionState.throwDOMException(NotFoundError, "One of the specified object stores was not found.");
    326             return 0;
    327         }
    328         objectStoreIds.append(objectStoreId);
    329     }
    330 
    331     if (!m_backend) {
    332         exceptionState.throwDOMException(InvalidStateError, IDBDatabase::databaseClosedErrorMessage);
    333         return 0;
    334     }
    335 
    336     int64_t transactionId = nextTransactionId();
    337     m_backend->createTransaction(transactionId, WebIDBDatabaseCallbacksImpl::create(m_databaseCallbacks).leakPtr(), objectStoreIds, mode);
    338 
    339     return IDBTransaction::create(scriptState, transactionId, scope, mode, this);
    340 }
    341 
    342 IDBTransaction* IDBDatabase::transaction(ScriptState* scriptState, const String& storeName, const String& mode, ExceptionState& exceptionState)
    343 {
    344     RefPtrWillBeRawPtr<DOMStringList> storeNames = DOMStringList::create();
    345     storeNames->append(storeName);
    346     return transaction(scriptState, storeNames, mode, exceptionState);
    347 }
    348 
    349 void IDBDatabase::forceClose()
    350 {
    351     for (TransactionMap::const_iterator::Values it = m_transactions.begin().values(), end = m_transactions.end().values(); it != end; ++it)
    352         (*it)->abort(IGNORE_EXCEPTION);
    353     this->close();
    354     enqueueEvent(Event::create(EventTypeNames::close));
    355 }
    356 
    357 void IDBDatabase::close()
    358 {
    359     IDB_TRACE("IDBDatabase::close");
    360     if (m_closePending)
    361         return;
    362 
    363     m_closePending = true;
    364 
    365     if (m_transactions.isEmpty())
    366         closeConnection();
    367 }
    368 
    369 void IDBDatabase::closeConnection()
    370 {
    371     ASSERT(m_closePending);
    372     ASSERT(m_transactions.isEmpty());
    373 
    374     if (m_backend) {
    375         m_backend->close();
    376         m_backend.clear();
    377     }
    378 
    379     if (m_contextStopped || !executionContext())
    380         return;
    381 
    382     EventQueue* eventQueue = executionContext()->eventQueue();
    383     // Remove any pending versionchange events scheduled to fire on this
    384     // connection. They would have been scheduled by the backend when another
    385     // connection called setVersion, but the frontend connection is being
    386     // closed before they could fire.
    387     for (size_t i = 0; i < m_enqueuedEvents.size(); ++i) {
    388         bool removed = eventQueue->cancelEvent(m_enqueuedEvents[i].get());
    389         ASSERT_UNUSED(removed, removed);
    390     }
    391 }
    392 
    393 void IDBDatabase::onVersionChange(int64_t oldVersion, int64_t newVersion)
    394 {
    395     IDB_TRACE("IDBDatabase::onVersionChange");
    396     if (m_contextStopped || !executionContext())
    397         return;
    398 
    399     if (m_closePending) {
    400         // If we're pending, that means there's a busy transaction. We won't
    401         // fire 'versionchange' but since we're not closing immediately the
    402         // back-end should still send out 'blocked'.
    403         m_backend->versionChangeIgnored();
    404         return;
    405     }
    406 
    407     Nullable<unsigned long long> newVersionNullable = (newVersion == IDBDatabaseMetadata::NoIntVersion) ? Nullable<unsigned long long>() : Nullable<unsigned long long>(newVersion);
    408     enqueueEvent(IDBVersionChangeEvent::create(EventTypeNames::versionchange, oldVersion, newVersionNullable));
    409 }
    410 
    411 void IDBDatabase::enqueueEvent(PassRefPtrWillBeRawPtr<Event> event)
    412 {
    413     ASSERT(!m_contextStopped);
    414     ASSERT(executionContext());
    415     EventQueue* eventQueue = executionContext()->eventQueue();
    416     event->setTarget(this);
    417     eventQueue->enqueueEvent(event.get());
    418     m_enqueuedEvents.append(event);
    419 }
    420 
    421 bool IDBDatabase::dispatchEvent(PassRefPtrWillBeRawPtr<Event> event)
    422 {
    423     IDB_TRACE("IDBDatabase::dispatchEvent");
    424     if (m_contextStopped || !executionContext())
    425         return false;
    426     ASSERT(event->type() == EventTypeNames::versionchange || event->type() == EventTypeNames::close);
    427     for (size_t i = 0; i < m_enqueuedEvents.size(); ++i) {
    428         if (m_enqueuedEvents[i].get() == event.get())
    429             m_enqueuedEvents.remove(i);
    430     }
    431 
    432     bool result = EventTarget::dispatchEvent(event.get());
    433     if (event->type() == EventTypeNames::versionchange && !m_closePending && m_backend)
    434         m_backend->versionChangeIgnored();
    435     return result;
    436 }
    437 
    438 int64_t IDBDatabase::findObjectStoreId(const String& name) const
    439 {
    440     for (IDBDatabaseMetadata::ObjectStoreMap::const_iterator it = m_metadata.objectStores.begin(); it != m_metadata.objectStores.end(); ++it) {
    441         if (it->value.name == name) {
    442             ASSERT(it->key != IDBObjectStoreMetadata::InvalidId);
    443             return it->key;
    444         }
    445     }
    446     return IDBObjectStoreMetadata::InvalidId;
    447 }
    448 
    449 bool IDBDatabase::hasPendingActivity() const
    450 {
    451     // The script wrapper must not be collected before the object is closed or
    452     // we can't fire a "versionchange" event to let script manually close the connection.
    453     return !m_closePending && hasEventListeners() && !m_contextStopped;
    454 }
    455 
    456 void IDBDatabase::stop()
    457 {
    458     m_contextStopped = true;
    459 
    460     // Immediately close the connection to the back end. Don't attempt a
    461     // normal close() since that may wait on transactions which require a
    462     // round trip to the back-end to abort.
    463     if (m_backend) {
    464         m_backend->close();
    465         m_backend.clear();
    466     }
    467 }
    468 
    469 const AtomicString& IDBDatabase::interfaceName() const
    470 {
    471     return EventTargetNames::IDBDatabase;
    472 }
    473 
    474 ExecutionContext* IDBDatabase::executionContext() const
    475 {
    476     return ActiveDOMObject::executionContext();
    477 }
    478 
    479 } // namespace blink
    480