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#if ENABLE(INDEXED_DATABASE)
30
31#include "DOMStringList.h"
32#include "EventQueue.h"
33#include "ExceptionCode.h"
34#include "IDBAny.h"
35#include "IDBDatabaseCallbacks.h"
36#include "IDBDatabaseError.h"
37#include "IDBDatabaseException.h"
38#include "IDBEventDispatcher.h"
39#include "IDBIndex.h"
40#include "IDBKeyPath.h"
41#include "IDBObjectStore.h"
42#include "IDBTransaction.h"
43#include "IDBVersionChangeEvent.h"
44#include "Logging.h"
45#include "ScriptExecutionContext.h"
46#include <atomic>
47#include <inspector/ScriptCallStack.h>
48#include <limits>
49#include <wtf/Atomics.h>
50
51namespace WebCore {
52
53PassRefPtr<IDBDatabase> IDBDatabase::create(ScriptExecutionContext* context, PassRefPtr<IDBDatabaseBackend> database, PassRefPtr<IDBDatabaseCallbacks> callbacks)
54{
55    RefPtr<IDBDatabase> idbDatabase(adoptRef(new IDBDatabase(context, database, callbacks)));
56    idbDatabase->suspendIfNeeded();
57    return idbDatabase.release();
58}
59
60IDBDatabase::IDBDatabase(ScriptExecutionContext* context, PassRefPtr<IDBDatabaseBackend> backend, PassRefPtr<IDBDatabaseCallbacks> callbacks)
61    : ActiveDOMObject(context)
62    , m_backend(backend)
63    , m_closePending(false)
64    , m_contextStopped(false)
65    , m_databaseCallbacks(callbacks)
66{
67    // We pass a reference of this object before it can be adopted.
68    relaxAdoptionRequirement();
69}
70
71IDBDatabase::~IDBDatabase()
72{
73    close();
74}
75
76int64_t IDBDatabase::nextTransactionId()
77{
78    // Only keep a 32-bit counter to allow ports to use the other 32
79    // bits of the id.
80    static std::atomic<uint32_t> currentTransactionId;
81
82    return ++currentTransactionId;
83}
84
85void IDBDatabase::transactionCreated(IDBTransaction* transaction)
86{
87    ASSERT(transaction);
88    ASSERT(!m_transactions.contains(transaction->id()));
89    m_transactions.add(transaction->id(), transaction);
90
91    if (transaction->isVersionChange()) {
92        ASSERT(!m_versionChangeTransaction);
93        m_versionChangeTransaction = transaction;
94    }
95}
96
97void IDBDatabase::transactionFinished(IDBTransaction* transaction)
98{
99    ASSERT(transaction);
100    ASSERT(m_transactions.contains(transaction->id()));
101    ASSERT(m_transactions.get(transaction->id()) == transaction);
102    m_transactions.remove(transaction->id());
103
104    if (transaction->isVersionChange()) {
105        ASSERT(m_versionChangeTransaction == transaction);
106        m_versionChangeTransaction = 0;
107    }
108
109    if (m_closePending && m_transactions.isEmpty())
110        closeConnection();
111}
112
113void IDBDatabase::onAbort(int64_t transactionId, PassRefPtr<IDBDatabaseError> error)
114{
115    ASSERT(m_transactions.contains(transactionId));
116    m_transactions.get(transactionId)->onAbort(error);
117}
118
119void IDBDatabase::onComplete(int64_t transactionId)
120{
121    ASSERT(m_transactions.contains(transactionId));
122    m_transactions.get(transactionId)->onComplete();
123}
124
125PassRefPtr<DOMStringList> IDBDatabase::objectStoreNames() const
126{
127    RefPtr<DOMStringList> objectStoreNames = DOMStringList::create();
128    for (IDBDatabaseMetadata::ObjectStoreMap::const_iterator it = m_metadata.objectStores.begin(); it != m_metadata.objectStores.end(); ++it)
129        objectStoreNames->append(it->value.name);
130    objectStoreNames->sort();
131    return objectStoreNames.release();
132}
133
134uint64_t IDBDatabase::version() const
135{
136    // NoIntVersion is a special value for internal use only and shouldn't be exposed to script.
137    // DefaultIntVersion should be exposed instead.
138    return m_metadata.version != IDBDatabaseMetadata::NoIntVersion ? m_metadata.version : static_cast<uint64_t>(IDBDatabaseMetadata::DefaultIntVersion);
139}
140
141PassRefPtr<IDBObjectStore> IDBDatabase::createObjectStore(const String& name, const Dictionary& options, ExceptionCode& ec)
142{
143    IDBKeyPath keyPath;
144    bool autoIncrement = false;
145    if (!options.isUndefinedOrNull()) {
146        String keyPathString;
147        Vector<String> keyPathArray;
148        if (options.get("keyPath", keyPathArray))
149            keyPath = IDBKeyPath(keyPathArray);
150        else if (options.getWithUndefinedOrNullCheck("keyPath", keyPathString))
151            keyPath = IDBKeyPath(keyPathString);
152
153        options.get("autoIncrement", autoIncrement);
154    }
155
156    return createObjectStore(name, keyPath, autoIncrement, ec);
157}
158
159PassRefPtr<IDBObjectStore> IDBDatabase::createObjectStore(const String& name, const IDBKeyPath& keyPath, bool autoIncrement, ExceptionCode& ec)
160{
161    LOG(StorageAPI, "IDBDatabase::createObjectStore");
162    if (!m_versionChangeTransaction) {
163        ec = IDBDatabaseException::InvalidStateError;
164        return 0;
165    }
166    if (!m_versionChangeTransaction->isActive()) {
167        ec = IDBDatabaseException::TransactionInactiveError;
168        return 0;
169    }
170
171    if (containsObjectStore(name)) {
172        ec = IDBDatabaseException::ConstraintError;
173        return 0;
174    }
175
176    if (!keyPath.isNull() && !keyPath.isValid()) {
177        ec = IDBDatabaseException::SyntaxError;
178        return 0;
179    }
180
181    if (autoIncrement && ((keyPath.type() == IDBKeyPath::StringType && keyPath.string().isEmpty()) || keyPath.type() == IDBKeyPath::ArrayType)) {
182        ec = IDBDatabaseException::InvalidAccessError;
183        return 0;
184    }
185
186    int64_t objectStoreId = m_metadata.maxObjectStoreId + 1;
187    m_backend->createObjectStore(m_versionChangeTransaction->id(), objectStoreId, name, keyPath, autoIncrement);
188
189    IDBObjectStoreMetadata metadata(name, objectStoreId, keyPath, autoIncrement, IDBDatabaseBackend::MinimumIndexId);
190    RefPtr<IDBObjectStore> objectStore = IDBObjectStore::create(metadata, m_versionChangeTransaction.get());
191    m_metadata.objectStores.set(metadata.id, metadata);
192    ++m_metadata.maxObjectStoreId;
193
194    m_versionChangeTransaction->objectStoreCreated(name, objectStore);
195    return objectStore.release();
196}
197
198void IDBDatabase::deleteObjectStore(const String& name, ExceptionCode& ec)
199{
200    LOG(StorageAPI, "IDBDatabase::deleteObjectStore");
201    if (!m_versionChangeTransaction) {
202        ec = IDBDatabaseException::InvalidStateError;
203        return;
204    }
205    if (!m_versionChangeTransaction->isActive()) {
206        ec = IDBDatabaseException::TransactionInactiveError;
207        return;
208    }
209
210    int64_t objectStoreId = findObjectStoreId(name);
211    if (objectStoreId == IDBObjectStoreMetadata::InvalidId) {
212        ec = IDBDatabaseException::NotFoundError;
213        return;
214    }
215
216    m_backend->deleteObjectStore(m_versionChangeTransaction->id(), objectStoreId);
217    m_versionChangeTransaction->objectStoreDeleted(name);
218    m_metadata.objectStores.remove(objectStoreId);
219}
220
221PassRefPtr<IDBTransaction> IDBDatabase::transaction(ScriptExecutionContext* context, const Vector<String>& scope, const String& modeString, ExceptionCode& ec)
222{
223    LOG(StorageAPI, "IDBDatabase::transaction");
224    if (!scope.size()) {
225        ec = IDBDatabaseException::InvalidAccessError;
226        return 0;
227    }
228
229    IndexedDB::TransactionMode mode = IDBTransaction::stringToMode(modeString, ec);
230    if (ec)
231        return 0;
232
233    if (m_versionChangeTransaction || m_closePending) {
234        ec = IDBDatabaseException::InvalidStateError;
235        return 0;
236    }
237
238    Vector<int64_t> objectStoreIds;
239    for (size_t i = 0; i < scope.size(); ++i) {
240        int64_t objectStoreId = findObjectStoreId(scope[i]);
241        if (objectStoreId == IDBObjectStoreMetadata::InvalidId) {
242            ec = IDBDatabaseException::NotFoundError;
243            return 0;
244        }
245        objectStoreIds.append(objectStoreId);
246    }
247
248    int64_t transactionId = nextTransactionId();
249    m_backend->createTransaction(transactionId, m_databaseCallbacks, objectStoreIds, mode);
250
251    RefPtr<IDBTransaction> transaction = IDBTransaction::create(context, transactionId, scope, mode, this);
252    return transaction.release();
253}
254
255PassRefPtr<IDBTransaction> IDBDatabase::transaction(ScriptExecutionContext* context, const String& storeName, const String& mode, ExceptionCode& ec)
256{
257    RefPtr<DOMStringList> storeNames = DOMStringList::create();
258    storeNames->append(storeName);
259    return transaction(context, storeNames, mode, ec);
260}
261
262void IDBDatabase::forceClose()
263{
264    for (TransactionMap::const_iterator::Values it = m_transactions.begin().values(), end = m_transactions.end().values(); it != end; ++it)
265        (*it)->abort(IGNORE_EXCEPTION);
266    this->close();
267}
268
269void IDBDatabase::close()
270{
271    LOG(StorageAPI, "IDBDatabase::close");
272    if (m_closePending)
273        return;
274
275    m_closePending = true;
276
277    if (m_transactions.isEmpty())
278        closeConnection();
279}
280
281void IDBDatabase::closeConnection()
282{
283    ASSERT(m_closePending);
284    ASSERT(m_transactions.isEmpty());
285
286    m_backend->close(m_databaseCallbacks);
287
288    if (m_contextStopped || !scriptExecutionContext())
289        return;
290
291    EventQueue& eventQueue = scriptExecutionContext()->eventQueue();
292    // Remove any pending versionchange events scheduled to fire on this
293    // connection. They would have been scheduled by the backend when another
294    // connection called setVersion, but the frontend connection is being
295    // closed before they could fire.
296    for (size_t i = 0; i < m_enqueuedEvents.size(); ++i) {
297        bool removed = eventQueue.cancelEvent(*m_enqueuedEvents[i]);
298        ASSERT_UNUSED(removed, removed);
299    }
300}
301
302void IDBDatabase::onVersionChange(uint64_t oldVersion, uint64_t newVersion, IndexedDB::VersionNullness newVersionNullness)
303{
304    LOG(StorageAPI, "IDBDatabase::onVersionChange");
305    if (m_contextStopped || !scriptExecutionContext())
306        return;
307
308    if (m_closePending)
309        return;
310
311    enqueueEvent(IDBVersionChangeEvent::create(oldVersion, newVersion, newVersionNullness));
312}
313
314void IDBDatabase::enqueueEvent(PassRefPtr<Event> event)
315{
316    ASSERT(!m_contextStopped);
317    ASSERT(scriptExecutionContext());
318    event->setTarget(this);
319    scriptExecutionContext()->eventQueue().enqueueEvent(event.get());
320    m_enqueuedEvents.append(event);
321}
322
323bool IDBDatabase::dispatchEvent(PassRefPtr<Event> event)
324{
325    LOG(StorageAPI, "IDBDatabase::dispatchEvent");
326    ASSERT(event->type() == eventNames().versionchangeEvent);
327    for (size_t i = 0; i < m_enqueuedEvents.size(); ++i) {
328        if (m_enqueuedEvents[i].get() == event.get())
329            m_enqueuedEvents.remove(i);
330    }
331    return EventTarget::dispatchEvent(event.get());
332}
333
334int64_t IDBDatabase::findObjectStoreId(const String& name) const
335{
336    for (IDBDatabaseMetadata::ObjectStoreMap::const_iterator it = m_metadata.objectStores.begin(); it != m_metadata.objectStores.end(); ++it) {
337        if (it->value.name == name) {
338            ASSERT(it->key != IDBObjectStoreMetadata::InvalidId);
339            return it->key;
340        }
341    }
342    return IDBObjectStoreMetadata::InvalidId;
343}
344
345bool IDBDatabase::hasPendingActivity() const
346{
347    // The script wrapper must not be collected before the object is closed or
348    // we can't fire a "versionchange" event to let script manually close the connection.
349    return !m_closePending && hasEventListeners() && !m_contextStopped;
350}
351
352void IDBDatabase::stop()
353{
354    // Stop fires at a deterministic time, so we need to call close in it.
355    close();
356
357    m_contextStopped = true;
358}
359
360} // namespace WebCore
361
362#endif // ENABLE(INDEXED_DATABASE)
363