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