Modern IDB: After versionchange transactions abort, fire onerror on the original...
[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     suspendIfNeeded();
56     relaxAdoptionRequirement();
57     m_serverConnection->registerDatabaseConnection(*this);
58 }
59
60 IDBDatabase::~IDBDatabase()
61 {
62     m_serverConnection->unregisterDatabaseConnection(*this);
63 }
64
65 bool IDBDatabase::hasPendingActivity() const
66 {
67     return !m_closedInServer;
68 }
69
70 const String IDBDatabase::name() const
71 {
72     return m_info.name();
73 }
74
75 uint64_t IDBDatabase::version() const
76 {
77     return m_info.version();
78 }
79
80 RefPtr<DOMStringList> IDBDatabase::objectStoreNames() const
81 {
82     RefPtr<DOMStringList> objectStoreNames = DOMStringList::create();
83     for (auto& name : m_info.objectStoreNames())
84         objectStoreNames->append(name);
85     objectStoreNames->sort();
86     return WTF::move(objectStoreNames);
87 }
88
89 RefPtr<WebCore::IDBObjectStore> IDBDatabase::createObjectStore(const String&, const Dictionary&, ExceptionCode&)
90 {
91     ASSERT_NOT_REACHED();
92     return nullptr;
93 }
94
95 RefPtr<WebCore::IDBObjectStore> IDBDatabase::createObjectStore(const String& name, const IDBKeyPath& keyPath, bool autoIncrement, ExceptionCode& ec)
96 {
97     LOG(IndexedDB, "IDBDatabase::createObjectStore");
98
99     ASSERT(!m_versionChangeTransaction || m_versionChangeTransaction->isVersionChange());
100
101     if (!m_versionChangeTransaction) {
102         ec = IDBDatabaseException::InvalidStateError;
103         return nullptr;
104     }
105
106     if (!m_versionChangeTransaction->isActive()) {
107         ec = IDBDatabaseException::TransactionInactiveError;
108         return nullptr;
109     }
110
111     if (m_info.hasObjectStore(name)) {
112         ec = IDBDatabaseException::ConstraintError;
113         return nullptr;
114     }
115
116     if (!keyPath.isNull() && !keyPath.isValid()) {
117         ec = IDBDatabaseException::SyntaxError;
118         return nullptr;
119     }
120
121     if (autoIncrement && !keyPath.isNull()) {
122         if ((keyPath.type() == IndexedDB::KeyPathType::String && keyPath.string().isEmpty()) || keyPath.type() == IndexedDB::KeyPathType::Array) {
123             ec = IDBDatabaseException::InvalidAccessError;
124             return nullptr;
125         }
126     }
127
128     // Install the new ObjectStore into the connection's metadata.
129     IDBObjectStoreInfo info = m_info.createNewObjectStore(name, keyPath, autoIncrement);
130
131     // Create the actual IDBObjectStore from the transaction, which also schedules the operation server side.
132     Ref<IDBObjectStore> objectStore = m_versionChangeTransaction->createObjectStore(info);
133     return adoptRef(&objectStore.leakRef());
134 }
135
136 RefPtr<WebCore::IDBTransaction> IDBDatabase::transaction(ScriptExecutionContext*, const Vector<String>& objectStores, const String& modeString, ExceptionCode& ec)
137 {
138     LOG(IndexedDB, "IDBDatabase::transaction");
139
140     if (m_closePending) {
141         ec = IDBDatabaseException::InvalidStateError;
142         return nullptr;
143     }
144
145     if (objectStores.isEmpty()) {
146         ec = IDBDatabaseException::InvalidAccessError;
147         return nullptr;
148     }
149
150     IndexedDB::TransactionMode mode = IDBTransaction::stringToMode(modeString, ec);
151     if (ec)
152         return nullptr;
153
154     if (mode != IndexedDB::TransactionMode::ReadOnly && mode != IndexedDB::TransactionMode::ReadWrite) {
155         ec = TypeError;
156         return nullptr;
157     }
158
159     if (m_versionChangeTransaction && !m_versionChangeTransaction->isFinishedOrFinishing()) {
160         ec = IDBDatabaseException::InvalidStateError;
161         return nullptr;
162     }
163
164     for (auto& objectStoreName : objectStores) {
165         if (m_info.hasObjectStore(objectStoreName))
166             continue;
167         ec = IDBDatabaseException::NotFoundError;
168         return nullptr;
169     }
170
171     auto info = IDBTransactionInfo::clientTransaction(m_serverConnection.get(), objectStores, mode);
172     auto transaction = IDBTransaction::create(*this, info);
173
174     LOG(IndexedDB, "IDBDatabase::transaction - Added active transaction %s", info.identifier().loggingString().utf8().data());
175
176     m_activeTransactions.set(info.identifier(), &transaction.get());
177
178     return adoptRef(&transaction.leakRef());
179 }
180
181 RefPtr<WebCore::IDBTransaction> IDBDatabase::transaction(ScriptExecutionContext* context, const String& objectStore, const String& mode, ExceptionCode& ec)
182 {
183     Vector<String> objectStores(1);
184     objectStores[0] = objectStore;
185     return transaction(context, objectStores, mode, ec);
186 }
187
188 void IDBDatabase::deleteObjectStore(const String& objectStoreName, ExceptionCode& ec)
189 {
190     LOG(IndexedDB, "IDBDatabase::deleteObjectStore");
191
192     if (!m_versionChangeTransaction) {
193         ec = INVALID_STATE_ERR;
194         return;
195     }
196
197     if (!m_versionChangeTransaction->isActive()) {
198         ec = IDBDatabaseException::TransactionInactiveError;
199         return;
200     }
201
202     if (!m_info.hasObjectStore(objectStoreName)) {
203         ec = IDBDatabaseException::NotFoundError;
204         return;
205     }
206
207     m_info.deleteObjectStore(objectStoreName);
208     m_versionChangeTransaction->deleteObjectStore(objectStoreName);
209 }
210
211 void IDBDatabase::close()
212 {
213     m_closePending = true;
214     maybeCloseInServer();
215 }
216
217 void IDBDatabase::maybeCloseInServer()
218 {
219     if (m_closedInServer)
220         return;
221
222     // 3.3.9 Database closing steps
223     // Wait for all transactions created using this connection to complete.
224     // Once they are complete, this connection is closed.
225     if (!m_activeTransactions.isEmpty() || !m_committingTransactions.isEmpty())
226         return;
227
228     m_closedInServer = true;
229     m_serverConnection->databaseConnectionClosed(*this);
230 }
231
232 const char* IDBDatabase::activeDOMObjectName() const
233 {
234     return "IDBDatabase";
235 }
236
237 bool IDBDatabase::canSuspendForPageCache() const
238 {
239     // FIXME: This value will sometimes be false when database operations are actually in progress.
240     // Such database operations do not yet exist.
241     return true;
242 }
243
244 Ref<IDBTransaction> IDBDatabase::startVersionChangeTransaction(const IDBTransactionInfo& info, IDBOpenDBRequest& request)
245 {
246     LOG(IndexedDB, "IDBDatabase::startVersionChangeTransaction %s", info.identifier().loggingString().utf8().data());
247
248     ASSERT(!m_versionChangeTransaction);
249     ASSERT(info.mode() == IndexedDB::TransactionMode::VersionChange);
250
251     Ref<IDBTransaction> transaction = IDBTransaction::create(*this, info, request);
252     m_versionChangeTransaction = &transaction.get();
253
254     m_activeTransactions.set(transaction->info().identifier(), &transaction.get());
255
256     return WTF::move(transaction);
257 }
258
259 void IDBDatabase::didStartTransaction(IDBTransaction& transaction)
260 {
261     LOG(IndexedDB, "IDBDatabase::didStartTransaction %s", transaction.info().identifier().loggingString().utf8().data());
262     ASSERT(!m_versionChangeTransaction);
263
264     // It is possible for the client to have aborted a transaction before the server replies back that it has started.
265     if (m_abortingTransactions.contains(transaction.info().identifier()))
266         return;
267
268     m_activeTransactions.set(transaction.info().identifier(), &transaction);
269 }
270
271 void IDBDatabase::willCommitTransaction(IDBTransaction& transaction)
272 {
273     LOG(IndexedDB, "IDBDatabase::willCommitTransaction %s", transaction.info().identifier().loggingString().utf8().data());
274
275     auto refTransaction = m_activeTransactions.take(transaction.info().identifier());
276     ASSERT(refTransaction);
277     m_committingTransactions.set(transaction.info().identifier(), WTF::move(refTransaction));
278 }
279
280 void IDBDatabase::didCommitTransaction(IDBTransaction& transaction)
281 {
282     LOG(IndexedDB, "IDBDatabase::didCommitTransaction %s", transaction.info().identifier().loggingString().utf8().data());
283
284     if (m_versionChangeTransaction == &transaction)
285         m_info.setVersion(transaction.info().newVersion());
286
287     didCommitOrAbortTransaction(transaction);
288 }
289
290 void IDBDatabase::willAbortTransaction(IDBTransaction& transaction)
291 {
292     LOG(IndexedDB, "IDBDatabase::willAbortTransaction %s", transaction.info().identifier().loggingString().utf8().data());
293
294     auto refTransaction = m_activeTransactions.take(transaction.info().identifier());
295     ASSERT(refTransaction);
296     m_abortingTransactions.set(transaction.info().identifier(), WTF::move(refTransaction));
297
298     if (transaction.isVersionChange())
299         m_closePending = true;
300 }
301
302 void IDBDatabase::didAbortTransaction(IDBTransaction& transaction)
303 {
304     LOG(IndexedDB, "IDBDatabase::didAbortTransaction %s", transaction.info().identifier().loggingString().utf8().data());
305
306     if (transaction.isVersionChange()) {
307         ASSERT(transaction.originalDatabaseInfo());
308         m_info = *transaction.originalDatabaseInfo();
309         m_closePending = true;
310         m_closedInServer = true;
311         m_serverConnection->databaseConnectionClosed(*this);
312     }
313
314     didCommitOrAbortTransaction(transaction);
315 }
316
317 void IDBDatabase::didCommitOrAbortTransaction(IDBTransaction& transaction)
318 {
319     LOG(IndexedDB, "IDBDatabase::didCommitOrAbortTransaction %s", transaction.info().identifier().loggingString().utf8().data());
320
321     if (m_versionChangeTransaction == &transaction)
322         m_versionChangeTransaction = nullptr;
323
324 #ifndef NDBEBUG
325     unsigned count = 0;
326     if (m_activeTransactions.contains(transaction.info().identifier()))
327         ++count;
328     if (m_committingTransactions.contains(transaction.info().identifier()))
329         ++count;
330     if (m_abortingTransactions.contains(transaction.info().identifier()))
331         ++count;
332
333     ASSERT(count == 1);
334 #endif
335
336     m_activeTransactions.remove(transaction.info().identifier());
337     m_committingTransactions.remove(transaction.info().identifier());
338     m_abortingTransactions.remove(transaction.info().identifier());
339
340     if (m_closePending)
341         maybeCloseInServer();
342 }
343
344 void IDBDatabase::fireVersionChangeEvent(uint64_t requestedVersion)
345 {
346     uint64_t currentVersion = m_info.version();
347     LOG(IndexedDB, "IDBDatabase::fireVersionChangeEvent - current version %" PRIu64 ", requested version %" PRIu64, currentVersion, requestedVersion);
348
349     if (!scriptExecutionContext())
350         return;
351     
352     Ref<Event> event = IDBVersionChangeEvent::create(currentVersion, requestedVersion, eventNames().versionchangeEvent);
353     event->setTarget(this);
354     scriptExecutionContext()->eventQueue().enqueueEvent(WTF::move(event));
355 }
356
357 void IDBDatabase::didCreateIndexInfo(const IDBIndexInfo& info)
358 {
359     auto* objectStore = m_info.infoForExistingObjectStore(info.objectStoreIdentifier());
360     ASSERT(objectStore);
361     objectStore->addExistingIndex(info);
362 }
363
364 void IDBDatabase::didDeleteIndexInfo(const IDBIndexInfo& info)
365 {
366     auto* objectStore = m_info.infoForExistingObjectStore(info.objectStoreIdentifier());
367     ASSERT(objectStore);
368     objectStore->deleteIndex(info.name());
369 }
370
371 } // namespace IDBClient
372 } // namespace WebCore
373
374 #endif // ENABLE(INDEXED_DATABASE)