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