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