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