IDB: storage/indexeddb/mozilla/versionchange-abort.html fails
[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     // NoIntVersion is a special value for internal use only and shouldn't be exposed to script.
139     // DefaultIntVersion should be exposed instead.
140     return m_metadata.version != IDBDatabaseMetadata::NoIntVersion ? m_metadata.version : IDBDatabaseMetadata::DefaultIntVersion;
141 }
142
143 PassRefPtr<IDBObjectStore> IDBDatabase::createObjectStore(const String& name, const Dictionary& options, ExceptionCode& ec)
144 {
145     IDBKeyPath keyPath;
146     bool autoIncrement = false;
147     if (!options.isUndefinedOrNull()) {
148         String keyPathString;
149         Vector<String> keyPathArray;
150         if (options.get("keyPath", keyPathArray))
151             keyPath = IDBKeyPath(keyPathArray);
152         else if (options.getWithUndefinedOrNullCheck("keyPath", keyPathString))
153             keyPath = IDBKeyPath(keyPathString);
154
155         options.get("autoIncrement", autoIncrement);
156     }
157
158     return createObjectStore(name, keyPath, autoIncrement, ec);
159 }
160
161 PassRefPtr<IDBObjectStore> IDBDatabase::createObjectStore(const String& name, const IDBKeyPath& keyPath, bool autoIncrement, ExceptionCode& ec)
162 {
163     LOG(StorageAPI, "IDBDatabase::createObjectStore");
164     HistogramSupport::histogramEnumeration("WebCore.IndexedDB.FrontEndAPICalls", IDBCreateObjectStoreCall, IDBMethodsMax);
165     if (!m_versionChangeTransaction) {
166         ec = IDBDatabaseException::InvalidStateError;
167         return 0;
168     }
169     if (!m_versionChangeTransaction->isActive()) {
170         ec = IDBDatabaseException::TransactionInactiveError;
171         return 0;
172     }
173
174     if (containsObjectStore(name)) {
175         ec = IDBDatabaseException::ConstraintError;
176         return 0;
177     }
178
179     if (!keyPath.isNull() && !keyPath.isValid()) {
180         ec = IDBDatabaseException::SyntaxError;
181         return 0;
182     }
183
184     if (autoIncrement && ((keyPath.type() == IDBKeyPath::StringType && keyPath.string().isEmpty()) || keyPath.type() == IDBKeyPath::ArrayType)) {
185         ec = IDBDatabaseException::InvalidAccessError;
186         return 0;
187     }
188
189     int64_t objectStoreId = m_metadata.maxObjectStoreId + 1;
190     m_backend->createObjectStore(m_versionChangeTransaction->id(), objectStoreId, name, keyPath, autoIncrement);
191
192     IDBObjectStoreMetadata metadata(name, objectStoreId, keyPath, autoIncrement, IDBDatabaseBackend::MinimumIndexId);
193     RefPtr<IDBObjectStore> objectStore = IDBObjectStore::create(metadata, m_versionChangeTransaction.get());
194     m_metadata.objectStores.set(metadata.id, metadata);
195     ++m_metadata.maxObjectStoreId;
196
197     m_versionChangeTransaction->objectStoreCreated(name, objectStore);
198     return objectStore.release();
199 }
200
201 void IDBDatabase::deleteObjectStore(const String& name, ExceptionCode& ec)
202 {
203     LOG(StorageAPI, "IDBDatabase::deleteObjectStore");
204     HistogramSupport::histogramEnumeration("WebCore.IndexedDB.FrontEndAPICalls", IDBDeleteObjectStoreCall, IDBMethodsMax);
205     if (!m_versionChangeTransaction) {
206         ec = IDBDatabaseException::InvalidStateError;
207         return;
208     }
209     if (!m_versionChangeTransaction->isActive()) {
210         ec = IDBDatabaseException::TransactionInactiveError;
211         return;
212     }
213
214     int64_t objectStoreId = findObjectStoreId(name);
215     if (objectStoreId == IDBObjectStoreMetadata::InvalidId) {
216         ec = IDBDatabaseException::NotFoundError;
217         return;
218     }
219
220     m_backend->deleteObjectStore(m_versionChangeTransaction->id(), objectStoreId);
221     m_versionChangeTransaction->objectStoreDeleted(name);
222     m_metadata.objectStores.remove(objectStoreId);
223 }
224
225 PassRefPtr<IDBTransaction> IDBDatabase::transaction(ScriptExecutionContext* context, const Vector<String>& scope, const String& modeString, ExceptionCode& ec)
226 {
227     LOG(StorageAPI, "IDBDatabase::transaction");
228     HistogramSupport::histogramEnumeration("WebCore.IndexedDB.FrontEndAPICalls", IDBTransactionCall, IDBMethodsMax);
229     if (!scope.size()) {
230         ec = IDBDatabaseException::InvalidAccessError;
231         return 0;
232     }
233
234     IndexedDB::TransactionMode mode = IDBTransaction::stringToMode(modeString, ec);
235     if (ec)
236         return 0;
237
238     if (m_versionChangeTransaction || m_closePending) {
239         ec = IDBDatabaseException::InvalidStateError;
240         return 0;
241     }
242
243     Vector<int64_t> objectStoreIds;
244     for (size_t i = 0; i < scope.size(); ++i) {
245         int64_t objectStoreId = findObjectStoreId(scope[i]);
246         if (objectStoreId == IDBObjectStoreMetadata::InvalidId) {
247             ec = IDBDatabaseException::NotFoundError;
248             return 0;
249         }
250         objectStoreIds.append(objectStoreId);
251     }
252
253     int64_t transactionId = nextTransactionId();
254     m_backend->createTransaction(transactionId, m_databaseCallbacks, objectStoreIds, mode);
255
256     RefPtr<IDBTransaction> transaction = IDBTransaction::create(context, transactionId, scope, mode, this);
257     return transaction.release();
258 }
259
260 PassRefPtr<IDBTransaction> IDBDatabase::transaction(ScriptExecutionContext* context, const String& storeName, const String& mode, ExceptionCode& ec)
261 {
262     RefPtr<DOMStringList> storeNames = DOMStringList::create();
263     storeNames->append(storeName);
264     return transaction(context, storeNames, mode, ec);
265 }
266
267 void IDBDatabase::forceClose()
268 {
269     for (TransactionMap::const_iterator::Values it = m_transactions.begin().values(), end = m_transactions.end().values(); it != end; ++it)
270         (*it)->abort(IGNORE_EXCEPTION);
271     this->close();
272 }
273
274 void IDBDatabase::close()
275 {
276     LOG(StorageAPI, "IDBDatabase::close");
277     if (m_closePending)
278         return;
279
280     m_closePending = true;
281
282     if (m_transactions.isEmpty())
283         closeConnection();
284 }
285
286 void IDBDatabase::closeConnection()
287 {
288     ASSERT(m_closePending);
289     ASSERT(m_transactions.isEmpty());
290
291     m_backend->close(m_databaseCallbacks);
292
293     if (m_contextStopped || !scriptExecutionContext())
294         return;
295
296     EventQueue& eventQueue = scriptExecutionContext()->eventQueue();
297     // Remove any pending versionchange events scheduled to fire on this
298     // connection. They would have been scheduled by the backend when another
299     // connection called setVersion, but the frontend connection is being
300     // closed before they could fire.
301     for (size_t i = 0; i < m_enqueuedEvents.size(); ++i) {
302         bool removed = eventQueue.cancelEvent(*m_enqueuedEvents[i]);
303         ASSERT_UNUSED(removed, removed);
304     }
305 }
306
307 void IDBDatabase::onVersionChange(uint64_t oldVersion, uint64_t newVersion, IndexedDB::VersionNullness newVersionNullness)
308 {
309     LOG(StorageAPI, "IDBDatabase::onVersionChange");
310     if (m_contextStopped || !scriptExecutionContext())
311         return;
312
313     if (m_closePending)
314         return;
315
316     enqueueEvent(IDBVersionChangeEvent::create(oldVersion, newVersion, newVersionNullness));
317 }
318
319 void IDBDatabase::enqueueEvent(PassRefPtr<Event> event)
320 {
321     ASSERT(!m_contextStopped);
322     ASSERT(scriptExecutionContext());
323     event->setTarget(this);
324     scriptExecutionContext()->eventQueue().enqueueEvent(event.get());
325     m_enqueuedEvents.append(event);
326 }
327
328 bool IDBDatabase::dispatchEvent(PassRefPtr<Event> event)
329 {
330     LOG(StorageAPI, "IDBDatabase::dispatchEvent");
331     ASSERT(event->type() == eventNames().versionchangeEvent);
332     for (size_t i = 0; i < m_enqueuedEvents.size(); ++i) {
333         if (m_enqueuedEvents[i].get() == event.get())
334             m_enqueuedEvents.remove(i);
335     }
336     return EventTarget::dispatchEvent(event.get());
337 }
338
339 int64_t IDBDatabase::findObjectStoreId(const String& name) const
340 {
341     for (IDBDatabaseMetadata::ObjectStoreMap::const_iterator it = m_metadata.objectStores.begin(); it != m_metadata.objectStores.end(); ++it) {
342         if (it->value.name == name) {
343             ASSERT(it->key != IDBObjectStoreMetadata::InvalidId);
344             return it->key;
345         }
346     }
347     return IDBObjectStoreMetadata::InvalidId;
348 }
349
350 bool IDBDatabase::hasPendingActivity() const
351 {
352     // The script wrapper must not be collected before the object is closed or
353     // we can't fire a "versionchange" event to let script manually close the connection.
354     return !m_closePending && hasEventListeners() && !m_contextStopped;
355 }
356
357 void IDBDatabase::stop()
358 {
359     // Stop fires at a deterministic time, so we need to call close in it.
360     close();
361
362     m_contextStopped = true;
363 }
364
365 } // namespace WebCore
366
367 #endif // ENABLE(INDEXED_DATABASE)