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/v8/ExceptionState.h"
     30 #include "bindings/v8/ExceptionStatePlaceholder.h"
     31 #include "core/dom/DOMStringList.h"
     32 #include "core/dom/EventQueue.h"
     33 #include "core/dom/ScriptExecutionContext.h"
     34 #include "core/inspector/ScriptCallStack.h"
     35 #include "core/platform/HistogramSupport.h"
     36 #include "modules/indexeddb/IDBAny.h"
     37 #include "modules/indexeddb/IDBDatabaseCallbacks.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/IDBObjectStore.h"
     43 #include "modules/indexeddb/IDBTracing.h"
     44 #include "modules/indexeddb/IDBTransaction.h"
     45 #include "modules/indexeddb/IDBVersionChangeEvent.h"
     46 #include <limits>
     47 #include "wtf/Atomics.h"
     48 
     49 namespace WebCore {
     50 
     51 const char IDBDatabase::indexDeletedErrorMessage[] = "The index or its object store has been deleted.";
     52 const char IDBDatabase::isKeyCursorErrorMessage[] = "The cursor is a key cursor.";
     53 const char IDBDatabase::noKeyOrKeyRangeErrorMessage[] = "No key or key range specified.";
     54 const char IDBDatabase::noSuchIndexErrorMessage[] = "The specified index was not found.";
     55 const char IDBDatabase::noSuchObjectStoreErrorMessage[] = "The specified object store was not found.";
     56 const char IDBDatabase::noValueErrorMessage[] = "The cursor is being iterated or has iterated past its end.";
     57 const char IDBDatabase::notValidKeyErrorMessage[] = "The parameter is not a valid key.";
     58 const char IDBDatabase::notVersionChangeTransactionErrorMessage[] = "The database is not running a version change transaction.";
     59 const char IDBDatabase::objectStoreDeletedErrorMessage[] = "The object store has been deleted.";
     60 const char IDBDatabase::requestNotFinishedErrorMessage[] = "The request has not finished.";
     61 const char IDBDatabase::sourceDeletedErrorMessage[] = "The cursor's source or effective object store has been deleted.";
     62 const char IDBDatabase::transactionInactiveErrorMessage[] = "The transaction is not active.";
     63 const char IDBDatabase::transactionFinishedErrorMessage[] = "The transaction has finished.";
     64 
     65 PassRefPtr<IDBDatabase> IDBDatabase::create(ScriptExecutionContext* context, PassRefPtr<IDBDatabaseBackendInterface> database, PassRefPtr<IDBDatabaseCallbacks> callbacks)
     66 {
     67     RefPtr<IDBDatabase> idbDatabase(adoptRef(new IDBDatabase(context, database, callbacks)));
     68     idbDatabase->suspendIfNeeded();
     69     return idbDatabase.release();
     70 }
     71 
     72 IDBDatabase::IDBDatabase(ScriptExecutionContext* context, PassRefPtr<IDBDatabaseBackendInterface> backend, PassRefPtr<IDBDatabaseCallbacks> callbacks)
     73     : ActiveDOMObject(context)
     74     , m_backend(backend)
     75     , m_closePending(false)
     76     , m_contextStopped(false)
     77     , m_databaseCallbacks(callbacks)
     78 {
     79     // We pass a reference of this object before it can be adopted.
     80     relaxAdoptionRequirement();
     81     ScriptWrappable::init(this);
     82 }
     83 
     84 IDBDatabase::~IDBDatabase()
     85 {
     86     close();
     87 }
     88 
     89 int64_t IDBDatabase::nextTransactionId()
     90 {
     91     // Only keep a 32-bit counter to allow ports to use the other 32
     92     // bits of the id.
     93     AtomicallyInitializedStatic(int, currentTransactionId = 0);
     94     return atomicIncrement(&currentTransactionId);
     95 }
     96 
     97 void IDBDatabase::indexCreated(int64_t objectStoreId, const IDBIndexMetadata& metadata)
     98 {
     99     IDBDatabaseMetadata::ObjectStoreMap::iterator it = m_metadata.objectStores.find(objectStoreId);
    100     ASSERT(it != m_metadata.objectStores.end());
    101     it->value.indexes.set(metadata.id, metadata);
    102 }
    103 
    104 void IDBDatabase::indexDeleted(int64_t objectStoreId, int64_t indexId)
    105 {
    106     IDBDatabaseMetadata::ObjectStoreMap::iterator it = m_metadata.objectStores.find(objectStoreId);
    107     ASSERT(it != m_metadata.objectStores.end());
    108     it->value.indexes.remove(indexId);
    109 }
    110 
    111 void IDBDatabase::transactionCreated(IDBTransaction* transaction)
    112 {
    113     ASSERT(transaction);
    114     ASSERT(!m_transactions.contains(transaction->id()));
    115     m_transactions.add(transaction->id(), transaction);
    116 
    117     if (transaction->isVersionChange()) {
    118         ASSERT(!m_versionChangeTransaction);
    119         m_versionChangeTransaction = transaction;
    120     }
    121 }
    122 
    123 void IDBDatabase::transactionFinished(IDBTransaction* transaction)
    124 {
    125     ASSERT(transaction);
    126     ASSERT(m_transactions.contains(transaction->id()));
    127     ASSERT(m_transactions.get(transaction->id()) == transaction);
    128     m_transactions.remove(transaction->id());
    129 
    130     if (transaction->isVersionChange()) {
    131         ASSERT(m_versionChangeTransaction == transaction);
    132         m_versionChangeTransaction = 0;
    133     }
    134 
    135     if (m_closePending && m_transactions.isEmpty())
    136         closeConnection();
    137 }
    138 
    139 void IDBDatabase::onAbort(int64_t transactionId, PassRefPtr<DOMError> error)
    140 {
    141     ASSERT(m_transactions.contains(transactionId));
    142     m_transactions.get(transactionId)->onAbort(error);
    143 }
    144 
    145 void IDBDatabase::onComplete(int64_t transactionId)
    146 {
    147     ASSERT(m_transactions.contains(transactionId));
    148     m_transactions.get(transactionId)->onComplete();
    149 }
    150 
    151 PassRefPtr<DOMStringList> IDBDatabase::objectStoreNames() const
    152 {
    153     RefPtr<DOMStringList> objectStoreNames = DOMStringList::create();
    154     for (IDBDatabaseMetadata::ObjectStoreMap::const_iterator it = m_metadata.objectStores.begin(); it != m_metadata.objectStores.end(); ++it)
    155         objectStoreNames->append(it->value.name);
    156     objectStoreNames->sort();
    157     return objectStoreNames.release();
    158 }
    159 
    160 PassRefPtr<IDBAny> IDBDatabase::version() const
    161 {
    162     int64_t intVersion = m_metadata.intVersion;
    163     if (intVersion == IDBDatabaseMetadata::NoIntVersion)
    164         return IDBAny::createString(m_metadata.version);
    165     return IDBAny::create(intVersion);
    166 }
    167 
    168 PassRefPtr<IDBObjectStore> IDBDatabase::createObjectStore(const String& name, const Dictionary& options, ExceptionState& es)
    169 {
    170     IDBKeyPath keyPath;
    171     bool autoIncrement = false;
    172     if (!options.isUndefinedOrNull()) {
    173         String keyPathString;
    174         Vector<String> keyPathArray;
    175         if (options.get("keyPath", keyPathArray))
    176             keyPath = IDBKeyPath(keyPathArray);
    177         else if (options.getWithUndefinedOrNullCheck("keyPath", keyPathString))
    178             keyPath = IDBKeyPath(keyPathString);
    179 
    180         options.get("autoIncrement", autoIncrement);
    181     }
    182 
    183     return createObjectStore(name, keyPath, autoIncrement, es);
    184 }
    185 
    186 PassRefPtr<IDBObjectStore> IDBDatabase::createObjectStore(const String& name, const IDBKeyPath& keyPath, bool autoIncrement, ExceptionState& es)
    187 {
    188     IDB_TRACE("IDBDatabase::createObjectStore");
    189     HistogramSupport::histogramEnumeration("WebCore.IndexedDB.FrontEndAPICalls", IDBCreateObjectStoreCall, IDBMethodsMax);
    190     if (!m_versionChangeTransaction) {
    191         es.throwDOMException(InvalidStateError, IDBDatabase::notVersionChangeTransactionErrorMessage);
    192         return 0;
    193     }
    194     if (m_versionChangeTransaction->isFinished()) {
    195         es.throwDOMException(TransactionInactiveError, IDBDatabase::transactionFinishedErrorMessage);
    196         return 0;
    197     }
    198     if (!m_versionChangeTransaction->isActive()) {
    199         es.throwDOMException(TransactionInactiveError, IDBDatabase::transactionInactiveErrorMessage);
    200         return 0;
    201     }
    202 
    203     if (containsObjectStore(name)) {
    204         es.throwDOMException(ConstraintError, "An object store with the specified name already exists.");
    205         return 0;
    206     }
    207 
    208     if (!keyPath.isNull() && !keyPath.isValid()) {
    209         es.throwDOMException(SyntaxError, "The keyPath option is not a valid key path.");
    210         return 0;
    211     }
    212 
    213     if (autoIncrement && ((keyPath.type() == IDBKeyPath::StringType && keyPath.string().isEmpty()) || keyPath.type() == IDBKeyPath::ArrayType)) {
    214         es.throwDOMException(InvalidAccessError, "The autoIncrement option was set but the keyPath option was empty or an array.");
    215         return 0;
    216     }
    217 
    218     int64_t objectStoreId = m_metadata.maxObjectStoreId + 1;
    219     m_backend->createObjectStore(m_versionChangeTransaction->id(), objectStoreId, name, keyPath, autoIncrement);
    220 
    221     IDBObjectStoreMetadata metadata(name, objectStoreId, keyPath, autoIncrement, IDBDatabaseBackendInterface::MinimumIndexId);
    222     RefPtr<IDBObjectStore> objectStore = IDBObjectStore::create(metadata, m_versionChangeTransaction.get());
    223     m_metadata.objectStores.set(metadata.id, metadata);
    224     ++m_metadata.maxObjectStoreId;
    225 
    226     m_versionChangeTransaction->objectStoreCreated(name, objectStore);
    227     return objectStore.release();
    228 }
    229 
    230 void IDBDatabase::deleteObjectStore(const String& name, ExceptionState& es)
    231 {
    232     IDB_TRACE("IDBDatabase::deleteObjectStore");
    233     HistogramSupport::histogramEnumeration("WebCore.IndexedDB.FrontEndAPICalls", IDBDeleteObjectStoreCall, IDBMethodsMax);
    234     if (!m_versionChangeTransaction) {
    235         es.throwDOMException(InvalidStateError, IDBDatabase::notVersionChangeTransactionErrorMessage);
    236         return;
    237     }
    238     if (m_versionChangeTransaction->isFinished()) {
    239         es.throwDOMException(TransactionInactiveError, IDBDatabase::transactionFinishedErrorMessage);
    240         return;
    241     }
    242     if (!m_versionChangeTransaction->isActive()) {
    243         es.throwDOMException(TransactionInactiveError, IDBDatabase::transactionInactiveErrorMessage);
    244         return;
    245     }
    246 
    247     int64_t objectStoreId = findObjectStoreId(name);
    248     if (objectStoreId == IDBObjectStoreMetadata::InvalidId) {
    249         es.throwDOMException(NotFoundError, "The specified object store was not found.");
    250         return;
    251     }
    252 
    253     m_backend->deleteObjectStore(m_versionChangeTransaction->id(), objectStoreId);
    254     m_versionChangeTransaction->objectStoreDeleted(name);
    255     m_metadata.objectStores.remove(objectStoreId);
    256 }
    257 
    258 PassRefPtr<IDBTransaction> IDBDatabase::transaction(ScriptExecutionContext* context, const Vector<String>& scope, const String& modeString, ExceptionState& es)
    259 {
    260     IDB_TRACE("IDBDatabase::transaction");
    261     HistogramSupport::histogramEnumeration("WebCore.IndexedDB.FrontEndAPICalls", IDBTransactionCall, IDBMethodsMax);
    262     if (!scope.size()) {
    263         es.throwDOMException(InvalidAccessError, "The storeNames parameter was empty.");
    264         return 0;
    265     }
    266 
    267     IndexedDB::TransactionMode mode = IDBTransaction::stringToMode(modeString, es);
    268     if (es.hadException())
    269         return 0;
    270 
    271     if (m_versionChangeTransaction) {
    272         es.throwDOMException(InvalidStateError, "A version change transaction is running.");
    273         return 0;
    274     }
    275 
    276     if (m_closePending) {
    277         es.throwDOMException(InvalidStateError, "The database connection is closing.");
    278         return 0;
    279     }
    280 
    281     Vector<int64_t> objectStoreIds;
    282     for (size_t i = 0; i < scope.size(); ++i) {
    283         int64_t objectStoreId = findObjectStoreId(scope[i]);
    284         if (objectStoreId == IDBObjectStoreMetadata::InvalidId) {
    285             es.throwDOMException(NotFoundError, "One of the specified object stores was not found.");
    286             return 0;
    287         }
    288         objectStoreIds.append(objectStoreId);
    289     }
    290 
    291     int64_t transactionId = nextTransactionId();
    292     m_backend->createTransaction(transactionId, m_databaseCallbacks, objectStoreIds, mode);
    293 
    294     RefPtr<IDBTransaction> transaction = IDBTransaction::create(context, transactionId, scope, mode, this);
    295     return transaction.release();
    296 }
    297 
    298 PassRefPtr<IDBTransaction> IDBDatabase::transaction(ScriptExecutionContext* context, const String& storeName, const String& mode, ExceptionState& es)
    299 {
    300     RefPtr<DOMStringList> storeNames = DOMStringList::create();
    301     storeNames->append(storeName);
    302     return transaction(context, storeNames, mode, es);
    303 }
    304 
    305 void IDBDatabase::forceClose()
    306 {
    307     for (TransactionMap::const_iterator::Values it = m_transactions.begin().values(), end = m_transactions.end().values(); it != end; ++it)
    308         (*it)->abort(IGNORE_EXCEPTION);
    309     this->close();
    310     enqueueEvent(Event::create(eventNames().closeEvent, false, false));
    311 }
    312 
    313 void IDBDatabase::close()
    314 {
    315     IDB_TRACE("IDBDatabase::close");
    316     if (m_closePending)
    317         return;
    318 
    319     m_closePending = true;
    320 
    321     if (m_transactions.isEmpty())
    322         closeConnection();
    323 }
    324 
    325 void IDBDatabase::closeConnection()
    326 {
    327     ASSERT(m_closePending);
    328     ASSERT(m_transactions.isEmpty());
    329 
    330     m_backend->close(m_databaseCallbacks);
    331     m_backend.clear();
    332 
    333     if (m_contextStopped || !scriptExecutionContext())
    334         return;
    335 
    336     EventQueue* eventQueue = scriptExecutionContext()->eventQueue();
    337     // Remove any pending versionchange events scheduled to fire on this
    338     // connection. They would have been scheduled by the backend when another
    339     // connection called setVersion, but the frontend connection is being
    340     // closed before they could fire.
    341     for (size_t i = 0; i < m_enqueuedEvents.size(); ++i) {
    342         bool removed = eventQueue->cancelEvent(m_enqueuedEvents[i].get());
    343         ASSERT_UNUSED(removed, removed);
    344     }
    345 }
    346 
    347 void IDBDatabase::onVersionChange(int64_t oldVersion, int64_t newVersion)
    348 {
    349     IDB_TRACE("IDBDatabase::onVersionChange");
    350     if (m_contextStopped || !scriptExecutionContext())
    351         return;
    352 
    353     if (m_closePending)
    354         return;
    355 
    356     RefPtr<IDBAny> newVersionAny = newVersion == IDBDatabaseMetadata::NoIntVersion ? IDBAny::createNull() : IDBAny::create(newVersion);
    357     enqueueEvent(IDBVersionChangeEvent::create(IDBAny::create(oldVersion), newVersionAny.release(), eventNames().versionchangeEvent));
    358 }
    359 
    360 void IDBDatabase::enqueueEvent(PassRefPtr<Event> event)
    361 {
    362     ASSERT(!m_contextStopped);
    363     ASSERT(scriptExecutionContext());
    364     EventQueue* eventQueue = scriptExecutionContext()->eventQueue();
    365     event->setTarget(this);
    366     eventQueue->enqueueEvent(event.get());
    367     m_enqueuedEvents.append(event);
    368 }
    369 
    370 bool IDBDatabase::dispatchEvent(PassRefPtr<Event> event)
    371 {
    372     IDB_TRACE("IDBDatabase::dispatchEvent");
    373     ASSERT(event->type() == eventNames().versionchangeEvent || event->type() == eventNames().closeEvent);
    374     for (size_t i = 0; i < m_enqueuedEvents.size(); ++i) {
    375         if (m_enqueuedEvents[i].get() == event.get())
    376             m_enqueuedEvents.remove(i);
    377     }
    378     return EventTarget::dispatchEvent(event.get());
    379 }
    380 
    381 int64_t IDBDatabase::findObjectStoreId(const String& name) const
    382 {
    383     for (IDBDatabaseMetadata::ObjectStoreMap::const_iterator it = m_metadata.objectStores.begin(); it != m_metadata.objectStores.end(); ++it) {
    384         if (it->value.name == name) {
    385             ASSERT(it->key != IDBObjectStoreMetadata::InvalidId);
    386             return it->key;
    387         }
    388     }
    389     return IDBObjectStoreMetadata::InvalidId;
    390 }
    391 
    392 bool IDBDatabase::hasPendingActivity() const
    393 {
    394     // The script wrapper must not be collected before the object is closed or
    395     // we can't fire a "versionchange" event to let script manually close the connection.
    396     return !m_closePending && !m_eventTargetData.eventListenerMap.isEmpty() && !m_contextStopped;
    397 }
    398 
    399 void IDBDatabase::stop()
    400 {
    401     // Stop fires at a deterministic time, so we need to call close in it.
    402     close();
    403 
    404     m_contextStopped = true;
    405 }
    406 
    407 const AtomicString& IDBDatabase::interfaceName() const
    408 {
    409     return eventNames().interfaceForIDBDatabase;
    410 }
    411 
    412 ScriptExecutionContext* IDBDatabase::scriptExecutionContext() const
    413 {
    414     return ActiveDOMObject::scriptExecutionContext();
    415 }
    416 
    417 EventTargetData* IDBDatabase::eventTargetData()
    418 {
    419     return &m_eventTargetData;
    420 }
    421 
    422 EventTargetData* IDBDatabase::ensureEventTargetData()
    423 {
    424     return &m_eventTargetData;
    425 }
    426 
    427 } // namespace WebCore
    428