IndexedDB 2.0: Clean up some exception ordering.
[WebKit-https.git] / Source / WebCore / Modules / indexeddb / IDBObjectStore.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 "IDBObjectStore.h"
28
29 #if ENABLE(INDEXED_DATABASE)
30
31 #include "DOMStringList.h"
32 #include "Document.h"
33 #include "IDBBindingUtilities.h"
34 #include "IDBCursor.h"
35 #include "IDBDatabase.h"
36 #include "IDBDatabaseException.h"
37 #include "IDBError.h"
38 #include "IDBGetRecordData.h"
39 #include "IDBIndex.h"
40 #include "IDBKey.h"
41 #include "IDBKeyRangeData.h"
42 #include "IDBRequest.h"
43 #include "IDBTransaction.h"
44 #include "IndexedDB.h"
45 #include "Logging.h"
46 #include "Page.h"
47 #include "ScriptExecutionContext.h"
48 #include "ScriptState.h"
49 #include "SerializedScriptValue.h"
50 #include <wtf/Locker.h>
51
52 using namespace JSC;
53
54 namespace WebCore {
55
56 IDBObjectStore::IDBObjectStore(ScriptExecutionContext& context, const IDBObjectStoreInfo& info, IDBTransaction& transaction)
57     : ActiveDOMObject(&context)
58     , m_info(info)
59     , m_originalInfo(info)
60     , m_transaction(transaction)
61 {
62     ASSERT(currentThread() == m_transaction.database().originThreadID());
63
64     suspendIfNeeded();
65 }
66
67 IDBObjectStore::~IDBObjectStore()
68 {
69     ASSERT(currentThread() == m_transaction.database().originThreadID());
70 }
71
72 const char* IDBObjectStore::activeDOMObjectName() const
73 {
74     return "IDBObjectStore";
75 }
76
77 bool IDBObjectStore::canSuspendForDocumentSuspension() const
78 {
79     return false;
80 }
81
82 bool IDBObjectStore::hasPendingActivity() const
83 {
84     return !m_transaction.isFinished();
85 }
86
87 const String& IDBObjectStore::name() const
88 {
89     ASSERT(currentThread() == m_transaction.database().originThreadID());
90     return m_info.name();
91 }
92
93 ExceptionOr<void> IDBObjectStore::setName(const String& name)
94 {
95     ASSERT(currentThread() == m_transaction.database().originThreadID());
96
97     if (m_deleted)
98         return Exception { INVALID_STATE_ERR, ASCIILiteral("Failed set property 'name' on 'IDBObjectStore': The object store has been deleted.") };
99
100     if (!m_transaction.isVersionChange())
101         return Exception { INVALID_STATE_ERR, ASCIILiteral("Failed set property 'name' on 'IDBObjectStore': The object store's transaction is not a version change transaction.") };
102
103     if (!m_transaction.isActive())
104         return Exception { IDBDatabaseException::TransactionInactiveError, ASCIILiteral("Failed set property 'name' on 'IDBObjectStore': The object store's transaction is not active.") };
105
106     if (m_info.name() == name)
107         return { };
108
109     if (m_transaction.database().info().hasObjectStore(name))
110         return Exception { IDBDatabaseException::ConstraintError, makeString("Failed set property 'name' on 'IDBObjectStore': The database already has an object store named '", name, "'.") };
111
112     m_transaction.database().renameObjectStore(*this, name);
113     m_info.rename(name);
114
115     return { };
116 }
117
118 const Optional<IDBKeyPath>& IDBObjectStore::keyPath() const
119 {
120     ASSERT(currentThread() == m_transaction.database().originThreadID());
121     return m_info.keyPath();
122 }
123
124 RefPtr<DOMStringList> IDBObjectStore::indexNames() const
125 {
126     ASSERT(currentThread() == m_transaction.database().originThreadID());
127
128     RefPtr<DOMStringList> indexNames = DOMStringList::create();
129
130     if (!m_deleted) {
131         for (auto& name : m_info.indexNames())
132             indexNames->append(name);
133         indexNames->sort();
134     }
135
136     return indexNames;
137 }
138
139 IDBTransaction& IDBObjectStore::transaction()
140 {
141     ASSERT(currentThread() == m_transaction.database().originThreadID());
142     return m_transaction;
143 }
144
145 bool IDBObjectStore::autoIncrement() const
146 {
147     ASSERT(currentThread() == m_transaction.database().originThreadID());
148     return m_info.autoIncrement();
149 }
150
151 ExceptionOr<Ref<IDBRequest>> IDBObjectStore::openCursor(ExecState& execState, RefPtr<IDBKeyRange> range, const String& directionString)
152 {
153     LOG(IndexedDB, "IDBObjectStore::openCursor");
154     ASSERT(currentThread() == m_transaction.database().originThreadID());
155
156     if (m_deleted)
157         return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'openCursor' on 'IDBObjectStore': The object store has been deleted.") };
158
159     if (!m_transaction.isActive())
160         return Exception { IDBDatabaseException::TransactionInactiveError, ASCIILiteral("Failed to execute 'openCursor' on 'IDBObjectStore': The transaction is inactive or finished.") };
161
162     auto direction = IDBCursor::stringToDirection(directionString);
163     if (!direction)
164         return Exception { TypeError };
165
166     auto info = IDBCursorInfo::objectStoreCursor(m_transaction, m_info.identifier(), range.get(), direction.value(), IndexedDB::CursorType::KeyAndValue);
167     return m_transaction.requestOpenCursor(execState, *this, info);
168 }
169
170 ExceptionOr<Ref<IDBRequest>> IDBObjectStore::openCursor(ExecState& execState, JSValue key, const String& direction)
171 {
172     auto onlyResult = IDBKeyRange::only(execState, key);
173     if (onlyResult.hasException())
174         return Exception { IDBDatabaseException::DataError, ASCIILiteral("Failed to execute 'openCursor' on 'IDBObjectStore': The parameter is not a valid key.") };
175
176     return openCursor(execState, onlyResult.releaseReturnValue(), direction);
177 }
178
179 ExceptionOr<Ref<IDBRequest>> IDBObjectStore::openKeyCursor(ExecState& execState, RefPtr<IDBKeyRange> range, const String& directionString)
180 {
181     LOG(IndexedDB, "IDBObjectStore::openCursor");
182     ASSERT(currentThread() == m_transaction.database().originThreadID());
183
184     if (m_deleted)
185         return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'openKeyCursor' on 'IDBObjectStore': The object store has been deleted.") };
186
187     if (!m_transaction.isActive())
188         return Exception { IDBDatabaseException::TransactionInactiveError, ASCIILiteral("Failed to execute 'openKeyCursor' on 'IDBObjectStore': The transaction is inactive or finished.") };
189
190     auto direction = IDBCursor::stringToDirection(directionString);
191     if (!direction)
192         return Exception { TypeError };
193
194     auto info = IDBCursorInfo::objectStoreCursor(m_transaction, m_info.identifier(), range.get(), direction.value(), IndexedDB::CursorType::KeyOnly);
195     return m_transaction.requestOpenCursor(execState, *this, info);
196 }
197
198 ExceptionOr<Ref<IDBRequest>> IDBObjectStore::openKeyCursor(ExecState& execState, JSValue key, const String& direction)
199 {
200     auto onlyResult = IDBKeyRange::only(execState, key);
201     if (onlyResult.hasException())
202         return Exception { IDBDatabaseException::DataError, ASCIILiteral("Failed to execute 'openKeyCursor' on 'IDBObjectStore': The parameter is not a valid key or key range.") };
203
204     return openKeyCursor(execState, onlyResult.releaseReturnValue(), direction);
205 }
206
207 ExceptionOr<Ref<IDBRequest>> IDBObjectStore::get(ExecState& execState, JSValue key)
208 {
209     LOG(IndexedDB, "IDBObjectStore::get");
210     ASSERT(currentThread() == m_transaction.database().originThreadID());
211
212     if (m_deleted)
213         return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'get' on 'IDBObjectStore': The object store has been deleted.") };
214
215     if (!m_transaction.isActive())
216         return Exception { IDBDatabaseException::TransactionInactiveError, ASCIILiteral("Failed to execute 'get' on 'IDBObjectStore': The transaction is inactive or finished.") };
217
218     auto idbKey = scriptValueToIDBKey(execState, key);
219     if (!idbKey->isValid())
220         return Exception { IDBDatabaseException::DataError, ASCIILiteral("Failed to execute 'get' on 'IDBObjectStore': The parameter is not a valid key.") };
221
222     return m_transaction.requestGetRecord(execState, *this, { idbKey.ptr() });
223 }
224
225 ExceptionOr<Ref<IDBRequest>> IDBObjectStore::get(ExecState& execState, IDBKeyRange* keyRange)
226 {
227     LOG(IndexedDB, "IDBObjectStore::get");
228     ASSERT(currentThread() == m_transaction.database().originThreadID());
229
230     if (m_deleted)
231         return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'get' on 'IDBObjectStore': The object store has been deleted.") };
232
233     if (!m_transaction.isActive())
234         return Exception { IDBDatabaseException::TransactionInactiveError };
235
236     IDBKeyRangeData keyRangeData(keyRange);
237     if (!keyRangeData.isValid())
238         return Exception { IDBDatabaseException::DataError };
239
240     return m_transaction.requestGetRecord(execState, *this, { keyRangeData });
241 }
242
243 ExceptionOr<Ref<IDBRequest>> IDBObjectStore::add(ExecState& execState, JSValue value, JSValue key)
244 {
245     RefPtr<IDBKey> idbKey;
246     if (!key.isUndefined())
247         idbKey = scriptValueToIDBKey(execState, key);
248     return putOrAdd(execState, value, idbKey, IndexedDB::ObjectStoreOverwriteMode::NoOverwrite, InlineKeyCheck::Perform);
249 }
250
251 ExceptionOr<Ref<IDBRequest>> IDBObjectStore::put(ExecState& execState, JSValue value, JSValue key)
252 {
253     RefPtr<IDBKey> idbKey;
254     if (!key.isUndefined())
255         idbKey = scriptValueToIDBKey(execState, key);
256     return putOrAdd(execState, value, idbKey, IndexedDB::ObjectStoreOverwriteMode::Overwrite, InlineKeyCheck::Perform);
257 }
258
259 ExceptionOr<Ref<IDBRequest>> IDBObjectStore::putForCursorUpdate(ExecState& state, JSValue value, JSValue key)
260 {
261     return putOrAdd(state, value, scriptValueToIDBKey(state, key), IndexedDB::ObjectStoreOverwriteMode::OverwriteForCursor, InlineKeyCheck::DoNotPerform);
262 }
263
264 ExceptionOr<Ref<IDBRequest>> IDBObjectStore::putOrAdd(ExecState& state, JSValue value, RefPtr<IDBKey> key, IndexedDB::ObjectStoreOverwriteMode overwriteMode, InlineKeyCheck inlineKeyCheck)
265 {
266     VM& vm = state.vm();
267     auto scope = DECLARE_CATCH_SCOPE(vm);
268
269     LOG(IndexedDB, "IDBObjectStore::putOrAdd");
270     ASSERT(currentThread() == m_transaction.database().originThreadID());
271
272     auto context = scriptExecutionContextFromExecState(&state);
273     if (!context)
274         return Exception { IDBDatabaseException::UnknownError, ASCIILiteral("Unable to store record in object store because it does not have a valid script execution context") };
275
276     if (m_deleted)
277         return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to store record in an IDBObjectStore: The object store has been deleted.") };
278
279     if (!m_transaction.isActive())
280         return Exception { IDBDatabaseException::TransactionInactiveError, ASCIILiteral("Failed to store record in an IDBObjectStore: The transaction is inactive or finished.") };
281
282     if (m_transaction.isReadOnly())
283         return Exception { IDBDatabaseException::ReadOnlyError, ASCIILiteral("Failed to store record in an IDBObjectStore: The transaction is read-only.") };
284
285     auto serializedValue = SerializedScriptValue::create(state, value);
286     if (UNLIKELY(scope.exception())) {
287         // Clear the DOM exception from the serializer so we can give a more targeted exception.
288         scope.clearException();
289
290         return Exception { IDBDatabaseException::DataCloneError, ASCIILiteral("Failed to store record in an IDBObjectStore: An object could not be cloned.") };
291     }
292
293     bool privateBrowsingEnabled = false;
294     if (context->isDocument()) {
295         if (auto* page = static_cast<Document*>(context)->page())
296             privateBrowsingEnabled = page->sessionID().isEphemeral();
297     }
298
299     if (serializedValue->hasBlobURLs() && privateBrowsingEnabled) {
300         // https://bugs.webkit.org/show_bug.cgi?id=156347 - Support Blobs in private browsing.
301         return Exception { IDBDatabaseException::DataCloneError, ASCIILiteral("Failed to store record in an IDBObjectStore: BlobURLs are not yet supported.") };
302     }
303
304     if (key && !key->isValid())
305         return Exception { IDBDatabaseException::DataError, ASCIILiteral("Failed to store record in an IDBObjectStore: The parameter is not a valid key.") };
306
307     bool usesInlineKeys = !!m_info.keyPath();
308     bool usesKeyGenerator = autoIncrement();
309     if (usesInlineKeys && inlineKeyCheck == InlineKeyCheck::Perform) {
310         if (key)
311             return Exception { IDBDatabaseException::DataError, ASCIILiteral("Failed to store record in an IDBObjectStore: The object store uses in-line keys and the key parameter was provided.") };
312
313         RefPtr<IDBKey> keyPathKey = maybeCreateIDBKeyFromScriptValueAndKeyPath(state, value, m_info.keyPath().value());
314         if (keyPathKey && !keyPathKey->isValid())
315             return Exception { IDBDatabaseException::DataError, ASCIILiteral("Failed to store record in an IDBObjectStore: Evaluating the object store's key path yielded a value that is not a valid key.") };
316
317         if (!keyPathKey) {
318             if (!usesKeyGenerator)
319                 return Exception { IDBDatabaseException::DataError, ASCIILiteral("Failed to store record in an IDBObjectStore: Evaluating the object store's key path did not yield a value.") };
320             if (!canInjectIDBKeyIntoScriptValue(state, value, m_info.keyPath().value()))
321                 return Exception { IDBDatabaseException::DataError };
322         }
323
324         if (keyPathKey) {
325             ASSERT(!key);
326             key = keyPathKey;
327         }
328     } else if (!usesKeyGenerator && !key)
329         return Exception { IDBDatabaseException::DataError, ASCIILiteral("Failed to store record in an IDBObjectStore: The object store uses out-of-line keys and has no key generator and the key parameter was not provided.") };
330
331     return m_transaction.requestPutOrAdd(state, *this, key.get(), *serializedValue, overwriteMode);
332 }
333
334 ExceptionOr<Ref<IDBRequest>> IDBObjectStore::deleteFunction(ExecState& execState, IDBKeyRange* keyRange)
335 {
336     return doDelete(execState, keyRange);
337 }
338
339 ExceptionOr<Ref<IDBRequest>> IDBObjectStore::doDelete(ExecState& execState, IDBKeyRange* keyRange)
340 {
341     LOG(IndexedDB, "IDBObjectStore::deleteFunction");
342     ASSERT(currentThread() == m_transaction.database().originThreadID());
343
344     // The IDB spec for several IDBObjectStore methods states that transaction related exceptions should fire before
345     // the exception for an object store being deleted.
346     // However, a handful of W3C IDB tests expect the deleted exception even though the transaction inactive exception also applies.
347     // Additionally, Chrome and Edge agree with the test, as does Legacy IDB in WebKit.
348     // Until this is sorted out, we'll agree with the test and the majority share browsers.
349     if (m_deleted)
350         return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'delete' on 'IDBObjectStore': The object store has been deleted.") };
351
352     if (!m_transaction.isActive())
353         return Exception { IDBDatabaseException::TransactionInactiveError, ASCIILiteral("Failed to execute 'delete' on 'IDBObjectStore': The transaction is inactive or finished.") };
354
355     if (m_transaction.isReadOnly())
356         return Exception { IDBDatabaseException::ReadOnlyError, ASCIILiteral("Failed to execute 'delete' on 'IDBObjectStore': The transaction is read-only.") };
357
358     IDBKeyRangeData keyRangeData(keyRange);
359     if (!keyRangeData.isValid())
360         return Exception { IDBDatabaseException::DataError, ASCIILiteral("Failed to execute 'delete' on 'IDBObjectStore': The parameter is not a valid key range.") };
361
362     return m_transaction.requestDeleteRecord(execState, *this, keyRangeData);
363 }
364
365 ExceptionOr<Ref<IDBRequest>> IDBObjectStore::deleteFunction(ExecState& execState, JSValue key)
366 {
367     Ref<IDBKey> idbKey = scriptValueToIDBKey(execState, key);
368     if (!idbKey->isValid())
369         return Exception { IDBDatabaseException::DataError, ASCIILiteral("Failed to execute 'delete' on 'IDBObjectStore': The parameter is not a valid key.") };
370     return doDelete(execState, IDBKeyRange::create(WTFMove(idbKey)).ptr());
371 }
372
373 ExceptionOr<Ref<IDBRequest>> IDBObjectStore::clear(ExecState& execState)
374 {
375     LOG(IndexedDB, "IDBObjectStore::clear");
376     ASSERT(currentThread() == m_transaction.database().originThreadID());
377
378     // The IDB spec for several IDBObjectStore methods states that transaction related exceptions should fire before
379     // the exception for an object store being deleted.
380     // However, a handful of W3C IDB tests expect the deleted exception even though the transaction inactive exception also applies.
381     // Additionally, Chrome and Edge agree with the test, as does Legacy IDB in WebKit.
382     // Until this is sorted out, we'll agree with the test and the majority share browsers.
383     if (m_deleted)
384         return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'clear' on 'IDBObjectStore': The object store has been deleted.") };
385
386     if (!m_transaction.isActive())
387         return Exception { IDBDatabaseException::TransactionInactiveError, ASCIILiteral("Failed to execute 'clear' on 'IDBObjectStore': The transaction is inactive or finished.") };
388
389     if (m_transaction.isReadOnly())
390         return Exception { IDBDatabaseException::ReadOnlyError, ASCIILiteral("Failed to execute 'clear' on 'IDBObjectStore': The transaction is read-only.") };
391
392     return m_transaction.requestClearObjectStore(execState, *this);
393 }
394
395 ExceptionOr<Ref<IDBIndex>> IDBObjectStore::createIndex(ExecState&, const String& name, IDBKeyPath&& keyPath, const IndexParameters& parameters)
396 {
397     LOG(IndexedDB, "IDBObjectStore::createIndex %s", name.utf8().data());
398     ASSERT(currentThread() == m_transaction.database().originThreadID());
399
400     if (!m_transaction.isVersionChange())
401         return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'createIndex' on 'IDBObjectStore': The database is not running a version change transaction.") };
402
403     if (m_deleted)
404         return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'createIndex' on 'IDBObjectStore': The object store has been deleted.") };
405
406     if (!m_transaction.isActive())
407         return Exception { IDBDatabaseException::TransactionInactiveError, ASCIILiteral("Failed to execute 'createIndex' on 'IDBObjectStore': The transaction is inactive.")};
408
409     if (m_info.hasIndex(name))
410         return Exception { IDBDatabaseException::ConstraintError, ASCIILiteral("Failed to execute 'createIndex' on 'IDBObjectStore': An index with the specified name already exists.") };
411
412     if (!isIDBKeyPathValid(keyPath))
413         return Exception { IDBDatabaseException::SyntaxError, ASCIILiteral("Failed to execute 'createIndex' on 'IDBObjectStore': The keyPath argument contains an invalid key path.") };
414
415     if (name.isNull())
416         return Exception { TypeError };
417
418     if (parameters.multiEntry && WTF::holds_alternative<Vector<String>>(keyPath))
419         return Exception { IDBDatabaseException::InvalidAccessError, ASCIILiteral("Failed to execute 'createIndex' on 'IDBObjectStore': The keyPath argument was an array and the multiEntry option is true.") };
420
421     // Install the new Index into the ObjectStore's info.
422     IDBIndexInfo info = m_info.createNewIndex(name, WTFMove(keyPath), parameters.unique, parameters.multiEntry);
423     m_transaction.database().didCreateIndexInfo(info);
424
425     // Create the actual IDBObjectStore from the transaction, which also schedules the operation server side.
426     auto index = m_transaction.createIndex(*this, info);
427
428     Ref<IDBIndex> referencedIndex { *index };
429
430     Locker<Lock> locker(m_referencedIndexLock);
431     m_referencedIndexes.set(name, WTFMove(index));
432
433     return WTFMove(referencedIndex);
434 }
435
436 ExceptionOr<Ref<IDBIndex>> IDBObjectStore::index(const String& indexName)
437 {
438     LOG(IndexedDB, "IDBObjectStore::index");
439     ASSERT(currentThread() == m_transaction.database().originThreadID());
440
441     if (!scriptExecutionContext())
442         return Exception { IDBDatabaseException::InvalidStateError }; // FIXME: Is this code tested? Is iteven reachable?
443
444     if (m_deleted)
445         return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'index' on 'IDBObjectStore': The object store has been deleted.") };
446
447     if (m_transaction.isFinishedOrFinishing())
448         return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'index' on 'IDBObjectStore': The transaction is finished.") };
449
450     Locker<Lock> locker(m_referencedIndexLock);
451     auto iterator = m_referencedIndexes.find(indexName);
452     if (iterator != m_referencedIndexes.end())
453         return Ref<IDBIndex> { *iterator->value };
454
455     auto* info = m_info.infoForExistingIndex(indexName);
456     if (!info)
457         return Exception { IDBDatabaseException::NotFoundError, ASCIILiteral("Failed to execute 'index' on 'IDBObjectStore': The specified index was not found.") };
458
459     auto index = std::make_unique<IDBIndex>(*scriptExecutionContext(), *info, *this);
460
461     Ref<IDBIndex> referencedIndex { *index };
462
463     m_referencedIndexes.set(indexName, WTFMove(index));
464
465     return WTFMove(referencedIndex);
466 }
467
468 ExceptionOr<void> IDBObjectStore::deleteIndex(const String& name)
469 {
470     LOG(IndexedDB, "IDBObjectStore::deleteIndex %s", name.utf8().data());
471     ASSERT(currentThread() == m_transaction.database().originThreadID());
472
473     if (m_deleted)
474         return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'deleteIndex' on 'IDBObjectStore': The object store has been deleted.") };
475
476     if (!m_transaction.isVersionChange())
477         return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'deleteIndex' on 'IDBObjectStore': The database is not running a version change transaction.") };
478
479     if (!m_transaction.isActive())
480         return Exception { IDBDatabaseException::TransactionInactiveError,  ASCIILiteral("Failed to execute 'deleteIndex' on 'IDBObjectStore': The transaction is inactive or finished.") };
481
482     if (!m_info.hasIndex(name))
483         return Exception { IDBDatabaseException::NotFoundError, ASCIILiteral("Failed to execute 'deleteIndex' on 'IDBObjectStore': The specified index was not found.") };
484
485     auto* info = m_info.infoForExistingIndex(name);
486     ASSERT(info);
487     m_transaction.database().didDeleteIndexInfo(*info);
488
489     m_info.deleteIndex(name);
490
491     {
492         Locker<Lock> locker(m_referencedIndexLock);
493         if (auto index = m_referencedIndexes.take(name)) {
494             index->markAsDeleted();
495             m_deletedIndexes.add(index->info().identifier(), WTFMove(index));
496         }
497     }
498
499     m_transaction.deleteIndex(m_info.identifier(), name);
500
501     return { };
502 }
503
504 ExceptionOr<Ref<IDBRequest>> IDBObjectStore::count(ExecState& execState, JSValue key)
505 {
506     LOG(IndexedDB, "IDBObjectStore::count");
507
508     Ref<IDBKey> idbKey = scriptValueToIDBKey(execState, key);
509     if (!idbKey->isValid())
510         return Exception { IDBDatabaseException::DataError, ASCIILiteral("Failed to execute 'count' on 'IDBObjectStore': The parameter is not a valid key.") };
511
512     return doCount(execState, IDBKeyRangeData(idbKey.ptr()));
513 }
514
515 ExceptionOr<Ref<IDBRequest>> IDBObjectStore::count(ExecState& execState, IDBKeyRange* range)
516 {
517     LOG(IndexedDB, "IDBObjectStore::count");
518
519     return doCount(execState, range ? IDBKeyRangeData(range) : IDBKeyRangeData::allKeys());
520 }
521
522 ExceptionOr<Ref<IDBRequest>> IDBObjectStore::doCount(ExecState& execState, const IDBKeyRangeData& range)
523 {
524     ASSERT(currentThread() == m_transaction.database().originThreadID());
525
526     // The IDB spec for several IDBObjectStore methods states that transaction related exceptions should fire before
527     // the exception for an object store being deleted.
528     // However, a handful of W3C IDB tests expect the deleted exception even though the transaction inactive exception also applies.
529     // Additionally, Chrome and Edge agree with the test, as does Legacy IDB in WebKit.
530     // Until this is sorted out, we'll agree with the test and the majority share browsers.
531     if (m_deleted)
532         return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'count' on 'IDBObjectStore': The object store has been deleted.") };
533
534     if (!m_transaction.isActive())
535         return Exception { IDBDatabaseException::TransactionInactiveError, ASCIILiteral("Failed to execute 'count' on 'IDBObjectStore': The transaction is inactive or finished.") };
536
537     if (!range.isValid())
538         return Exception { IDBDatabaseException::DataError };
539
540     return m_transaction.requestCount(execState, *this, range);
541 }
542
543 ExceptionOr<Ref<IDBRequest>> IDBObjectStore::getAll(ExecState& execState, RefPtr<IDBKeyRange> range, Optional<uint32_t> count)
544 {
545     LOG(IndexedDB, "IDBObjectStore::getAll");
546     ASSERT(currentThread() == m_transaction.database().originThreadID());
547
548     if (m_deleted)
549         return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'getAll' on 'IDBObjectStore': The object store has been deleted.") };
550
551     if (!m_transaction.isActive())
552         return Exception { IDBDatabaseException::TransactionInactiveError, ASCIILiteral("Failed to execute 'getAll' on 'IDBObjectStore': The transaction is inactive or finished.") };
553
554     return m_transaction.requestGetAllObjectStoreRecords(execState, *this, range.get(), IndexedDB::GetAllType::Values, count);
555 }
556
557 ExceptionOr<Ref<IDBRequest>> IDBObjectStore::getAll(ExecState& execState, JSValue key, Optional<uint32_t> count)
558 {
559     auto onlyResult = IDBKeyRange::only(execState, key);
560     if (onlyResult.hasException())
561         return Exception { IDBDatabaseException::DataError, ASCIILiteral("Failed to execute 'getAll' on 'IDBObjectStore': The parameter is not a valid key.") };
562
563     return getAll(execState, onlyResult.releaseReturnValue(), count);
564 }
565
566 ExceptionOr<Ref<IDBRequest>> IDBObjectStore::getAllKeys(ExecState& execState, RefPtr<IDBKeyRange> range, Optional<uint32_t> count)
567 {
568     LOG(IndexedDB, "IDBObjectStore::getAllKeys");
569     ASSERT(currentThread() == m_transaction.database().originThreadID());
570
571     if (m_deleted)
572         return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'getAllKeys' on 'IDBObjectStore': The object store has been deleted.") };
573
574     if (!m_transaction.isActive())
575         return Exception { IDBDatabaseException::TransactionInactiveError, ASCIILiteral("Failed to execute 'getAllKeys' on 'IDBObjectStore': The transaction is inactive or finished.") };
576
577     return m_transaction.requestGetAllObjectStoreRecords(execState, *this, range.get(), IndexedDB::GetAllType::Keys, count);
578 }
579
580 ExceptionOr<Ref<IDBRequest>> IDBObjectStore::getAllKeys(ExecState& execState, JSValue key, Optional<uint32_t> count)
581 {
582     auto onlyResult = IDBKeyRange::only(execState, key);
583     if (onlyResult.hasException())
584         return Exception { IDBDatabaseException::DataError, ASCIILiteral("Failed to execute 'getAllKeys' on 'IDBObjectStore': The parameter is not a valid key.") };
585
586     return getAllKeys(execState, onlyResult.releaseReturnValue(), count);
587 }
588
589 void IDBObjectStore::markAsDeleted()
590 {
591     ASSERT(currentThread() == m_transaction.database().originThreadID());
592     m_deleted = true;
593 }
594
595 void IDBObjectStore::rollbackForVersionChangeAbort()
596 {
597     ASSERT(currentThread() == m_transaction.database().originThreadID());
598
599     String currentName = m_info.name();
600     m_info = m_originalInfo;
601
602     auto& databaseInfo = transaction().database().info();
603     auto* objectStoreInfo = databaseInfo.infoForExistingObjectStore(m_info.identifier());
604     if (!objectStoreInfo) {
605         m_info.rename(currentName);
606         m_deleted = true;
607     } else {
608         m_deleted = false;
609         
610         HashSet<uint64_t> indexesToRemove;
611         for (auto indexIdentifier : objectStoreInfo->indexMap().keys()) {
612             if (!objectStoreInfo->hasIndex(indexIdentifier))
613                 indexesToRemove.add(indexIdentifier);
614         }
615
616         for (auto indexIdentifier : indexesToRemove)
617             m_info.deleteIndex(indexIdentifier);
618     }
619
620     Locker<Lock> locker(m_referencedIndexLock);
621     for (auto& iterator : m_deletedIndexes) {
622         if (m_info.hasIndex(iterator.key)) {
623             auto name = iterator.value->info().name();
624             m_referencedIndexes.set(name, WTFMove(iterator.value));
625         }
626     }
627
628     for (auto& index : m_referencedIndexes.values())
629         index->rollbackInfoForVersionChangeAbort();
630 }
631
632 void IDBObjectStore::visitReferencedIndexes(SlotVisitor& visitor) const
633 {
634     Locker<Lock> locker(m_referencedIndexLock);
635     for (auto& index : m_referencedIndexes.values())
636         visitor.addOpaqueRoot(index.get());
637     for (auto& index : m_deletedIndexes.values())
638         visitor.addOpaqueRoot(index.get());
639 }
640
641 void IDBObjectStore::renameReferencedIndex(IDBIndex& index, const String& newName)
642 {
643     LOG(IndexedDB, "IDBObjectStore::renameReferencedIndex");
644
645     auto* indexInfo = m_info.infoForExistingIndex(index.info().identifier());
646     ASSERT(indexInfo);
647     indexInfo->rename(newName);
648
649     ASSERT(m_referencedIndexes.contains(index.info().name()));
650     ASSERT(!m_referencedIndexes.contains(newName));
651     ASSERT(m_referencedIndexes.get(index.info().name()) == &index);
652
653     m_referencedIndexes.set(newName, m_referencedIndexes.take(index.info().name()));
654 }
655
656 void IDBObjectStore::ref()
657 {
658     m_transaction.ref();
659 }
660
661 void IDBObjectStore::deref()
662 {
663     m_transaction.deref();
664 }
665
666 } // namespace WebCore
667
668 #endif // ENABLE(INDEXED_DATABASE)