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