2 * Copyright (C) 2015 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
27 #include "IDBServer.h"
29 #if ENABLE(INDEXED_DATABASE)
31 #include "IDBRequestData.h"
32 #include "IDBResultData.h"
34 #include "MemoryIDBBackingStore.h"
35 #include "SQLiteFileSystem.h"
36 #include "SQLiteIDBBackingStore.h"
37 #include "SecurityOrigin.h"
38 #include <wtf/CrossThreadCopier.h>
39 #include <wtf/Locker.h>
40 #include <wtf/MainThread.h>
45 Ref<IDBServer> IDBServer::create(IDBBackingStoreTemporaryFileHandler& fileHandler)
47 return adoptRef(*new IDBServer(fileHandler));
50 Ref<IDBServer> IDBServer::create(const String& databaseDirectoryPath, IDBBackingStoreTemporaryFileHandler& fileHandler)
52 return adoptRef(*new IDBServer(databaseDirectoryPath, fileHandler));
55 IDBServer::IDBServer(IDBBackingStoreTemporaryFileHandler& fileHandler)
56 : m_backingStoreTemporaryFileHandler(fileHandler)
58 Locker<Lock> locker(m_databaseThreadCreationLock);
59 m_threadID = createThread(IDBServer::databaseThreadEntry, this, "IndexedDatabase Server");
62 IDBServer::IDBServer(const String& databaseDirectoryPath, IDBBackingStoreTemporaryFileHandler& fileHandler)
63 : m_databaseDirectoryPath(databaseDirectoryPath)
64 , m_backingStoreTemporaryFileHandler(fileHandler)
66 LOG(IndexedDB, "IDBServer created at path %s", databaseDirectoryPath.utf8().data());
68 Locker<Lock> locker(m_databaseThreadCreationLock);
69 m_threadID = createThread(IDBServer::databaseThreadEntry, this, "IndexedDatabase Server");
72 void IDBServer::registerConnection(IDBConnectionToClient& connection)
74 ASSERT(!m_connectionMap.contains(connection.identifier()));
75 m_connectionMap.set(connection.identifier(), &connection);
78 void IDBServer::unregisterConnection(IDBConnectionToClient& connection)
80 ASSERT(m_connectionMap.contains(connection.identifier()));
81 ASSERT(m_connectionMap.get(connection.identifier()) == &connection);
83 connection.connectionToClientClosed();
85 m_connectionMap.remove(connection.identifier());
88 void IDBServer::registerTransaction(UniqueIDBDatabaseTransaction& transaction)
90 ASSERT(!m_transactions.contains(transaction.info().identifier()));
91 m_transactions.set(transaction.info().identifier(), &transaction);
94 void IDBServer::unregisterTransaction(UniqueIDBDatabaseTransaction& transaction)
96 ASSERT(m_transactions.contains(transaction.info().identifier()));
97 ASSERT(m_transactions.get(transaction.info().identifier()) == &transaction);
99 m_transactions.remove(transaction.info().identifier());
102 void IDBServer::registerDatabaseConnection(UniqueIDBDatabaseConnection& connection)
104 ASSERT(!m_databaseConnections.contains(connection.identifier()));
105 m_databaseConnections.set(connection.identifier(), &connection);
108 void IDBServer::unregisterDatabaseConnection(UniqueIDBDatabaseConnection& connection)
110 ASSERT(m_databaseConnections.contains(connection.identifier()));
111 m_databaseConnections.remove(connection.identifier());
114 UniqueIDBDatabase& IDBServer::getOrCreateUniqueIDBDatabase(const IDBDatabaseIdentifier& identifier)
116 ASSERT(isMainThread());
118 auto uniqueIDBDatabase = m_uniqueIDBDatabaseMap.add(identifier, nullptr);
119 if (uniqueIDBDatabase.isNewEntry)
120 uniqueIDBDatabase.iterator->value = UniqueIDBDatabase::create(*this, identifier);
122 return *uniqueIDBDatabase.iterator->value;
125 std::unique_ptr<IDBBackingStore> IDBServer::createBackingStore(const IDBDatabaseIdentifier& identifier)
127 ASSERT(!isMainThread());
129 if (m_databaseDirectoryPath.isEmpty())
130 return MemoryIDBBackingStore::create(identifier);
132 return std::make_unique<SQLiteIDBBackingStore>(identifier, m_databaseDirectoryPath, m_backingStoreTemporaryFileHandler);
135 void IDBServer::openDatabase(const IDBRequestData& requestData)
137 LOG(IndexedDB, "IDBServer::openDatabase");
139 auto& uniqueIDBDatabase = getOrCreateUniqueIDBDatabase(requestData.databaseIdentifier());
141 auto connection = m_connectionMap.get(requestData.requestIdentifier().connectionIdentifier());
143 // If the connection back to the client is gone, there's no way to open the database as
144 // well as no way to message back failure.
148 uniqueIDBDatabase.openDatabaseConnection(*connection, requestData);
151 void IDBServer::deleteDatabase(const IDBRequestData& requestData)
153 LOG(IndexedDB, "IDBServer::deleteDatabase - %s", requestData.databaseIdentifier().debugString().utf8().data());
154 ASSERT(isMainThread());
156 auto connection = m_connectionMap.get(requestData.requestIdentifier().connectionIdentifier());
158 // If the connection back to the client is gone, there's no way to delete the database as
159 // well as no way to message back failure.
163 auto* database = m_uniqueIDBDatabaseMap.get(requestData.databaseIdentifier());
165 database = &getOrCreateUniqueIDBDatabase(requestData.databaseIdentifier());
167 database->handleDelete(*connection, requestData);
170 void IDBServer::closeUniqueIDBDatabase(UniqueIDBDatabase& database)
172 LOG(IndexedDB, "IDBServer::closeUniqueIDBDatabase");
173 ASSERT(isMainThread());
175 m_uniqueIDBDatabaseMap.remove(database.identifier());
178 void IDBServer::abortTransaction(const IDBResourceIdentifier& transactionIdentifier)
180 LOG(IndexedDB, "IDBServer::abortTransaction");
182 auto transaction = m_transactions.get(transactionIdentifier);
184 // If there is no transaction there is nothing to abort.
185 // We also have no access to a connection over which to message failure-to-abort.
189 transaction->abort();
192 void IDBServer::createObjectStore(const IDBRequestData& requestData, const IDBObjectStoreInfo& info)
194 LOG(IndexedDB, "IDBServer::createObjectStore");
196 auto transaction = m_transactions.get(requestData.transactionIdentifier());
200 ASSERT(transaction->isVersionChange());
201 transaction->createObjectStore(requestData, info);
204 void IDBServer::deleteObjectStore(const IDBRequestData& requestData, const String& objectStoreName)
206 LOG(IndexedDB, "IDBServer::deleteObjectStore");
208 auto transaction = m_transactions.get(requestData.transactionIdentifier());
212 ASSERT(transaction->isVersionChange());
213 transaction->deleteObjectStore(requestData, objectStoreName);
216 void IDBServer::renameObjectStore(const IDBRequestData& requestData, uint64_t objectStoreIdentifier, const String& newName)
218 LOG(IndexedDB, "IDBServer::renameObjectStore");
220 auto transaction = m_transactions.get(requestData.transactionIdentifier());
224 ASSERT(transaction->isVersionChange());
225 transaction->renameObjectStore(requestData, objectStoreIdentifier, newName);
228 void IDBServer::clearObjectStore(const IDBRequestData& requestData, uint64_t objectStoreIdentifier)
230 LOG(IndexedDB, "IDBServer::clearObjectStore");
232 auto transaction = m_transactions.get(requestData.transactionIdentifier());
236 transaction->clearObjectStore(requestData, objectStoreIdentifier);
239 void IDBServer::createIndex(const IDBRequestData& requestData, const IDBIndexInfo& info)
241 LOG(IndexedDB, "IDBServer::createIndex");
243 auto transaction = m_transactions.get(requestData.transactionIdentifier());
247 ASSERT(transaction->isVersionChange());
248 transaction->createIndex(requestData, info);
251 void IDBServer::deleteIndex(const IDBRequestData& requestData, uint64_t objectStoreIdentifier, const String& indexName)
253 LOG(IndexedDB, "IDBServer::deleteIndex");
255 auto transaction = m_transactions.get(requestData.transactionIdentifier());
259 ASSERT(transaction->isVersionChange());
260 transaction->deleteIndex(requestData, objectStoreIdentifier, indexName);
263 void IDBServer::renameIndex(const IDBRequestData& requestData, uint64_t objectStoreIdentifier, uint64_t indexIdentifier, const String& newName)
265 LOG(IndexedDB, "IDBServer::renameIndex");
267 auto transaction = m_transactions.get(requestData.transactionIdentifier());
271 ASSERT(transaction->isVersionChange());
272 transaction->renameIndex(requestData, objectStoreIdentifier, indexIdentifier, newName);
275 void IDBServer::putOrAdd(const IDBRequestData& requestData, const IDBKeyData& keyData, const IDBValue& value, IndexedDB::ObjectStoreOverwriteMode overwriteMode)
277 LOG(IndexedDB, "IDBServer::putOrAdd");
279 auto transaction = m_transactions.get(requestData.transactionIdentifier());
283 transaction->putOrAdd(requestData, keyData, value, overwriteMode);
286 void IDBServer::getRecord(const IDBRequestData& requestData, const IDBGetRecordData& getRecordData)
288 LOG(IndexedDB, "IDBServer::getRecord");
290 auto transaction = m_transactions.get(requestData.transactionIdentifier());
294 transaction->getRecord(requestData, getRecordData);
297 void IDBServer::getAllRecords(const IDBRequestData& requestData, const IDBGetAllRecordsData& getAllRecordsData)
299 LOG(IndexedDB, "IDBServer::getAllRecords");
301 auto transaction = m_transactions.get(requestData.transactionIdentifier());
305 transaction->getAllRecords(requestData, getAllRecordsData);
308 void IDBServer::getCount(const IDBRequestData& requestData, const IDBKeyRangeData& keyRangeData)
310 LOG(IndexedDB, "IDBServer::getCount");
312 auto transaction = m_transactions.get(requestData.transactionIdentifier());
316 transaction->getCount(requestData, keyRangeData);
319 void IDBServer::deleteRecord(const IDBRequestData& requestData, const IDBKeyRangeData& keyRangeData)
321 LOG(IndexedDB, "IDBServer::deleteRecord");
323 auto transaction = m_transactions.get(requestData.transactionIdentifier());
327 transaction->deleteRecord(requestData, keyRangeData);
330 void IDBServer::openCursor(const IDBRequestData& requestData, const IDBCursorInfo& info)
332 LOG(IndexedDB, "IDBServer::openCursor");
334 auto transaction = m_transactions.get(requestData.transactionIdentifier());
338 transaction->openCursor(requestData, info);
341 void IDBServer::iterateCursor(const IDBRequestData& requestData, const IDBIterateCursorData& data)
343 LOG(IndexedDB, "IDBServer::iterateCursor");
345 auto transaction = m_transactions.get(requestData.transactionIdentifier());
349 transaction->iterateCursor(requestData, data);
352 void IDBServer::establishTransaction(uint64_t databaseConnectionIdentifier, const IDBTransactionInfo& info)
354 LOG(IndexedDB, "IDBServer::establishTransaction");
356 auto databaseConnection = m_databaseConnections.get(databaseConnectionIdentifier);
357 if (!databaseConnection)
360 databaseConnection->establishTransaction(info);
363 void IDBServer::commitTransaction(const IDBResourceIdentifier& transactionIdentifier)
365 LOG(IndexedDB, "IDBServer::commitTransaction");
367 auto transaction = m_transactions.get(transactionIdentifier);
369 // If there is no transaction there is nothing to commit.
370 // We also have no access to a connection over which to message failure-to-commit.
374 transaction->commit();
377 void IDBServer::didFinishHandlingVersionChangeTransaction(uint64_t databaseConnectionIdentifier, const IDBResourceIdentifier& transactionIdentifier)
379 LOG(IndexedDB, "IDBServer::didFinishHandlingVersionChangeTransaction - %s", transactionIdentifier.loggingString().utf8().data());
381 auto* connection = m_databaseConnections.get(databaseConnectionIdentifier);
385 connection->didFinishHandlingVersionChange(transactionIdentifier);
388 void IDBServer::databaseConnectionPendingClose(uint64_t databaseConnectionIdentifier)
390 LOG(IndexedDB, "IDBServer::databaseConnectionPendingClose - %" PRIu64, databaseConnectionIdentifier);
392 auto databaseConnection = m_databaseConnections.get(databaseConnectionIdentifier);
393 if (!databaseConnection)
396 databaseConnection->connectionPendingCloseFromClient();
399 void IDBServer::databaseConnectionClosed(uint64_t databaseConnectionIdentifier)
401 LOG(IndexedDB, "IDBServer::databaseConnectionClosed - %" PRIu64, databaseConnectionIdentifier);
403 auto databaseConnection = m_databaseConnections.get(databaseConnectionIdentifier);
404 if (!databaseConnection)
407 databaseConnection->connectionClosedFromClient();
410 void IDBServer::abortOpenAndUpgradeNeeded(uint64_t databaseConnectionIdentifier, const IDBResourceIdentifier& transactionIdentifier)
412 LOG(IndexedDB, "IDBServer::abortOpenAndUpgradeNeeded");
414 auto transaction = m_transactions.get(transactionIdentifier);
416 transaction->abortWithoutCallback();
418 auto databaseConnection = m_databaseConnections.get(databaseConnectionIdentifier);
419 if (!databaseConnection)
422 databaseConnection->connectionClosedFromClient();
425 void IDBServer::didFireVersionChangeEvent(uint64_t databaseConnectionIdentifier, const IDBResourceIdentifier& requestIdentifier)
427 LOG(IndexedDB, "IDBServer::didFireVersionChangeEvent");
429 if (auto databaseConnection = m_databaseConnections.get(databaseConnectionIdentifier))
430 databaseConnection->didFireVersionChangeEvent(requestIdentifier);
433 void IDBServer::openDBRequestCancelled(const IDBRequestData& requestData)
435 LOG(IndexedDB, "IDBServer::openDBRequestCancelled");
436 ASSERT(isMainThread());
438 auto* uniqueIDBDatabase = m_uniqueIDBDatabaseMap.get(requestData.databaseIdentifier());
439 if (!uniqueIDBDatabase)
442 uniqueIDBDatabase->openDBRequestCancelled(requestData.requestIdentifier());
445 void IDBServer::confirmDidCloseFromServer(uint64_t databaseConnectionIdentifier)
447 LOG(IndexedDB, "IDBServer::confirmDidCloseFromServer");
449 if (auto databaseConnection = m_databaseConnections.get(databaseConnectionIdentifier))
450 databaseConnection->confirmDidCloseFromServer();
453 void IDBServer::getAllDatabaseNames(uint64_t serverConnectionIdentifier, const SecurityOriginData& mainFrameOrigin, const SecurityOriginData& openingOrigin, uint64_t callbackID)
455 postDatabaseTask(createCrossThreadTask(*this, &IDBServer::performGetAllDatabaseNames, serverConnectionIdentifier, mainFrameOrigin, openingOrigin, callbackID));
458 void IDBServer::performGetAllDatabaseNames(uint64_t serverConnectionIdentifier, const SecurityOriginData& mainFrameOrigin, const SecurityOriginData& openingOrigin, uint64_t callbackID)
460 String directory = IDBDatabaseIdentifier::databaseDirectoryRelativeToRoot(mainFrameOrigin, openingOrigin, m_databaseDirectoryPath);
462 Vector<String> entries = listDirectory(directory, ASCIILiteral("*"));
463 Vector<String> databases;
464 databases.reserveInitialCapacity(entries.size());
465 for (auto& entry : entries) {
466 String encodedName = lastComponentOfPathIgnoringTrailingSlash(entry);
467 databases.uncheckedAppend(SQLiteIDBBackingStore::databaseNameFromEncodedFilename(encodedName));
470 postDatabaseTaskReply(createCrossThreadTask(*this, &IDBServer::didGetAllDatabaseNames, serverConnectionIdentifier, callbackID, databases));
473 void IDBServer::didGetAllDatabaseNames(uint64_t serverConnectionIdentifier, uint64_t callbackID, const Vector<String>& databaseNames)
475 auto connection = m_connectionMap.get(serverConnectionIdentifier);
479 connection->didGetAllDatabaseNames(callbackID, databaseNames);
482 void IDBServer::postDatabaseTask(CrossThreadTask&& task)
484 ASSERT(isMainThread());
485 m_databaseQueue.append(WTFMove(task));
488 void IDBServer::postDatabaseTaskReply(CrossThreadTask&& task)
490 ASSERT(!isMainThread());
491 m_databaseReplyQueue.append(WTFMove(task));
494 Locker<Lock> locker(m_mainThreadReplyLock);
495 if (m_mainThreadReplyScheduled)
498 m_mainThreadReplyScheduled = true;
499 callOnMainThread([this] {
500 handleTaskRepliesOnMainThread();
504 void IDBServer::databaseThreadEntry(void* threadData)
507 IDBServer* server = reinterpret_cast<IDBServer*>(threadData);
508 server->databaseRunLoop();
511 void IDBServer::databaseRunLoop()
513 ASSERT(!isMainThread());
515 Locker<Lock> locker(m_databaseThreadCreationLock);
518 while (!m_databaseQueue.isKilled())
519 m_databaseQueue.waitForMessage().performTask();
522 void IDBServer::handleTaskRepliesOnMainThread()
525 Locker<Lock> locker(m_mainThreadReplyLock);
526 m_mainThreadReplyScheduled = false;
529 while (auto task = m_databaseReplyQueue.tryGetMessage())
533 static uint64_t generateDeleteCallbackID()
535 ASSERT(isMainThread());
536 static uint64_t currentID = 0;
540 void IDBServer::closeAndDeleteDatabasesModifiedSince(std::chrono::system_clock::time_point modificationTime, std::function<void ()> completionHandler)
542 uint64_t callbackID = generateDeleteCallbackID();
543 auto addResult = m_deleteDatabaseCompletionHandlers.add(callbackID, WTFMove(completionHandler));
544 ASSERT_UNUSED(addResult, addResult.isNewEntry);
546 // If the modification time is in the future, don't both doing anything.
547 if (modificationTime > std::chrono::system_clock::now()) {
548 postDatabaseTaskReply(createCrossThreadTask(*this, &IDBServer::didPerformCloseAndDeleteDatabases, callbackID));
552 HashSet<RefPtr<UniqueIDBDatabase>> openDatabases;
553 for (auto* connection : m_databaseConnections.values())
554 openDatabases.add(&connection->database());
556 for (auto& database : openDatabases)
557 database->immediateCloseForUserDelete();
559 postDatabaseTask(createCrossThreadTask(*this, &IDBServer::performCloseAndDeleteDatabasesModifiedSince, modificationTime, callbackID));
562 void IDBServer::closeAndDeleteDatabasesForOrigins(const Vector<SecurityOriginData>& origins, std::function<void ()> completionHandler)
564 uint64_t callbackID = generateDeleteCallbackID();
565 auto addResult = m_deleteDatabaseCompletionHandlers.add(callbackID, WTFMove(completionHandler));
566 ASSERT_UNUSED(addResult, addResult.isNewEntry);
568 HashSet<RefPtr<UniqueIDBDatabase>> openDatabases;
569 for (auto* connection : m_databaseConnections.values()) {
570 const auto& identifier = connection->database().identifier();
571 for (auto& origin : origins) {
572 if (identifier.isRelatedToOrigin(origin)) {
573 openDatabases.add(&connection->database());
579 for (auto& database : openDatabases)
580 database->immediateCloseForUserDelete();
582 postDatabaseTask(createCrossThreadTask(*this, &IDBServer::performCloseAndDeleteDatabasesForOrigins, origins, callbackID));
585 static void removeAllDatabasesForOriginPath(const String& originPath, std::chrono::system_clock::time_point modifiedSince)
587 Vector<String> databasePaths = listDirectory(originPath, "*");
589 for (auto& databasePath : databasePaths) {
590 String databaseFile = pathByAppendingComponent(databasePath, "IndexedDB.sqlite3");
592 if (modifiedSince > std::chrono::system_clock::time_point::min() && fileExists(databaseFile)) {
593 time_t modificationTime;
594 if (!getFileModificationTime(databaseFile, modificationTime))
597 if (std::chrono::system_clock::from_time_t(modificationTime) < modifiedSince)
601 // Deleting this database means we need to delete all files that represent it.
603 // - The directory itself, which is named after the database.
604 // - IndexedDB.sqlite3 and related SQLite files.
605 // - Blob files that we stored in the directory.
607 // To be conservative, we should *not* try to delete files that are unexpected;
608 // We should only delete files we think we put there.
610 // IndexedDB blob files are named "N.blob" where N is a decimal integer,
611 // so those are the only blob files we should be trying to delete.
612 for (auto& blobPath : listDirectory(databasePath, "[0-9]*.blob")) {
613 // Globbing can't give us only filenames starting with 1-or-more digits.
614 // The above globbing gives us files that start with a digit and ends with ".blob", but there might be non-digits in between.
615 // We need to validate that each filename contains only digits before deleting it, as any other files are not ones we put there.
616 String filename = pathGetFileName(blobPath);
617 auto filenameLength = filename.length();
619 ASSERT(filenameLength >= 6);
620 ASSERT(filename.endsWith(".blob"));
622 if (filename.length() < 6)
624 if (!filename.endsWith(".blob"))
627 bool validFilename = true;
628 for (unsigned i = 0; i < filenameLength - 5; ++i) {
629 if (!isASCIIDigit(filename[i])) {
630 validFilename = false;
636 deleteFile(blobPath);
639 // Now delete IndexedDB.sqlite3 and related SQLite files.
640 SQLiteFileSystem::deleteDatabaseFile(databaseFile);
642 // And finally, if we can, delete the empty directory.
643 deleteEmptyDirectory(databasePath);
646 // If no databases remain for this origin, we can delete the origin directory as well.
647 deleteEmptyDirectory(originPath);
650 void IDBServer::performCloseAndDeleteDatabasesModifiedSince(std::chrono::system_clock::time_point modifiedSince, uint64_t callbackID)
652 if (!m_databaseDirectoryPath.isEmpty()) {
653 Vector<String> originPaths = listDirectory(m_databaseDirectoryPath, "*");
654 for (auto& originPath : originPaths)
655 removeAllDatabasesForOriginPath(originPath, modifiedSince);
658 postDatabaseTaskReply(createCrossThreadTask(*this, &IDBServer::didPerformCloseAndDeleteDatabases, callbackID));
661 void IDBServer::performCloseAndDeleteDatabasesForOrigins(const Vector<SecurityOriginData>& origins, uint64_t callbackID)
663 if (!m_databaseDirectoryPath.isEmpty()) {
664 for (const auto& origin : origins) {
665 String originPath = pathByAppendingComponent(m_databaseDirectoryPath, origin.databaseIdentifier());
666 removeAllDatabasesForOriginPath(originPath, std::chrono::system_clock::time_point::min());
670 postDatabaseTaskReply(createCrossThreadTask(*this, &IDBServer::didPerformCloseAndDeleteDatabases, callbackID));
673 void IDBServer::didPerformCloseAndDeleteDatabases(uint64_t callbackID)
675 auto callback = m_deleteDatabaseCompletionHandlers.take(callbackID);
680 } // namespace IDBServer
681 } // namespace WebCore
683 #endif // ENABLE(INDEXED_DATABASE)