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