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