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