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 "IDBTransaction.h"
28
29#if ENABLE(INDEXED_DATABASE)
30
31#include "EventException.h"
32#include "EventQueue.h"
33#include "ExceptionCodePlaceholder.h"
34#include "IDBDatabase.h"
35#include "IDBDatabaseException.h"
36#include "IDBEventDispatcher.h"
37#include "IDBIndex.h"
38#include "IDBObjectStore.h"
39#include "IDBOpenDBRequest.h"
40#include "IDBPendingTransactionMonitor.h"
41#include "Logging.h"
42#include "ScriptExecutionContext.h"
43
44namespace WebCore {
45
46PassRefPtr<IDBTransaction> IDBTransaction::create(ScriptExecutionContext* context, int64_t id, const Vector<String>& objectStoreNames, IndexedDB::TransactionMode mode, IDBDatabase* db)
47{
48    IDBOpenDBRequest* openDBRequest = 0;
49    RefPtr<IDBTransaction> transaction(adoptRef(new IDBTransaction(context, id, objectStoreNames, mode, db, openDBRequest, IDBDatabaseMetadata())));
50    transaction->suspendIfNeeded();
51    return transaction.release();
52}
53
54PassRefPtr<IDBTransaction> IDBTransaction::create(ScriptExecutionContext* context, int64_t id, IDBDatabase* db, IDBOpenDBRequest* openDBRequest, const IDBDatabaseMetadata& previousMetadata)
55{
56    RefPtr<IDBTransaction> transaction(adoptRef(new IDBTransaction(context, id, Vector<String>(), IndexedDB::TransactionMode::VersionChange, db, openDBRequest, previousMetadata)));
57    transaction->suspendIfNeeded();
58    return transaction.release();
59}
60
61const AtomicString& IDBTransaction::modeReadOnly()
62{
63    DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, readonly, ("readonly", AtomicString::ConstructFromLiteral));
64    return readonly;
65}
66
67const AtomicString& IDBTransaction::modeReadWrite()
68{
69    DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, readwrite, ("readwrite", AtomicString::ConstructFromLiteral));
70    return readwrite;
71}
72
73const AtomicString& IDBTransaction::modeVersionChange()
74{
75    DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, versionchange, ("versionchange", AtomicString::ConstructFromLiteral));
76    return versionchange;
77}
78
79const AtomicString& IDBTransaction::modeReadOnlyLegacy()
80{
81    DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, readonly, ("0", AtomicString::ConstructFromLiteral));
82    return readonly;
83}
84
85const AtomicString& IDBTransaction::modeReadWriteLegacy()
86{
87    DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, readwrite, ("1", AtomicString::ConstructFromLiteral));
88    return readwrite;
89}
90
91
92IDBTransaction::IDBTransaction(ScriptExecutionContext* context, int64_t id, const Vector<String>& objectStoreNames, IndexedDB::TransactionMode mode, IDBDatabase* db, IDBOpenDBRequest* openDBRequest, const IDBDatabaseMetadata& previousMetadata)
93    : ActiveDOMObject(context)
94    , m_id(id)
95    , m_database(db)
96    , m_objectStoreNames(objectStoreNames)
97    , m_openDBRequest(openDBRequest)
98    , m_mode(mode)
99    , m_state(Active)
100    , m_hasPendingActivity(true)
101    , m_contextStopped(false)
102    , m_previousMetadata(previousMetadata)
103{
104    if (mode == IndexedDB::TransactionMode::VersionChange) {
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
116IDBTransaction::~IDBTransaction()
117{
118    ASSERT(m_state == Finished || m_contextStopped);
119    ASSERT(m_requestList.isEmpty() || m_contextStopped);
120}
121
122const String& IDBTransaction::mode() const
123{
124    return modeToString(m_mode);
125}
126
127void IDBTransaction::setError(PassRefPtr<DOMError> error, const String& errorMessage)
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        m_errorMessage = errorMessage;
137    }
138}
139
140PassRefPtr<IDBObjectStore> IDBTransaction::objectStore(const String& name, ExceptionCode& ec)
141{
142    if (m_state == Finished) {
143        ec = IDBDatabaseException::InvalidStateError;
144        return 0;
145    }
146
147    IDBObjectStoreMap::iterator it = m_objectStoreMap.find(name);
148    if (it != m_objectStoreMap.end())
149        return it->value;
150
151    if (!isVersionChange() && !m_objectStoreNames.contains(name)) {
152        ec = IDBDatabaseException::NotFoundError;
153        return 0;
154    }
155
156    int64_t objectStoreId = m_database->findObjectStoreId(name);
157    if (objectStoreId == IDBObjectStoreMetadata::InvalidId) {
158        ASSERT(isVersionChange());
159        ec = IDBDatabaseException::NotFoundError;
160        return 0;
161    }
162
163    const IDBDatabaseMetadata& metadata = m_database->metadata();
164
165    RefPtr<IDBObjectStore> objectStore = IDBObjectStore::create(metadata.objectStores.get(objectStoreId), this);
166    objectStoreCreated(name, objectStore);
167    return objectStore.release();
168}
169
170void IDBTransaction::objectStoreCreated(const String& name, PassRefPtr<IDBObjectStore> prpObjectStore)
171{
172    ASSERT(m_state != Finished);
173    RefPtr<IDBObjectStore> objectStore = prpObjectStore;
174    m_objectStoreMap.set(name, objectStore);
175    if (isVersionChange())
176        m_objectStoreCleanupMap.set(objectStore, objectStore->metadata());
177}
178
179void IDBTransaction::objectStoreDeleted(const String& name)
180{
181    ASSERT(m_state != Finished);
182    ASSERT(isVersionChange());
183    IDBObjectStoreMap::iterator it = m_objectStoreMap.find(name);
184    if (it != m_objectStoreMap.end()) {
185        RefPtr<IDBObjectStore> objectStore = it->value;
186        m_objectStoreMap.remove(name);
187        objectStore->markDeleted();
188        m_objectStoreCleanupMap.set(objectStore, objectStore->metadata());
189        m_deletedObjectStores.add(objectStore);
190    }
191}
192
193void IDBTransaction::setActive(bool active)
194{
195    LOG(StorageAPI, "IDBTransaction::setActive(%s) for transaction id %lli", active ? "true" : "false", static_cast<long long>(m_id));
196    ASSERT_WITH_MESSAGE(m_state != Finished, "A finished transaction tried to setActive(%s)", active ? "true" : "false");
197    if (m_state == Finishing)
198        return;
199    ASSERT(active != (m_state == Active));
200    m_state = active ? Active : Inactive;
201
202    if (!active && m_requestList.isEmpty())
203        backendDB()->commit(m_id);
204}
205
206void IDBTransaction::abort(ExceptionCode& ec)
207{
208    if (m_state == Finishing || m_state == Finished) {
209        ec = IDBDatabaseException::InvalidStateError;
210        return;
211    }
212
213    m_state = Finishing;
214
215    while (!m_requestList.isEmpty()) {
216        RefPtr<IDBRequest> request = *m_requestList.begin();
217        m_requestList.remove(request);
218        request->abort();
219    }
220
221    RefPtr<IDBTransaction> selfRef = this;
222    backendDB()->abort(m_id);
223}
224
225IDBTransaction::OpenCursorNotifier::OpenCursorNotifier(PassRefPtr<IDBTransaction> transaction, IDBCursor* cursor)
226    : m_transaction(transaction),
227      m_cursor(cursor)
228{
229    m_transaction->registerOpenCursor(m_cursor);
230}
231
232IDBTransaction::OpenCursorNotifier::~OpenCursorNotifier()
233{
234    if (m_cursor)
235        m_transaction->unregisterOpenCursor(m_cursor);
236}
237
238void 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
247void IDBTransaction::registerOpenCursor(IDBCursor* cursor)
248{
249    m_openCursors.add(cursor);
250}
251
252void IDBTransaction::unregisterOpenCursor(IDBCursor* cursor)
253{
254    m_openCursors.remove(cursor);
255}
256
257void 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
265void IDBTransaction::registerRequest(IDBRequest* request)
266{
267    ASSERT(request);
268    ASSERT(m_state == Active);
269    m_requestList.add(request);
270}
271
272void 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
279void IDBTransaction::onAbort(PassRefPtr<IDBDatabaseError> prpError)
280{
281    LOG(StorageAPI, "IDBTransaction::onAbort");
282    RefPtr<IDBDatabaseError> error = prpError;
283    ASSERT(m_state != Finished);
284
285    if (m_state != Finishing) {
286        ASSERT(error.get());
287        setError(DOMError::create(error->name()), error->message());
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    m_database->transactionFinished(this);
311}
312
313void IDBTransaction::onComplete()
314{
315    LOG(StorageAPI, "IDBTransaction::onComplete");
316    ASSERT(m_state != Finished);
317    m_state = Finishing;
318    m_objectStoreCleanupMap.clear();
319    closeOpenCursors();
320
321    // Enqueue events before notifying database, as database may close which enqueues more events and order matters.
322    enqueueEvent(Event::create(eventNames().completeEvent, false, false));
323    m_database->transactionFinished(this);
324}
325
326bool IDBTransaction::hasPendingActivity() const
327{
328    // FIXME: In an ideal world, we should return true as long as anyone has a or can
329    //        get a handle to us or any child request object and any of those have
330    //        event listeners. This is  in order to handle user generated events properly.
331    return m_hasPendingActivity && !m_contextStopped;
332}
333
334IndexedDB::TransactionMode IDBTransaction::stringToMode(const String& modeString, ExceptionCode& ec)
335{
336    if (modeString.isNull()
337        || modeString == IDBTransaction::modeReadOnly())
338        return IndexedDB::TransactionMode::ReadOnly;
339    if (modeString == IDBTransaction::modeReadWrite())
340        return IndexedDB::TransactionMode::ReadWrite;
341
342    ec = TypeError;
343    return IndexedDB::TransactionMode::ReadOnly;
344}
345
346const AtomicString& IDBTransaction::modeToString(IndexedDB::TransactionMode mode)
347{
348    switch (mode) {
349    case IndexedDB::TransactionMode::ReadOnly:
350        return IDBTransaction::modeReadOnly();
351
352    case IndexedDB::TransactionMode::ReadWrite:
353        return IDBTransaction::modeReadWrite();
354
355    case IndexedDB::TransactionMode::VersionChange:
356        return IDBTransaction::modeVersionChange();
357    }
358
359    ASSERT_NOT_REACHED();
360    return IDBTransaction::modeReadOnly();
361}
362
363bool IDBTransaction::dispatchEvent(PassRefPtr<Event> event)
364{
365    LOG(StorageAPI, "IDBTransaction::dispatchEvent");
366    ASSERT(m_state != Finished);
367    ASSERT(m_hasPendingActivity);
368    ASSERT(scriptExecutionContext());
369    ASSERT(event->target() == this);
370    m_state = Finished;
371
372    // Break reference cycles.
373    for (IDBObjectStoreMap::iterator it = m_objectStoreMap.begin(); it != m_objectStoreMap.end(); ++it)
374        it->value->transactionFinished();
375    m_objectStoreMap.clear();
376    for (IDBObjectStoreSet::iterator it = m_deletedObjectStores.begin(); it != m_deletedObjectStores.end(); ++it)
377        (*it)->transactionFinished();
378    m_deletedObjectStores.clear();
379
380    Vector<RefPtr<EventTarget>> targets;
381    targets.append(this);
382    targets.append(db());
383
384    // FIXME: When we allow custom event dispatching, this will probably need to change.
385    ASSERT(event->type() == eventNames().completeEvent || event->type() == eventNames().abortEvent);
386    bool returnValue = IDBEventDispatcher::dispatch(event.get(), targets);
387    // FIXME: Try to construct a test where |this| outlives openDBRequest and we
388    // get a crash.
389    if (m_openDBRequest) {
390        ASSERT(isVersionChange());
391        m_openDBRequest->transactionDidFinishAndDispatch();
392    }
393    m_hasPendingActivity = false;
394    return returnValue;
395}
396
397bool IDBTransaction::canSuspend() const
398{
399    // FIXME: Technically we can suspend before the first request is schedule
400    //        and after the complete/abort event is enqueued.
401    return m_state == Finished;
402}
403
404void IDBTransaction::stop()
405{
406    m_contextStopped = true;
407
408    abort(IGNORE_EXCEPTION);
409}
410
411void IDBTransaction::enqueueEvent(PassRefPtr<Event> event)
412{
413    ASSERT_WITH_MESSAGE(m_state != Finished, "A finished transaction tried to enqueue an event of type %s.", event->type().string().utf8().data());
414    if (m_contextStopped || !scriptExecutionContext())
415        return;
416
417    event->setTarget(this);
418    scriptExecutionContext()->eventQueue().enqueueEvent(event);
419}
420
421IDBDatabaseBackend* IDBTransaction::backendDB() const
422{
423    return db()->backend();
424}
425
426}
427
428#endif // ENABLE(INDEXED_DATABASE)
429