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
42namespace WebCore {
43
44PassRefPtr<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
52PassRefPtr<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
59const AtomicString& IDBTransaction::modeReadOnly()
60{
61    DEFINE_STATIC_LOCAL(AtomicString, readonly, ("readonly", AtomicString::ConstructFromLiteral));
62    return readonly;
63}
64
65const AtomicString& IDBTransaction::modeReadWrite()
66{
67    DEFINE_STATIC_LOCAL(AtomicString, readwrite, ("readwrite", AtomicString::ConstructFromLiteral));
68    return readwrite;
69}
70
71const AtomicString& IDBTransaction::modeVersionChange()
72{
73    DEFINE_STATIC_LOCAL(AtomicString, versionchange, ("versionchange", AtomicString::ConstructFromLiteral));
74    return versionchange;
75}
76
77IDBTransaction::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
102IDBTransaction::~IDBTransaction()
103{
104    ASSERT(m_state == Finished || m_contextStopped);
105    ASSERT(m_requestList.isEmpty() || m_contextStopped);
106}
107
108const String& IDBTransaction::mode() const
109{
110    return modeToString(m_mode);
111}
112
113void 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
125PassRefPtr<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
155void 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
164void 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
178void 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
190void 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
211void IDBTransaction::registerRequest(IDBRequest* request)
212{
213    ASSERT(request);
214    ASSERT(m_state == Active);
215    m_requestList.add(request);
216}
217
218void 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
225void 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
261void 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
276bool 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
284IndexedDB::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
296const 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
316const AtomicString& IDBTransaction::interfaceName() const
317{
318    return EventTargetNames::IDBTransaction;
319}
320
321ExecutionContext* IDBTransaction::executionContext() const
322{
323    return ActiveDOMObject::executionContext();
324}
325
326bool 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
360void IDBTransaction::stop()
361{
362    if (m_contextStopped)
363        return;
364
365    m_contextStopped = true;
366
367    abort(IGNORE_EXCEPTION);
368}
369
370void 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
381blink::WebIDBDatabase* IDBTransaction::backendDB() const
382{
383    return m_database->backend();
384}
385
386} // namespace WebCore
387