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/IDBTransaction.h"
     28 
     29 #include "bindings/v8/ExceptionState.h"
     30 #include "bindings/v8/ExceptionStatePlaceholder.h"
     31 #include "core/dom/ExecutionContext.h"
     32 #include "core/events/EventQueue.h"
     33 #include "core/inspector/ScriptCallStack.h"
     34 #include "modules/indexeddb/IDBDatabase.h"
     35 #include "modules/indexeddb/IDBEventDispatcher.h"
     36 #include "modules/indexeddb/IDBIndex.h"
     37 #include "modules/indexeddb/IDBObjectStore.h"
     38 #include "modules/indexeddb/IDBOpenDBRequest.h"
     39 #include "modules/indexeddb/IDBPendingTransactionMonitor.h"
     40 #include "modules/indexeddb/IDBTracing.h"
     41 
     42 namespace WebCore {
     43 
     44 PassRefPtr<IDBTransaction> IDBTransaction::create(ExecutionContext* context, int64_t id, const Vector<String>& objectStoreNames, IndexedDB::TransactionMode mode, IDBDatabase* db)
     45 {
     46     IDBOpenDBRequest* openDBRequest = 0;
     47     RefPtr<IDBTransaction> transaction(adoptRef(new IDBTransaction(context, id, objectStoreNames, mode, db, openDBRequest, IDBDatabaseMetadata())));
     48     transaction->suspendIfNeeded();
     49     return transaction.release();
     50 }
     51 
     52 PassRefPtr<IDBTransaction> IDBTransaction::create(ExecutionContext* context, int64_t id, IDBDatabase* db, IDBOpenDBRequest* openDBRequest, const IDBDatabaseMetadata& previousMetadata)
     53 {
     54     RefPtr<IDBTransaction> transaction(adoptRef(new IDBTransaction(context, id, Vector<String>(), IndexedDB::TransactionVersionChange, db, openDBRequest, previousMetadata)));
     55     transaction->suspendIfNeeded();
     56     return transaction.release();
     57 }
     58 
     59 const AtomicString& IDBTransaction::modeReadOnly()
     60 {
     61     DEFINE_STATIC_LOCAL(AtomicString, readonly, ("readonly", AtomicString::ConstructFromLiteral));
     62     return readonly;
     63 }
     64 
     65 const AtomicString& IDBTransaction::modeReadWrite()
     66 {
     67     DEFINE_STATIC_LOCAL(AtomicString, readwrite, ("readwrite", AtomicString::ConstructFromLiteral));
     68     return readwrite;
     69 }
     70 
     71 const AtomicString& IDBTransaction::modeVersionChange()
     72 {
     73     DEFINE_STATIC_LOCAL(AtomicString, versionchange, ("versionchange", AtomicString::ConstructFromLiteral));
     74     return versionchange;
     75 }
     76 
     77 IDBTransaction::IDBTransaction(ExecutionContext* context, int64_t id, const Vector<String>& objectStoreNames, IndexedDB::TransactionMode mode, IDBDatabase* db, IDBOpenDBRequest* openDBRequest, const IDBDatabaseMetadata& previousMetadata)
     78     : ActiveDOMObject(context)
     79     , m_id(id)
     80     , m_database(db)
     81     , m_objectStoreNames(objectStoreNames)
     82     , m_openDBRequest(openDBRequest)
     83     , m_mode(mode)
     84     , m_state(Active)
     85     , m_hasPendingActivity(true)
     86     , m_contextStopped(false)
     87     , m_previousMetadata(previousMetadata)
     88 {
     89     ScriptWrappable::init(this);
     90     if (mode == IndexedDB::TransactionVersionChange) {
     91         // Not active until the callback.
     92         m_state = Inactive;
     93     }
     94 
     95     // We pass a reference of this object before it can be adopted.
     96     relaxAdoptionRequirement();
     97     if (m_state == Active)
     98         IDBPendingTransactionMonitor::addNewTransaction(this);
     99     m_database->transactionCreated(this);
    100 }
    101 
    102 IDBTransaction::~IDBTransaction()
    103 {
    104     ASSERT(m_state == Finished || m_contextStopped);
    105     ASSERT(m_requestList.isEmpty() || m_contextStopped);
    106 }
    107 
    108 const String& IDBTransaction::mode() const
    109 {
    110     return modeToString(m_mode);
    111 }
    112 
    113 void IDBTransaction::setError(PassRefPtr<DOMError> error)
    114 {
    115     ASSERT(m_state != Finished);
    116     ASSERT(error);
    117 
    118     // The first error to be set is the true cause of the
    119     // transaction abort.
    120     if (!m_error) {
    121         m_error = error;
    122     }
    123 }
    124 
    125 PassRefPtr<IDBObjectStore> IDBTransaction::objectStore(const String& name, ExceptionState& exceptionState)
    126 {
    127     if (m_state == Finished) {
    128         exceptionState.throwDOMException(InvalidStateError, IDBDatabase::transactionFinishedErrorMessage);
    129         return 0;
    130     }
    131 
    132     IDBObjectStoreMap::iterator it = m_objectStoreMap.find(name);
    133     if (it != m_objectStoreMap.end())
    134         return it->value;
    135 
    136     if (!isVersionChange() && !m_objectStoreNames.contains(name)) {
    137         exceptionState.throwDOMException(NotFoundError, IDBDatabase::noSuchObjectStoreErrorMessage);
    138         return 0;
    139     }
    140 
    141     int64_t objectStoreId = m_database->findObjectStoreId(name);
    142     if (objectStoreId == IDBObjectStoreMetadata::InvalidId) {
    143         ASSERT(isVersionChange());
    144         exceptionState.throwDOMException(NotFoundError, IDBDatabase::noSuchObjectStoreErrorMessage);
    145         return 0;
    146     }
    147 
    148     const IDBDatabaseMetadata& metadata = m_database->metadata();
    149 
    150     RefPtr<IDBObjectStore> objectStore = IDBObjectStore::create(metadata.objectStores.get(objectStoreId), this);
    151     objectStoreCreated(name, objectStore);
    152     return objectStore.release();
    153 }
    154 
    155 void IDBTransaction::objectStoreCreated(const String& name, PassRefPtr<IDBObjectStore> prpObjectStore)
    156 {
    157     ASSERT(m_state != Finished);
    158     RefPtr<IDBObjectStore> objectStore = prpObjectStore;
    159     m_objectStoreMap.set(name, objectStore);
    160     if (isVersionChange())
    161         m_objectStoreCleanupMap.set(objectStore, objectStore->metadata());
    162 }
    163 
    164 void IDBTransaction::objectStoreDeleted(const String& name)
    165 {
    166     ASSERT(m_state != Finished);
    167     ASSERT(isVersionChange());
    168     IDBObjectStoreMap::iterator it = m_objectStoreMap.find(name);
    169     if (it != m_objectStoreMap.end()) {
    170         RefPtr<IDBObjectStore> objectStore = it->value;
    171         m_objectStoreMap.remove(name);
    172         objectStore->markDeleted();
    173         m_objectStoreCleanupMap.set(objectStore, objectStore->metadata());
    174         m_deletedObjectStores.add(objectStore);
    175     }
    176 }
    177 
    178 void IDBTransaction::setActive(bool active)
    179 {
    180     ASSERT_WITH_MESSAGE(m_state != Finished, "A finished transaction tried to setActive(%s)", active ? "true" : "false");
    181     if (m_state == Finishing)
    182         return;
    183     ASSERT(active != (m_state == Active));
    184     m_state = active ? Active : Inactive;
    185 
    186     if (!active && m_requestList.isEmpty())
    187         backendDB()->commit(m_id);
    188 }
    189 
    190 void IDBTransaction::abort(ExceptionState& exceptionState)
    191 {
    192     if (m_state == Finishing || m_state == Finished) {
    193         exceptionState.throwDOMException(InvalidStateError, IDBDatabase::transactionFinishedErrorMessage);
    194         return;
    195     }
    196 
    197     m_state = Finishing;
    198 
    199     if (!m_contextStopped) {
    200         while (!m_requestList.isEmpty()) {
    201             RefPtr<IDBRequest> request = *m_requestList.begin();
    202             m_requestList.remove(request);
    203             request->abort();
    204         }
    205     }
    206 
    207     RefPtr<IDBTransaction> selfRef = this;
    208     backendDB()->abort(m_id);
    209 }
    210 
    211 void IDBTransaction::registerRequest(IDBRequest* request)
    212 {
    213     ASSERT(request);
    214     ASSERT(m_state == Active);
    215     m_requestList.add(request);
    216 }
    217 
    218 void IDBTransaction::unregisterRequest(IDBRequest* request)
    219 {
    220     ASSERT(request);
    221     // If we aborted the request, it will already have been removed.
    222     m_requestList.remove(request);
    223 }
    224 
    225 void IDBTransaction::onAbort(PassRefPtr<DOMError> prpError)
    226 {
    227     IDB_TRACE("IDBTransaction::onAbort");
    228     RefPtr<DOMError> error = prpError;
    229     ASSERT(m_state != Finished);
    230 
    231     if (m_state != Finishing) {
    232         ASSERT(error.get());
    233         setError(error.release());
    234 
    235         // Abort was not triggered by front-end, so outstanding requests must
    236         // be aborted now.
    237         while (!m_requestList.isEmpty()) {
    238             RefPtr<IDBRequest> request = *m_requestList.begin();
    239             m_requestList.remove(request);
    240             request->abort();
    241         }
    242         m_state = Finishing;
    243     }
    244 
    245     if (isVersionChange()) {
    246         for (IDBObjectStoreMetadataMap::iterator it = m_objectStoreCleanupMap.begin(); it != m_objectStoreCleanupMap.end(); ++it)
    247             it->key->setMetadata(it->value);
    248         m_database->setMetadata(m_previousMetadata);
    249         m_database->close();
    250     }
    251     m_objectStoreCleanupMap.clear();
    252 
    253     // Enqueue events before notifying database, as database may close which enqueues more events and order matters.
    254     enqueueEvent(Event::createBubble(EventTypeNames::abort));
    255 
    256     // If script has stopped and GC has completed, database may have last reference to this object.
    257     RefPtr<IDBTransaction> protect(this);
    258     m_database->transactionFinished(this);
    259 }
    260 
    261 void IDBTransaction::onComplete()
    262 {
    263     IDB_TRACE("IDBTransaction::onComplete");
    264     ASSERT(m_state != Finished);
    265     m_state = Finishing;
    266     m_objectStoreCleanupMap.clear();
    267 
    268     // Enqueue events before notifying database, as database may close which enqueues more events and order matters.
    269     enqueueEvent(Event::create(EventTypeNames::complete));
    270 
    271     // If script has stopped and GC has completed, database may have last reference to this object.
    272     RefPtr<IDBTransaction> protect(this);
    273     m_database->transactionFinished(this);
    274 }
    275 
    276 bool IDBTransaction::hasPendingActivity() const
    277 {
    278     // FIXME: In an ideal world, we should return true as long as anyone has a or can
    279     //        get a handle to us or any child request object and any of those have
    280     //        event listeners. This is  in order to handle user generated events properly.
    281     return m_hasPendingActivity && !m_contextStopped;
    282 }
    283 
    284 IndexedDB::TransactionMode IDBTransaction::stringToMode(const String& modeString, ExceptionState& exceptionState)
    285 {
    286     if (modeString.isNull()
    287         || modeString == IDBTransaction::modeReadOnly())
    288         return IndexedDB::TransactionReadOnly;
    289     if (modeString == IDBTransaction::modeReadWrite())
    290         return IndexedDB::TransactionReadWrite;
    291 
    292     exceptionState.throwUninformativeAndGenericTypeError();
    293     return IndexedDB::TransactionReadOnly;
    294 }
    295 
    296 const AtomicString& IDBTransaction::modeToString(IndexedDB::TransactionMode mode)
    297 {
    298     switch (mode) {
    299     case IndexedDB::TransactionReadOnly:
    300         return IDBTransaction::modeReadOnly();
    301         break;
    302 
    303     case IndexedDB::TransactionReadWrite:
    304         return IDBTransaction::modeReadWrite();
    305         break;
    306 
    307     case IndexedDB::TransactionVersionChange:
    308         return IDBTransaction::modeVersionChange();
    309         break;
    310     }
    311 
    312     ASSERT_NOT_REACHED();
    313     return IDBTransaction::modeReadOnly();
    314 }
    315 
    316 const AtomicString& IDBTransaction::interfaceName() const
    317 {
    318     return EventTargetNames::IDBTransaction;
    319 }
    320 
    321 ExecutionContext* IDBTransaction::executionContext() const
    322 {
    323     return ActiveDOMObject::executionContext();
    324 }
    325 
    326 bool IDBTransaction::dispatchEvent(PassRefPtr<Event> event)
    327 {
    328     IDB_TRACE("IDBTransaction::dispatchEvent");
    329     ASSERT(m_state != Finished);
    330     ASSERT(m_hasPendingActivity);
    331     ASSERT(executionContext());
    332     ASSERT(event->target() == this);
    333     m_state = Finished;
    334 
    335     // Break reference cycles.
    336     for (IDBObjectStoreMap::iterator it = m_objectStoreMap.begin(); it != m_objectStoreMap.end(); ++it)
    337         it->value->transactionFinished();
    338     m_objectStoreMap.clear();
    339     for (IDBObjectStoreSet::iterator it = m_deletedObjectStores.begin(); it != m_deletedObjectStores.end(); ++it)
    340         (*it)->transactionFinished();
    341     m_deletedObjectStores.clear();
    342 
    343     Vector<RefPtr<EventTarget> > targets;
    344     targets.append(this);
    345     targets.append(db());
    346 
    347     // FIXME: When we allow custom event dispatching, this will probably need to change.
    348     ASSERT(event->type() == EventTypeNames::complete || event->type() == EventTypeNames::abort);
    349     bool returnValue = IDBEventDispatcher::dispatch(event.get(), targets);
    350     // FIXME: Try to construct a test where |this| outlives openDBRequest and we
    351     // get a crash.
    352     if (m_openDBRequest) {
    353         ASSERT(isVersionChange());
    354         m_openDBRequest->transactionDidFinishAndDispatch();
    355     }
    356     m_hasPendingActivity = false;
    357     return returnValue;
    358 }
    359 
    360 void IDBTransaction::stop()
    361 {
    362     if (m_contextStopped)
    363         return;
    364 
    365     m_contextStopped = true;
    366 
    367     abort(IGNORE_EXCEPTION);
    368 }
    369 
    370 void IDBTransaction::enqueueEvent(PassRefPtr<Event> event)
    371 {
    372     ASSERT_WITH_MESSAGE(m_state != Finished, "A finished transaction tried to enqueue an event of type %s.", event->type().string().utf8().data());
    373     if (m_contextStopped || !executionContext())
    374         return;
    375 
    376     EventQueue* eventQueue = executionContext()->eventQueue();
    377     event->setTarget(this);
    378     eventQueue->enqueueEvent(event);
    379 }
    380 
    381 blink::WebIDBDatabase* IDBTransaction::backendDB() const
    382 {
    383     return m_database->backend();
    384 }
    385 
    386 } // namespace WebCore
    387