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