Modern IDB: storage/indexeddb/transaction-basics.html fails.
[WebKit-https.git] / Source / WebCore / Modules / indexeddb / client / IDBDatabaseImpl.cpp
1 /*
2  * Copyright (C) 2015 Apple 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  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "IDBDatabaseImpl.h"
28
29 #if ENABLE(INDEXED_DATABASE)
30
31 #include "EventQueue.h"
32 #include "IDBConnectionToServer.h"
33 #include "IDBDatabaseException.h"
34 #include "IDBOpenDBRequestImpl.h"
35 #include "IDBResultData.h"
36 #include "IDBTransactionImpl.h"
37 #include "IDBVersionChangeEventImpl.h"
38 #include "Logging.h"
39 #include "ScriptExecutionContext.h"
40
41 namespace WebCore {
42 namespace IDBClient {
43
44 Ref<IDBDatabase> IDBDatabase::create(ScriptExecutionContext& context, IDBConnectionToServer& connection, const IDBResultData& resultData)
45 {
46     return adoptRef(*new IDBDatabase(context, connection, resultData));
47 }
48
49 IDBDatabase::IDBDatabase(ScriptExecutionContext& context, IDBConnectionToServer& connection, const IDBResultData& resultData)
50     : WebCore::IDBDatabase(&context)
51     , m_serverConnection(connection)
52     , m_info(resultData.databaseInfo())
53     , m_databaseConnectionIdentifier(resultData.databaseConnectionIdentifier())
54 {
55     LOG(IndexedDB, "IDBDatabase::IDBDatabase - Creating database %s with version %" PRIu64, m_info.name().utf8().data(), m_info.version());
56     suspendIfNeeded();
57     relaxAdoptionRequirement();
58     m_serverConnection->registerDatabaseConnection(*this);
59 }
60
61 IDBDatabase::~IDBDatabase()
62 {
63     m_serverConnection->unregisterDatabaseConnection(*this);
64 }
65
66 bool IDBDatabase::hasPendingActivity() const
67 {
68     return !m_closedInServer;
69 }
70
71 const String IDBDatabase::name() const
72 {
73     return m_info.name();
74 }
75
76 uint64_t IDBDatabase::version() const
77 {
78     return m_info.version();
79 }
80
81 RefPtr<DOMStringList> IDBDatabase::objectStoreNames() const
82 {
83     RefPtr<DOMStringList> objectStoreNames = DOMStringList::create();
84     for (auto& name : m_info.objectStoreNames())
85         objectStoreNames->append(name);
86     objectStoreNames->sort();
87     return WTF::move(objectStoreNames);
88 }
89
90 RefPtr<WebCore::IDBObjectStore> IDBDatabase::createObjectStore(const String&, const Dictionary&, ExceptionCodeWithMessage&)
91 {
92     ASSERT_NOT_REACHED();
93     return nullptr;
94 }
95
96 RefPtr<WebCore::IDBObjectStore> IDBDatabase::createObjectStore(const String& name, const IDBKeyPath& keyPath, bool autoIncrement, ExceptionCodeWithMessage& ec)
97 {
98     LOG(IndexedDB, "IDBDatabase::createObjectStore");
99
100     ASSERT(!m_versionChangeTransaction || m_versionChangeTransaction->isVersionChange());
101
102     if (!m_versionChangeTransaction) {
103         ec.code = IDBDatabaseException::InvalidStateError;
104         ec.message = ASCIILiteral("Failed to execute 'createObjectStore' on 'IDBDatabase': The database is not running a version change transaction.");
105         return nullptr;
106     }
107
108     if (!m_versionChangeTransaction->isActive()) {
109         ec.code = IDBDatabaseException::TransactionInactiveError;
110         return nullptr;
111     }
112
113     if (m_info.hasObjectStore(name)) {
114         ec.code = IDBDatabaseException::ConstraintError;
115         ec.message = ASCIILiteral("Failed to execute 'createObjectStore' on 'IDBDatabase': An object store with the specified name already exists.");
116         return nullptr;
117     }
118
119     if (!keyPath.isNull() && !keyPath.isValid()) {
120         ec.code = IDBDatabaseException::SyntaxError;
121         ec.message = ASCIILiteral("Failed to execute 'createObjectStore' on 'IDBDatabase': The keyPath option is not a valid key path.");
122         return nullptr;
123     }
124
125     if (autoIncrement && !keyPath.isNull()) {
126         if ((keyPath.type() == IndexedDB::KeyPathType::String && keyPath.string().isEmpty()) || keyPath.type() == IndexedDB::KeyPathType::Array) {
127             ec.code = IDBDatabaseException::InvalidAccessError;
128             ec.message = ASCIILiteral("Failed to execute 'createObjectStore' on 'IDBDatabase': The autoIncrement option was set but the keyPath option was empty or an array.");
129             return nullptr;
130         }
131     }
132
133     // Install the new ObjectStore into the connection's metadata.
134     IDBObjectStoreInfo info = m_info.createNewObjectStore(name, keyPath, autoIncrement);
135
136     // Create the actual IDBObjectStore from the transaction, which also schedules the operation server side.
137     Ref<IDBObjectStore> objectStore = m_versionChangeTransaction->createObjectStore(info);
138     return adoptRef(&objectStore.leakRef());
139 }
140
141 RefPtr<WebCore::IDBTransaction> IDBDatabase::transaction(ScriptExecutionContext*, const Vector<String>& objectStores, const String& modeString, ExceptionCodeWithMessage& ec)
142 {
143     LOG(IndexedDB, "IDBDatabase::transaction");
144
145     if (m_closePending) {
146         ec.code = IDBDatabaseException::InvalidStateError;
147         ec.message = ASCIILiteral("Failed to execute 'transaction' on 'IDBDatabase': The database connection is closing.");
148         return nullptr;
149     }
150
151     if (objectStores.isEmpty()) {
152         ec.code = IDBDatabaseException::InvalidAccessError;
153         ec.message = ASCIILiteral("Failed to execute 'transaction' on 'IDBDatabase': The storeNames parameter was empty.");
154         return nullptr;
155     }
156
157     IndexedDB::TransactionMode mode = IDBTransaction::stringToMode(modeString, ec.code);
158     if (ec.code) {
159         ec.message = makeString(ASCIILiteral("Failed to execute 'transaction' on 'IDBDatabase': The mode provided ('"), modeString, ASCIILiteral("') is not one of 'readonly' or 'readwrite'."));
160         return nullptr;
161     }
162
163     if (mode != IndexedDB::TransactionMode::ReadOnly && mode != IndexedDB::TransactionMode::ReadWrite) {
164         ec.code = TypeError;
165         return nullptr;
166     }
167
168     if (m_versionChangeTransaction && !m_versionChangeTransaction->isFinishedOrFinishing()) {
169         ec.code = IDBDatabaseException::InvalidStateError;
170         ec.message = ASCIILiteral("Failed to execute 'transaction' on 'IDBDatabase': A version change transaction is running.");
171         return nullptr;
172     }
173
174     for (auto& objectStoreName : objectStores) {
175         if (m_info.hasObjectStore(objectStoreName))
176             continue;
177         ec.code = IDBDatabaseException::NotFoundError;
178         ec.message = ASCIILiteral("Failed to execute 'transaction' on 'IDBDatabase': One of the specified object stores was not found.");
179         return nullptr;
180     }
181
182     auto info = IDBTransactionInfo::clientTransaction(m_serverConnection.get(), objectStores, mode);
183     auto transaction = IDBTransaction::create(*this, info);
184
185     LOG(IndexedDB, "IDBDatabase::transaction - Added active transaction %s", info.identifier().loggingString().utf8().data());
186
187     m_activeTransactions.set(info.identifier(), &transaction.get());
188
189     return adoptRef(&transaction.leakRef());
190 }
191
192 RefPtr<WebCore::IDBTransaction> IDBDatabase::transaction(ScriptExecutionContext* context, const String& objectStore, const String& mode, ExceptionCodeWithMessage& ec)
193 {
194     Vector<String> objectStores(1);
195     objectStores[0] = objectStore;
196     return transaction(context, objectStores, mode, ec);
197 }
198
199 void IDBDatabase::deleteObjectStore(const String& objectStoreName, ExceptionCodeWithMessage& ec)
200 {
201     LOG(IndexedDB, "IDBDatabase::deleteObjectStore");
202
203     if (!m_versionChangeTransaction) {
204         ec.code = IDBDatabaseException::InvalidStateError;
205         ec.message = ASCIILiteral("Failed to execute 'deleteObjectStore' on 'IDBDatabase': The database is not running a version change transaction.");
206         return;
207     }
208
209     if (!m_versionChangeTransaction->isActive()) {
210         ec.code = IDBDatabaseException::TransactionInactiveError;
211         return;
212     }
213
214     if (!m_info.hasObjectStore(objectStoreName)) {
215         ec.code = IDBDatabaseException::NotFoundError;
216         ec.message = ASCIILiteral("Failed to execute 'deleteObjectStore' on 'IDBDatabase': The specified object store was not found.");
217         return;
218     }
219
220     m_info.deleteObjectStore(objectStoreName);
221     m_versionChangeTransaction->deleteObjectStore(objectStoreName);
222 }
223
224 void IDBDatabase::close()
225 {
226     m_closePending = true;
227     maybeCloseInServer();
228 }
229
230 void IDBDatabase::maybeCloseInServer()
231 {
232     if (m_closedInServer)
233         return;
234
235     // 3.3.9 Database closing steps
236     // Wait for all transactions created using this connection to complete.
237     // Once they are complete, this connection is closed.
238     if (!m_activeTransactions.isEmpty() || !m_committingTransactions.isEmpty())
239         return;
240
241     m_closedInServer = true;
242     m_serverConnection->databaseConnectionClosed(*this);
243 }
244
245 const char* IDBDatabase::activeDOMObjectName() const
246 {
247     return "IDBDatabase";
248 }
249
250 bool IDBDatabase::canSuspendForDocumentSuspension() const
251 {
252     // FIXME: This value will sometimes be false when database operations are actually in progress.
253     // Such database operations do not yet exist.
254     return true;
255 }
256
257 Ref<IDBTransaction> IDBDatabase::startVersionChangeTransaction(const IDBTransactionInfo& info, IDBOpenDBRequest& request)
258 {
259     LOG(IndexedDB, "IDBDatabase::startVersionChangeTransaction %s", info.identifier().loggingString().utf8().data());
260
261     ASSERT(!m_versionChangeTransaction);
262     ASSERT(info.mode() == IndexedDB::TransactionMode::VersionChange);
263
264     Ref<IDBTransaction> transaction = IDBTransaction::create(*this, info, request);
265     m_versionChangeTransaction = &transaction.get();
266
267     m_activeTransactions.set(transaction->info().identifier(), &transaction.get());
268
269     return WTF::move(transaction);
270 }
271
272 void IDBDatabase::didStartTransaction(IDBTransaction& transaction)
273 {
274     LOG(IndexedDB, "IDBDatabase::didStartTransaction %s", transaction.info().identifier().loggingString().utf8().data());
275     ASSERT(!m_versionChangeTransaction);
276
277     // It is possible for the client to have aborted a transaction before the server replies back that it has started.
278     if (m_abortingTransactions.contains(transaction.info().identifier()))
279         return;
280
281     m_activeTransactions.set(transaction.info().identifier(), &transaction);
282 }
283
284 void IDBDatabase::willCommitTransaction(IDBTransaction& transaction)
285 {
286     LOG(IndexedDB, "IDBDatabase::willCommitTransaction %s", transaction.info().identifier().loggingString().utf8().data());
287
288     auto refTransaction = m_activeTransactions.take(transaction.info().identifier());
289     ASSERT(refTransaction);
290     m_committingTransactions.set(transaction.info().identifier(), WTF::move(refTransaction));
291 }
292
293 void IDBDatabase::didCommitTransaction(IDBTransaction& transaction)
294 {
295     LOG(IndexedDB, "IDBDatabase::didCommitTransaction %s", transaction.info().identifier().loggingString().utf8().data());
296
297     if (m_versionChangeTransaction == &transaction)
298         m_info.setVersion(transaction.info().newVersion());
299
300     didCommitOrAbortTransaction(transaction);
301 }
302
303 void IDBDatabase::willAbortTransaction(IDBTransaction& transaction)
304 {
305     LOG(IndexedDB, "IDBDatabase::willAbortTransaction %s", transaction.info().identifier().loggingString().utf8().data());
306
307     auto refTransaction = m_activeTransactions.take(transaction.info().identifier());
308     ASSERT(refTransaction);
309     m_abortingTransactions.set(transaction.info().identifier(), WTF::move(refTransaction));
310
311     if (transaction.isVersionChange()) {
312         ASSERT(transaction.originalDatabaseInfo());
313         m_info = *transaction.originalDatabaseInfo();
314         m_closePending = true;
315     }
316 }
317
318 void IDBDatabase::didAbortTransaction(IDBTransaction& transaction)
319 {
320     LOG(IndexedDB, "IDBDatabase::didAbortTransaction %s", transaction.info().identifier().loggingString().utf8().data());
321
322     if (transaction.isVersionChange()) {
323         ASSERT(transaction.originalDatabaseInfo());
324         ASSERT(m_info.version() == transaction.originalDatabaseInfo()->version());
325         m_closePending = true;
326         m_closedInServer = true;
327         m_serverConnection->databaseConnectionClosed(*this);
328     }
329
330     didCommitOrAbortTransaction(transaction);
331 }
332
333 void IDBDatabase::didCommitOrAbortTransaction(IDBTransaction& transaction)
334 {
335     LOG(IndexedDB, "IDBDatabase::didCommitOrAbortTransaction %s", transaction.info().identifier().loggingString().utf8().data());
336
337     if (m_versionChangeTransaction == &transaction)
338         m_versionChangeTransaction = nullptr;
339
340 #ifndef NDBEBUG
341     unsigned count = 0;
342     if (m_activeTransactions.contains(transaction.info().identifier()))
343         ++count;
344     if (m_committingTransactions.contains(transaction.info().identifier()))
345         ++count;
346     if (m_abortingTransactions.contains(transaction.info().identifier()))
347         ++count;
348
349     ASSERT(count == 1);
350 #endif
351
352     m_activeTransactions.remove(transaction.info().identifier());
353     m_committingTransactions.remove(transaction.info().identifier());
354     m_abortingTransactions.remove(transaction.info().identifier());
355
356     if (m_closePending)
357         maybeCloseInServer();
358 }
359
360 void IDBDatabase::fireVersionChangeEvent(uint64_t requestedVersion)
361 {
362     uint64_t currentVersion = m_info.version();
363     LOG(IndexedDB, "IDBDatabase::fireVersionChangeEvent - current version %" PRIu64 ", requested version %" PRIu64, currentVersion, requestedVersion);
364
365     if (!scriptExecutionContext() || m_closePending)
366         return;
367
368     Ref<Event> event = IDBVersionChangeEvent::create(currentVersion, requestedVersion, eventNames().versionchangeEvent);
369     event->setTarget(this);
370     scriptExecutionContext()->eventQueue().enqueueEvent(WTF::move(event));
371 }
372
373 void IDBDatabase::didCreateIndexInfo(const IDBIndexInfo& info)
374 {
375     auto* objectStore = m_info.infoForExistingObjectStore(info.objectStoreIdentifier());
376     ASSERT(objectStore);
377     objectStore->addExistingIndex(info);
378 }
379
380 void IDBDatabase::didDeleteIndexInfo(const IDBIndexInfo& info)
381 {
382     auto* objectStore = m_info.infoForExistingObjectStore(info.objectStoreIdentifier());
383     ASSERT(objectStore);
384     objectStore->deleteIndex(info.name());
385 }
386
387 } // namespace IDBClient
388 } // namespace WebCore
389
390 #endif // ENABLE(INDEXED_DATABASE)