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 "IDBDatabase.h"
28
29#include "Document.h"
30#include "EventQueue.h"
31#include "IDBAny.h"
32#include "IDBDatabaseCallbacksImpl.h"
33#include "IDBDatabaseError.h"
34#include "IDBDatabaseException.h"
35#include "IDBEventDispatcher.h"
36#include "IDBFactoryBackendInterface.h"
37#include "IDBIndex.h"
38#include "IDBObjectStore.h"
39#include "IDBVersionChangeEvent.h"
40#include "IDBVersionChangeRequest.h"
41#include "IDBTransaction.h"
42#include "ScriptExecutionContext.h"
43#include <limits>
44
45#if ENABLE(INDEXED_DATABASE)
46
47namespace WebCore {
48
49PassRefPtr<IDBDatabase> IDBDatabase::create(ScriptExecutionContext* context, PassRefPtr<IDBDatabaseBackendInterface> database)
50{
51    return adoptRef(new IDBDatabase(context, database));
52}
53
54IDBDatabase::IDBDatabase(ScriptExecutionContext* context, PassRefPtr<IDBDatabaseBackendInterface> backend)
55    : ActiveDOMObject(context, this)
56    , m_backend(backend)
57    , m_noNewTransactions(false)
58    , m_stopped(false)
59{
60    // We pass a reference of this object before it can be adopted.
61    relaxAdoptionRequirement();
62    m_databaseCallbacks = IDBDatabaseCallbacksImpl::create(this);
63}
64
65IDBDatabase::~IDBDatabase()
66{
67    m_databaseCallbacks->unregisterDatabase(this);
68}
69
70void IDBDatabase::setSetVersionTransaction(IDBTransaction* transaction)
71{
72    m_setVersionTransaction = transaction;
73}
74
75PassRefPtr<IDBObjectStore> IDBDatabase::createObjectStore(const String& name, const OptionsObject& options, ExceptionCode& ec)
76{
77    if (!m_setVersionTransaction) {
78        ec = IDBDatabaseException::NOT_ALLOWED_ERR;
79        return 0;
80    }
81
82    String keyPath;
83    options.getKeyString("keyPath", keyPath);
84    bool autoIncrement = false;
85    options.getKeyBool("autoIncrement", autoIncrement);
86    // FIXME: Look up evictable and pass that on as well.
87
88    RefPtr<IDBObjectStoreBackendInterface> objectStore = m_backend->createObjectStore(name, keyPath, autoIncrement, m_setVersionTransaction->backend(), ec);
89    if (!objectStore) {
90        ASSERT(ec);
91        return 0;
92    }
93    return IDBObjectStore::create(objectStore.release(), m_setVersionTransaction.get());
94}
95
96void IDBDatabase::deleteObjectStore(const String& name, ExceptionCode& ec)
97{
98    if (!m_setVersionTransaction) {
99        ec = IDBDatabaseException::NOT_ALLOWED_ERR;
100        return;
101    }
102
103    m_backend->deleteObjectStore(name, m_setVersionTransaction->backend(), ec);
104}
105
106PassRefPtr<IDBVersionChangeRequest> IDBDatabase::setVersion(ScriptExecutionContext* context, const String& version, ExceptionCode& ec)
107{
108    RefPtr<IDBVersionChangeRequest> request = IDBVersionChangeRequest::create(context, IDBAny::create(this), version);
109    m_backend->setVersion(version, request, m_databaseCallbacks, ec);
110    return request;
111}
112
113PassRefPtr<IDBTransaction> IDBDatabase::transaction(ScriptExecutionContext* context, PassRefPtr<DOMStringList> prpStoreNames, unsigned short mode, ExceptionCode& ec)
114{
115    RefPtr<DOMStringList> storeNames = prpStoreNames;
116    if (!storeNames)
117        storeNames = DOMStringList::create();
118
119    if (mode != IDBTransaction::READ_WRITE && mode != IDBTransaction::READ_ONLY) {
120        // FIXME: May need to change when specced: http://www.w3.org/Bugs/Public/show_bug.cgi?id=11406
121        ec = IDBDatabaseException::CONSTRAINT_ERR;
122        return 0;
123    }
124    if (m_noNewTransactions) {
125        ec = IDBDatabaseException::NOT_ALLOWED_ERR;
126        return 0;
127    }
128
129    // We need to create a new transaction synchronously. Locks are acquired asynchronously. Operations
130    // can be queued against the transaction at any point. They will start executing as soon as the
131    // appropriate locks have been acquired.
132    // Also note that each backend object corresponds to exactly one IDBTransaction object.
133    RefPtr<IDBTransactionBackendInterface> transactionBackend = m_backend->transaction(storeNames.get(), mode, ec);
134    if (!transactionBackend) {
135        ASSERT(ec);
136        return 0;
137    }
138    RefPtr<IDBTransaction> transaction = IDBTransaction::create(context, transactionBackend, this);
139    transactionBackend->setCallbacks(transaction.get());
140    return transaction.release();
141}
142
143void IDBDatabase::close()
144{
145    if (m_noNewTransactions)
146        return;
147
148    ASSERT(scriptExecutionContext()->isDocument());
149    EventQueue* eventQueue = static_cast<Document*>(scriptExecutionContext())->eventQueue();
150    // Remove any pending versionchange events scheduled to fire on this
151    // connection. They would have been scheduled by the backend when another
152    // connection called setVersion, but the frontend connection is being
153    // closed before they could fire.
154    for (size_t i = 0; i < m_enqueuedEvents.size(); ++i) {
155        bool removed = eventQueue->cancelEvent(m_enqueuedEvents[i].get());
156        ASSERT_UNUSED(removed, removed);
157    }
158
159    m_noNewTransactions = true;
160    m_backend->close(m_databaseCallbacks);
161}
162
163void IDBDatabase::onVersionChange(const String& version)
164{
165    enqueueEvent(IDBVersionChangeEvent::create(version, eventNames().versionchangeEvent));
166}
167
168bool IDBDatabase::hasPendingActivity() const
169{
170    // FIXME: Try to find some way not to just leak this object until page navigation.
171    // FIXME: In an ideal world, we should return true as long as anyone has or can
172    //        get a handle to us or any derivative transaction/request object and any
173    //        of those have event listeners. This is in order to handle user generated
174    //        events properly.
175    return !m_stopped || ActiveDOMObject::hasPendingActivity();
176}
177
178void IDBDatabase::open()
179{
180    m_backend->open(m_databaseCallbacks);
181}
182
183void IDBDatabase::enqueueEvent(PassRefPtr<Event> event)
184{
185    ASSERT(scriptExecutionContext()->isDocument());
186    EventQueue* eventQueue = static_cast<Document*>(scriptExecutionContext())->eventQueue();
187    event->setTarget(this);
188    eventQueue->enqueueEvent(event.get());
189    m_enqueuedEvents.append(event);
190}
191
192bool IDBDatabase::dispatchEvent(PassRefPtr<Event> event)
193{
194    ASSERT(event->type() == eventNames().versionchangeEvent);
195    for (size_t i = 0; i < m_enqueuedEvents.size(); ++i) {
196        if (m_enqueuedEvents[i].get() == event.get())
197            m_enqueuedEvents.remove(i);
198    }
199    return EventTarget::dispatchEvent(event.get());
200}
201
202void IDBDatabase::stop()
203{
204    // Stop fires at a deterministic time, so we need to call close in it.
205    close();
206
207    m_stopped = true;
208}
209
210ScriptExecutionContext* IDBDatabase::scriptExecutionContext() const
211{
212    return ActiveDOMObject::scriptExecutionContext();
213}
214
215EventTargetData* IDBDatabase::eventTargetData()
216{
217    return &m_eventTargetData;
218}
219
220EventTargetData* IDBDatabase::ensureEventTargetData()
221{
222    return &m_eventTargetData;
223}
224
225} // namespace WebCore
226
227#endif // ENABLE(INDEXED_DATABASE)
228