Bug 1885602 - Part 5: Implement navigating to the SUMO help topic from the menu heade...
[gecko.git] / dom / indexedDB / IDBObjectStore.cpp
blob728f10b1055b24615c73104c77c7546ad8419af1
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "IDBObjectStore.h"
9 #include <numeric>
10 #include <utility>
12 #include "IDBCursorType.h"
13 #include "IDBDatabase.h"
14 #include "IDBEvents.h"
15 #include "IDBFactory.h"
16 #include "IDBIndex.h"
17 #include "IDBKeyRange.h"
18 #include "IDBRequest.h"
19 #include "IDBTransaction.h"
20 #include "IndexedDatabase.h"
21 #include "IndexedDatabaseInlines.h"
22 #include "IndexedDatabaseManager.h"
23 #include "IndexedDBCommon.h"
24 #include "KeyPath.h"
25 #include "ProfilerHelpers.h"
26 #include "ReportInternalError.h"
27 #include "js/Array.h" // JS::GetArrayLength, JS::IsArrayObject
28 #include "js/Class.h"
29 #include "js/Date.h"
30 #include "js/Object.h" // JS::GetClass
31 #include "js/PropertyAndElement.h" // JS_GetProperty, JS_GetPropertyById, JS_HasOwnProperty, JS_HasOwnPropertyById
32 #include "js/StructuredClone.h"
33 #include "mozilla/EndianUtils.h"
34 #include "mozilla/ErrorResult.h"
35 #include "mozilla/ResultExtensions.h"
36 #include "mozilla/dom/BindingUtils.h"
37 #include "mozilla/dom/BlobBinding.h"
38 #include "mozilla/dom/Document.h"
39 #include "mozilla/dom/File.h"
40 #include "mozilla/dom/IDBObjectStoreBinding.h"
41 #include "mozilla/dom/MemoryBlobImpl.h"
42 #include "mozilla/dom/StreamBlobImpl.h"
43 #include "mozilla/dom/StructuredCloneHolder.h"
44 #include "mozilla/dom/StructuredCloneTags.h"
45 #include "mozilla/dom/indexedDB/PBackgroundIDBSharedTypes.h"
46 #include "mozilla/ipc/BackgroundChild.h"
47 #include "mozilla/ipc/PBackgroundSharedTypes.h"
48 #include "nsCOMPtr.h"
49 #include "nsStreamUtils.h"
50 #include "nsStringStream.h"
52 // Include this last to avoid path problems on Windows.
53 #include "ActorsChild.h"
55 namespace mozilla::dom {
57 using namespace mozilla::dom::indexedDB;
58 using namespace mozilla::dom::quota;
59 using namespace mozilla::ipc;
61 namespace {
63 Result<IndexUpdateInfo, nsresult> MakeIndexUpdateInfo(
64 const int64_t aIndexID, const Key& aKey, const nsCString& aLocale) {
65 IndexUpdateInfo indexUpdateInfo;
66 indexUpdateInfo.indexId() = aIndexID;
67 indexUpdateInfo.value() = aKey;
68 if (!aLocale.IsEmpty()) {
69 QM_TRY_UNWRAP(indexUpdateInfo.localizedValue(),
70 aKey.ToLocaleAwareKey(aLocale));
72 return indexUpdateInfo;
75 } // namespace
77 struct IDBObjectStore::StructuredCloneWriteInfo {
78 JSAutoStructuredCloneBuffer mCloneBuffer;
79 nsTArray<StructuredCloneFileChild> mFiles;
80 IDBDatabase* mDatabase;
81 uint64_t mOffsetToKeyProp;
83 explicit StructuredCloneWriteInfo(IDBDatabase* aDatabase)
84 : mCloneBuffer(JS::StructuredCloneScope::DifferentProcessForIndexedDB,
85 nullptr, nullptr),
86 mDatabase(aDatabase),
87 mOffsetToKeyProp(0) {
88 MOZ_ASSERT(aDatabase);
90 MOZ_COUNT_CTOR(StructuredCloneWriteInfo);
93 StructuredCloneWriteInfo(StructuredCloneWriteInfo&& aCloneWriteInfo) noexcept
94 : mCloneBuffer(std::move(aCloneWriteInfo.mCloneBuffer)),
95 mFiles(std::move(aCloneWriteInfo.mFiles)),
96 mDatabase(aCloneWriteInfo.mDatabase),
97 mOffsetToKeyProp(aCloneWriteInfo.mOffsetToKeyProp) {
98 MOZ_ASSERT(mDatabase);
100 MOZ_COUNT_CTOR(StructuredCloneWriteInfo);
102 aCloneWriteInfo.mOffsetToKeyProp = 0;
105 MOZ_COUNTED_DTOR(StructuredCloneWriteInfo)
108 // Used by ValueWrapper::Clone to hold strong references to any blob-like
109 // objects through the clone process. This is necessary because:
110 // - The structured clone process may trigger content code via getters/other
111 // which can potentially cause existing strong references to be dropped,
112 // necessitating the clone to hold its own strong references.
113 // - The structured clone can abort partway through, so it's necessary to track
114 // what strong references have been acquired so that they can be freed even
115 // if a de-serialization does not occur.
116 struct IDBObjectStore::StructuredCloneInfo {
117 nsTArray<StructuredCloneFileChild> mFiles;
120 namespace {
122 struct MOZ_STACK_CLASS GetAddInfoClosure final {
123 IDBObjectStore::StructuredCloneWriteInfo& mCloneWriteInfo;
124 JS::Handle<JS::Value> mValue;
126 GetAddInfoClosure(IDBObjectStore::StructuredCloneWriteInfo& aCloneWriteInfo,
127 JS::Handle<JS::Value> aValue)
128 : mCloneWriteInfo(aCloneWriteInfo), mValue(aValue) {
129 MOZ_COUNT_CTOR(GetAddInfoClosure);
132 MOZ_COUNTED_DTOR(GetAddInfoClosure)
135 MovingNotNull<RefPtr<IDBRequest>> GenerateRequest(
136 JSContext* aCx, IDBObjectStore* aObjectStore) {
137 MOZ_ASSERT(aObjectStore);
138 aObjectStore->AssertIsOnOwningThread();
140 auto transaction = aObjectStore->AcquireTransaction();
141 auto* const database = transaction->Database();
143 return IDBRequest::Create(aCx, aObjectStore, database,
144 std::move(transaction));
147 bool StructuredCloneWriteCallback(JSContext* aCx,
148 JSStructuredCloneWriter* aWriter,
149 JS::Handle<JSObject*> aObj,
150 bool* aSameProcessRequired, void* aClosure) {
151 MOZ_ASSERT(aCx);
152 MOZ_ASSERT(aWriter);
153 MOZ_ASSERT(aClosure);
155 auto* const cloneWriteInfo =
156 static_cast<IDBObjectStore::StructuredCloneWriteInfo*>(aClosure);
158 if (JS::GetClass(aObj) == IDBObjectStore::DummyPropClass()) {
159 MOZ_ASSERT(!cloneWriteInfo->mOffsetToKeyProp);
160 cloneWriteInfo->mOffsetToKeyProp = js::GetSCOffset(aWriter);
162 uint64_t value = 0;
163 // Omit endian swap
164 return JS_WriteBytes(aWriter, &value, sizeof(value));
167 // UNWRAP_OBJECT calls might mutate this.
168 JS::Rooted<JSObject*> obj(aCx, aObj);
171 Blob* blob = nullptr;
172 if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, &obj, blob))) {
173 ErrorResult rv;
174 const uint64_t nativeEndianSize = blob->GetSize(rv);
175 MOZ_ASSERT(!rv.Failed());
177 const uint64_t size = NativeEndian::swapToLittleEndian(nativeEndianSize);
179 nsString type;
180 blob->GetType(type);
182 const NS_ConvertUTF16toUTF8 convType(type);
183 const uint32_t convTypeLength =
184 NativeEndian::swapToLittleEndian(convType.Length());
186 if (cloneWriteInfo->mFiles.Length() > size_t(UINT32_MAX)) {
187 MOZ_ASSERT(false,
188 "Fix the structured clone data to use a bigger type!");
189 return false;
192 const uint32_t index = cloneWriteInfo->mFiles.Length();
194 if (!JS_WriteUint32Pair(aWriter,
195 blob->IsFile() ? SCTAG_DOM_FILE : SCTAG_DOM_BLOB,
196 index) ||
197 !JS_WriteBytes(aWriter, &size, sizeof(size)) ||
198 !JS_WriteBytes(aWriter, &convTypeLength, sizeof(convTypeLength)) ||
199 !JS_WriteBytes(aWriter, convType.get(), convType.Length())) {
200 return false;
203 const RefPtr<File> file = blob->ToFile();
204 if (file) {
205 ErrorResult rv;
206 const int64_t nativeEndianLastModifiedDate = file->GetLastModified(rv);
207 MOZ_ALWAYS_TRUE(!rv.Failed());
209 const int64_t lastModifiedDate =
210 NativeEndian::swapToLittleEndian(nativeEndianLastModifiedDate);
212 nsString name;
213 file->GetName(name);
215 const NS_ConvertUTF16toUTF8 convName(name);
216 const uint32_t convNameLength =
217 NativeEndian::swapToLittleEndian(convName.Length());
219 if (!JS_WriteBytes(aWriter, &lastModifiedDate,
220 sizeof(lastModifiedDate)) ||
221 !JS_WriteBytes(aWriter, &convNameLength, sizeof(convNameLength)) ||
222 !JS_WriteBytes(aWriter, convName.get(), convName.Length())) {
223 return false;
227 cloneWriteInfo->mFiles.EmplaceBack(StructuredCloneFileBase::eBlob, blob);
229 return true;
233 return StructuredCloneHolder::WriteFullySerializableObjects(aCx, aWriter,
234 aObj);
237 bool CopyingStructuredCloneWriteCallback(JSContext* aCx,
238 JSStructuredCloneWriter* aWriter,
239 JS::Handle<JSObject*> aObj,
240 bool* aSameProcessRequired,
241 void* aClosure) {
242 MOZ_ASSERT(aCx);
243 MOZ_ASSERT(aWriter);
244 MOZ_ASSERT(aClosure);
246 auto* const cloneInfo =
247 static_cast<IDBObjectStore::StructuredCloneInfo*>(aClosure);
249 // UNWRAP_OBJECT calls might mutate this.
250 JS::Rooted<JSObject*> obj(aCx, aObj);
253 Blob* blob = nullptr;
254 if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, &obj, blob))) {
255 if (cloneInfo->mFiles.Length() > size_t(UINT32_MAX)) {
256 MOZ_ASSERT(false,
257 "Fix the structured clone data to use a bigger type!");
258 return false;
261 const uint32_t index = cloneInfo->mFiles.Length();
263 if (!JS_WriteUint32Pair(aWriter,
264 blob->IsFile() ? SCTAG_DOM_FILE : SCTAG_DOM_BLOB,
265 index)) {
266 return false;
269 cloneInfo->mFiles.EmplaceBack(StructuredCloneFileBase::eBlob, blob);
271 return true;
275 return StructuredCloneHolder::WriteFullySerializableObjects(aCx, aWriter,
276 aObj);
279 nsresult GetAddInfoCallback(JSContext* aCx, void* aClosure) {
280 static const JSStructuredCloneCallbacks kStructuredCloneCallbacks = {
281 nullptr /* read */, StructuredCloneWriteCallback /* write */,
282 nullptr /* reportError */, nullptr /* readTransfer */,
283 nullptr /* writeTransfer */, nullptr /* freeTransfer */,
284 nullptr /* canTransfer */, nullptr /* sabCloned */
287 MOZ_ASSERT(aCx);
289 auto* const data = static_cast<GetAddInfoClosure*>(aClosure);
290 MOZ_ASSERT(data);
292 data->mCloneWriteInfo.mOffsetToKeyProp = 0;
294 if (!data->mCloneWriteInfo.mCloneBuffer.write(aCx, data->mValue,
295 &kStructuredCloneCallbacks,
296 &data->mCloneWriteInfo)) {
297 return NS_ERROR_DOM_DATA_CLONE_ERR;
300 return NS_OK;
303 using indexedDB::WrapAsJSObject;
305 template <typename T>
306 JSObject* WrapAsJSObject(JSContext* const aCx, T& aBaseObject) {
307 JS::Rooted<JSObject*> result(aCx);
308 const bool res = WrapAsJSObject(aCx, aBaseObject, &result);
309 return res ? static_cast<JSObject*>(result) : nullptr;
312 JSObject* CopyingStructuredCloneReadCallback(
313 JSContext* aCx, JSStructuredCloneReader* aReader,
314 const JS::CloneDataPolicy& aCloneDataPolicy, uint32_t aTag, uint32_t aData,
315 void* aClosure) {
316 MOZ_ASSERT(aTag != SCTAG_DOM_FILE_WITHOUT_LASTMODIFIEDDATE);
318 if (aTag == SCTAG_DOM_BLOB || aTag == SCTAG_DOM_FILE ||
319 aTag == SCTAG_DOM_MUTABLEFILE) {
320 auto* const cloneInfo =
321 static_cast<IDBObjectStore::StructuredCloneInfo*>(aClosure);
323 if (aData >= cloneInfo->mFiles.Length()) {
324 MOZ_ASSERT(false, "Bad index value!");
325 return nullptr;
328 StructuredCloneFileChild& file = cloneInfo->mFiles[aData];
330 switch (static_cast<StructuredCloneTags>(aTag)) {
331 case SCTAG_DOM_BLOB:
332 MOZ_ASSERT(file.Type() == StructuredCloneFileBase::eBlob);
333 MOZ_ASSERT(!file.Blob().IsFile());
335 return WrapAsJSObject(aCx, file.MutableBlob());
337 case SCTAG_DOM_FILE: {
338 MOZ_ASSERT(file.Type() == StructuredCloneFileBase::eBlob);
340 JS::Rooted<JSObject*> result(aCx);
343 // Create a scope so ~RefPtr fires before returning an unwrapped
344 // JS::Value.
345 const RefPtr<Blob> blob = file.BlobPtr();
346 MOZ_ASSERT(blob->IsFile());
348 const RefPtr<File> file = blob->ToFile();
349 MOZ_ASSERT(file);
351 if (!WrapAsJSObject(aCx, file, &result)) {
352 return nullptr;
356 return result;
359 case SCTAG_DOM_MUTABLEFILE:
360 MOZ_ASSERT(file.Type() == StructuredCloneFileBase::eMutableFile);
362 return nullptr;
364 default:
365 // This cannot be reached due to the if condition before.
366 break;
370 return StructuredCloneHolder::ReadFullySerializableObjects(aCx, aReader, aTag,
371 true);
374 } // namespace
376 const JSClass IDBObjectStore::sDummyPropJSClass = {
377 "IDBObjectStore Dummy", 0 /* flags */
380 IDBObjectStore::IDBObjectStore(SafeRefPtr<IDBTransaction> aTransaction,
381 ObjectStoreSpec* aSpec)
382 : mTransaction(std::move(aTransaction)),
383 mCachedKeyPath(JS::UndefinedValue()),
384 mSpec(aSpec),
385 mId(aSpec->metadata().id()),
386 mRooted(false) {
387 MOZ_ASSERT(mTransaction);
388 mTransaction->AssertIsOnOwningThread();
389 MOZ_ASSERT(aSpec);
392 IDBObjectStore::~IDBObjectStore() {
393 AssertIsOnOwningThread();
395 if (mRooted) {
396 mozilla::DropJSObjects(this);
400 // static
401 RefPtr<IDBObjectStore> IDBObjectStore::Create(
402 SafeRefPtr<IDBTransaction> aTransaction, ObjectStoreSpec& aSpec) {
403 MOZ_ASSERT(aTransaction);
404 aTransaction->AssertIsOnOwningThread();
406 return new IDBObjectStore(std::move(aTransaction), &aSpec);
409 // static
410 void IDBObjectStore::AppendIndexUpdateInfo(
411 const int64_t aIndexID, const KeyPath& aKeyPath, const bool aMultiEntry,
412 const nsCString& aLocale, JSContext* const aCx, JS::Handle<JS::Value> aVal,
413 nsTArray<IndexUpdateInfo>* const aUpdateInfoArray,
414 const VoidOrObjectStoreKeyPathString& aAutoIncrementedObjectStoreKeyPath,
415 ErrorResult* const aRv) {
416 // This precondition holds when `aVal` is the result of a structured clone.
417 js::AutoAssertNoContentJS noContentJS(aCx);
419 static_assert(std::is_same_v<IDBObjectStore::VoidOrObjectStoreKeyPathString,
420 KeyPath::VoidOrObjectStoreKeyPathString>,
421 "Inconsistent types");
423 if (!aMultiEntry) {
424 Key key;
425 *aRv =
426 aKeyPath.ExtractKey(aCx, aVal, key, aAutoIncrementedObjectStoreKeyPath);
428 // If an index's keyPath doesn't match an object, we ignore that object.
429 if (aRv->ErrorCodeIs(NS_ERROR_DOM_INDEXEDDB_DATA_ERR) || key.IsUnset()) {
430 aRv->SuppressException();
431 return;
434 if (aRv->Failed()) {
435 return;
438 QM_TRY_UNWRAP(auto item, MakeIndexUpdateInfo(aIndexID, key, aLocale),
439 QM_VOID,
440 [aRv](const nsresult tryResult) { aRv->Throw(tryResult); });
442 aUpdateInfoArray->AppendElement(std::move(item));
443 return;
446 JS::Rooted<JS::Value> val(aCx);
447 if (NS_FAILED(aKeyPath.ExtractKeyAsJSVal(aCx, aVal, val.address()))) {
448 return;
451 bool isArray;
452 if (NS_WARN_IF(!JS::IsArrayObject(aCx, val, &isArray))) {
453 IDB_REPORT_INTERNAL_ERR();
454 aRv->Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
455 return;
457 if (isArray) {
458 JS::Rooted<JSObject*> array(aCx, &val.toObject());
459 uint32_t arrayLength;
460 if (NS_WARN_IF(!JS::GetArrayLength(aCx, array, &arrayLength))) {
461 IDB_REPORT_INTERNAL_ERR();
462 aRv->Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
463 return;
466 for (uint32_t arrayIndex = 0; arrayIndex < arrayLength; arrayIndex++) {
467 JS::Rooted<JS::PropertyKey> indexId(aCx);
468 if (NS_WARN_IF(!JS_IndexToId(aCx, arrayIndex, &indexId))) {
469 IDB_REPORT_INTERNAL_ERR();
470 aRv->Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
471 return;
474 bool hasOwnProperty;
475 if (NS_WARN_IF(
476 !JS_HasOwnPropertyById(aCx, array, indexId, &hasOwnProperty))) {
477 IDB_REPORT_INTERNAL_ERR();
478 aRv->Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
479 return;
482 if (!hasOwnProperty) {
483 continue;
486 JS::Rooted<JS::Value> arrayItem(aCx);
487 if (NS_WARN_IF(!JS_GetPropertyById(aCx, array, indexId, &arrayItem))) {
488 IDB_REPORT_INTERNAL_ERR();
489 aRv->Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
490 return;
493 Key value;
494 auto result = value.SetFromJSVal(aCx, arrayItem);
495 if (result.isErr() || value.IsUnset()) {
496 // Not a value we can do anything with, ignore it.
497 if (result.isErr() &&
498 result.inspectErr().Is(SpecialValues::Exception)) {
499 result.unwrapErr().AsException().SuppressException();
501 continue;
504 QM_TRY_UNWRAP(auto item, MakeIndexUpdateInfo(aIndexID, value, aLocale),
505 QM_VOID,
506 [aRv](const nsresult tryResult) { aRv->Throw(tryResult); });
508 aUpdateInfoArray->AppendElement(std::move(item));
510 } else {
511 Key value;
512 auto result = value.SetFromJSVal(aCx, val);
513 if (result.isErr() || value.IsUnset()) {
514 // Not a value we can do anything with, ignore it.
515 if (result.isErr() && result.inspectErr().Is(SpecialValues::Exception)) {
516 result.unwrapErr().AsException().SuppressException();
518 return;
521 QM_TRY_UNWRAP(auto item, MakeIndexUpdateInfo(aIndexID, value, aLocale),
522 QM_VOID,
523 [aRv](const nsresult tryResult) { aRv->Throw(tryResult); });
525 aUpdateInfoArray->AppendElement(std::move(item));
529 // static
530 void IDBObjectStore::ClearCloneReadInfo(
531 StructuredCloneReadInfoChild& aReadInfo) {
532 // This is kind of tricky, we only want to release stuff on the main thread,
533 // but we can end up being called on other threads if we have already been
534 // cleared on the main thread.
535 if (!aReadInfo.HasFiles()) {
536 return;
539 aReadInfo.ReleaseFiles();
542 // static
543 bool IDBObjectStore::DeserializeValue(
544 JSContext* aCx, StructuredCloneReadInfoChild&& aCloneReadInfo,
545 JS::MutableHandle<JS::Value> aValue) {
546 MOZ_ASSERT(aCx);
548 if (!aCloneReadInfo.Data().Size()) {
549 aValue.setUndefined();
550 return true;
553 MOZ_ASSERT(!(aCloneReadInfo.Data().Size() % sizeof(uint64_t)));
555 static const JSStructuredCloneCallbacks callbacks = {
556 StructuredCloneReadCallback<StructuredCloneReadInfoChild>,
557 nullptr,
558 nullptr,
559 nullptr,
560 nullptr,
561 nullptr,
562 nullptr,
563 nullptr};
565 // FIXME: Consider to use StructuredCloneHolder here and in other
566 // deserializing methods.
567 return JS_ReadStructuredClone(
568 aCx, aCloneReadInfo.Data(), JS_STRUCTURED_CLONE_VERSION,
569 JS::StructuredCloneScope::DifferentProcessForIndexedDB, aValue,
570 JS::CloneDataPolicy(), &callbacks, &aCloneReadInfo);
573 #ifdef DEBUG
575 void IDBObjectStore::AssertIsOnOwningThread() const {
576 MOZ_ASSERT(mTransaction);
577 mTransaction->AssertIsOnOwningThread();
580 #endif // DEBUG
582 void IDBObjectStore::GetAddInfo(JSContext* aCx, ValueWrapper& aValueWrapper,
583 JS::Handle<JS::Value> aKeyVal,
584 StructuredCloneWriteInfo& aCloneWriteInfo,
585 Key& aKey,
586 nsTArray<IndexUpdateInfo>& aUpdateInfoArray,
587 ErrorResult& aRv) {
588 // Return DATA_ERR if a key was passed in and this objectStore uses inline
589 // keys.
590 if (!aKeyVal.isUndefined() && HasValidKeyPath()) {
591 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_DATA_ERR);
592 return;
595 const bool isAutoIncrement = AutoIncrement();
597 if (!HasValidKeyPath()) {
598 // Out-of-line keys must be passed in.
599 auto result = aKey.SetFromJSVal(aCx, aKeyVal);
600 if (result.isErr()) {
601 aRv = result.unwrapErr().ExtractErrorResult(
602 InvalidMapsTo<NS_ERROR_DOM_INDEXEDDB_DATA_ERR>);
603 return;
605 } else if (!isAutoIncrement) {
606 if (!aValueWrapper.Clone(aCx)) {
607 aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
608 return;
611 aRv = GetKeyPath().ExtractKey(aCx, aValueWrapper.Value(), aKey);
612 if (aRv.Failed()) {
613 return;
617 // Return DATA_ERR if no key was specified this isn't an autoIncrement
618 // objectStore.
619 if (aKey.IsUnset() && !isAutoIncrement) {
620 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_DATA_ERR);
621 return;
624 // Figure out indexes and the index values to update here.
626 if (mSpec->indexes().Length() && !aValueWrapper.Clone(aCx)) {
627 aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
628 return;
632 const nsTArray<IndexMetadata>& indexes = mSpec->indexes();
633 const uint32_t idxCount = indexes.Length();
635 const auto& autoIncrementedObjectStoreKeyPath =
636 [this]() -> const nsAString& {
637 if (AutoIncrement() && GetKeyPath().IsValid()) {
638 // By https://w3c.github.io/IndexedDB/#database-interface ,
639 // createObjectStore algorithm, step 8, neither arrays nor empty paths
640 // are allowed for autoincremented object stores.
641 // See also KeyPath::IsAllowedForObjectStore.
642 MOZ_ASSERT(GetKeyPath().IsString());
643 MOZ_ASSERT(!GetKeyPath().IsEmpty());
644 return GetKeyPath().mStrings[0];
647 return VoidString();
648 }();
650 aUpdateInfoArray.SetCapacity(idxCount); // Pretty good estimate
652 for (uint32_t idxIndex = 0; idxIndex < idxCount; idxIndex++) {
653 const IndexMetadata& metadata = indexes[idxIndex];
655 AppendIndexUpdateInfo(metadata.id(), metadata.keyPath(),
656 metadata.multiEntry(), metadata.locale(), aCx,
657 aValueWrapper.Value(), &aUpdateInfoArray,
658 autoIncrementedObjectStoreKeyPath, &aRv);
659 if (NS_WARN_IF(aRv.Failed())) {
660 return;
665 if (isAutoIncrement && HasValidKeyPath()) {
666 if (!aValueWrapper.Clone(aCx)) {
667 aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
668 return;
671 GetAddInfoClosure data(aCloneWriteInfo, aValueWrapper.Value());
673 MOZ_ASSERT(aKey.IsUnset());
675 aRv = GetKeyPath().ExtractOrCreateKey(aCx, aValueWrapper.Value(), aKey,
676 &GetAddInfoCallback, &data);
677 } else {
678 GetAddInfoClosure data(aCloneWriteInfo, aValueWrapper.Value());
680 aRv = GetAddInfoCallback(aCx, &data);
684 RefPtr<IDBRequest> IDBObjectStore::AddOrPut(JSContext* aCx,
685 ValueWrapper& aValueWrapper,
686 JS::Handle<JS::Value> aKey,
687 bool aOverwrite, bool aFromCursor,
688 ErrorResult& aRv) {
689 AssertIsOnOwningThread();
690 MOZ_ASSERT(aCx);
691 MOZ_ASSERT_IF(aFromCursor, aOverwrite);
693 if (mTransaction->GetMode() == IDBTransaction::Mode::Cleanup ||
694 mDeletedSpec) {
695 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
696 return nullptr;
699 if (!mTransaction->IsActive()) {
700 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
701 return nullptr;
704 if (!mTransaction->IsWriteAllowed()) {
705 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_READ_ONLY_ERR);
706 return nullptr;
709 Key key;
710 StructuredCloneWriteInfo cloneWriteInfo(mTransaction->Database());
711 nsTArray<IndexUpdateInfo> updateInfos;
713 // According to spec https://w3c.github.io/IndexedDB/#clone-value,
714 // the transaction must be in inactive state during clone
715 mTransaction->TransitionToInactive();
717 #ifdef DEBUG
718 const uint32_t previousPendingRequestCount{
719 mTransaction->GetPendingRequestCount()};
720 #endif
721 GetAddInfo(aCx, aValueWrapper, aKey, cloneWriteInfo, key, updateInfos, aRv);
722 // Check that new requests were rejected in the Inactive state
723 // and possibly in the Finished state, if the transaction has been aborted,
724 // during the structured cloning.
725 MOZ_ASSERT(mTransaction->GetPendingRequestCount() ==
726 previousPendingRequestCount);
728 if (!mTransaction->IsAborted()) {
729 mTransaction->TransitionToActive();
730 } else if (!aRv.Failed()) {
731 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR);
732 return nullptr; // It is mandatory to return right after throw
735 if (aRv.Failed()) {
736 return nullptr;
739 // Check the size limit of the serialized message which mainly consists of
740 // a StructuredCloneBuffer, an encoded object key, and the encoded index keys.
741 // kMaxIDBMsgOverhead covers the minor stuff not included in this calculation
742 // because the precise calculation would slow down this AddOrPut operation.
743 static const size_t kMaxIDBMsgOverhead = 1024 * 1024; // 1MB
744 const uint32_t maximalSizeFromPref =
745 IndexedDatabaseManager::MaxSerializedMsgSize();
746 MOZ_ASSERT(maximalSizeFromPref > kMaxIDBMsgOverhead);
747 const size_t kMaxMessageSize = maximalSizeFromPref - kMaxIDBMsgOverhead;
749 const size_t indexUpdateInfoSize =
750 std::accumulate(updateInfos.cbegin(), updateInfos.cend(), 0u,
751 [](size_t old, const IndexUpdateInfo& updateInfo) {
752 return old + updateInfo.value().GetBuffer().Length() +
753 updateInfo.localizedValue().GetBuffer().Length();
756 const size_t messageSize = cloneWriteInfo.mCloneBuffer.data().Size() +
757 key.GetBuffer().Length() + indexUpdateInfoSize;
759 if (messageSize > kMaxMessageSize) {
760 IDB_REPORT_INTERNAL_ERR();
761 aRv.ThrowUnknownError(
762 nsPrintfCString("The serialized value is too large"
763 " (size=%zu bytes, max=%zu bytes).",
764 messageSize, kMaxMessageSize));
765 return nullptr;
768 ObjectStoreAddPutParams commonParams;
769 commonParams.objectStoreId() = Id();
770 commonParams.cloneInfo().data().data =
771 std::move(cloneWriteInfo.mCloneBuffer.data());
772 commonParams.cloneInfo().offsetToKeyProp() = cloneWriteInfo.mOffsetToKeyProp;
773 commonParams.key() = key;
774 commonParams.indexUpdateInfos() = std::move(updateInfos);
776 // Convert any blobs or mutable files into FileAddInfo.
777 QM_TRY_UNWRAP(
778 commonParams.fileAddInfos(),
779 TransformIntoNewArrayAbortOnErr(
780 cloneWriteInfo.mFiles,
781 [&database = *mTransaction->Database()](
782 auto& file) -> Result<FileAddInfo, nsresult> {
783 switch (file.Type()) {
784 case StructuredCloneFileBase::eBlob: {
785 MOZ_ASSERT(file.HasBlob());
787 PBackgroundIDBDatabaseFileChild* const fileActor =
788 database.GetOrCreateFileActorForBlob(file.MutableBlob());
789 if (NS_WARN_IF(!fileActor)) {
790 IDB_REPORT_INTERNAL_ERR();
791 return Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
794 return FileAddInfo{WrapNotNull(fileActor),
795 StructuredCloneFileBase::eBlob};
798 case StructuredCloneFileBase::eWasmBytecode:
799 case StructuredCloneFileBase::eWasmCompiled: {
800 MOZ_ASSERT(file.HasBlob());
802 PBackgroundIDBDatabaseFileChild* const fileActor =
803 database.GetOrCreateFileActorForBlob(file.MutableBlob());
804 if (NS_WARN_IF(!fileActor)) {
805 IDB_REPORT_INTERNAL_ERR();
806 return Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
809 return FileAddInfo{WrapNotNull(fileActor), file.Type()};
812 default:
813 MOZ_CRASH("Should never get here!");
816 fallible),
817 nullptr, [&aRv](const nsresult result) { aRv = result; });
819 const auto& params =
820 aOverwrite ? RequestParams{ObjectStorePutParams(std::move(commonParams))}
821 : RequestParams{ObjectStoreAddParams(std::move(commonParams))};
823 auto request = GenerateRequest(aCx, this).unwrap();
825 if (!aFromCursor) {
826 if (aOverwrite) {
827 IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
828 "database(%s).transaction(%s).objectStore(%s).put(%s)",
829 "IDBObjectStore.put(%.0s%.0s%.0s%.0s)",
830 mTransaction->LoggingSerialNumber(), request->LoggingSerialNumber(),
831 IDB_LOG_STRINGIFY(mTransaction->Database()),
832 IDB_LOG_STRINGIFY(*mTransaction), IDB_LOG_STRINGIFY(this),
833 IDB_LOG_STRINGIFY(key));
834 } else {
835 IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
836 "database(%s).transaction(%s).objectStore(%s).add(%s)",
837 "IDBObjectStore.add(%.0s%.0s%.0s%.0s)",
838 mTransaction->LoggingSerialNumber(), request->LoggingSerialNumber(),
839 IDB_LOG_STRINGIFY(mTransaction->Database()),
840 IDB_LOG_STRINGIFY(*mTransaction), IDB_LOG_STRINGIFY(this),
841 IDB_LOG_STRINGIFY(key));
845 mTransaction->StartRequest(request, params);
847 mTransaction->InvalidateCursorCaches();
849 return request;
852 RefPtr<IDBRequest> IDBObjectStore::GetAllInternal(
853 bool aKeysOnly, JSContext* aCx, JS::Handle<JS::Value> aKey,
854 const Optional<uint32_t>& aLimit, ErrorResult& aRv) {
855 AssertIsOnOwningThread();
857 if (mDeletedSpec) {
858 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
859 return nullptr;
862 if (!mTransaction->IsActive()) {
863 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
864 return nullptr;
867 RefPtr<IDBKeyRange> keyRange;
868 IDBKeyRange::FromJSVal(aCx, aKey, &keyRange, aRv);
869 if (NS_WARN_IF(aRv.Failed())) {
870 return nullptr;
873 const int64_t id = Id();
875 Maybe<SerializedKeyRange> optionalKeyRange;
876 if (keyRange) {
877 SerializedKeyRange serializedKeyRange;
878 keyRange->ToSerialized(serializedKeyRange);
879 optionalKeyRange.emplace(serializedKeyRange);
882 const uint32_t limit = aLimit.WasPassed() ? aLimit.Value() : 0;
884 RequestParams params;
885 if (aKeysOnly) {
886 params = ObjectStoreGetAllKeysParams(id, optionalKeyRange, limit);
887 } else {
888 params = ObjectStoreGetAllParams(id, optionalKeyRange, limit);
891 auto request = GenerateRequest(aCx, this).unwrap();
893 if (aKeysOnly) {
894 IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
895 "database(%s).transaction(%s).objectStore(%s)."
896 "getAllKeys(%s, %s)",
897 "IDBObjectStore.getAllKeys(%.0s%.0s%.0s%.0s%.0s)",
898 mTransaction->LoggingSerialNumber(), request->LoggingSerialNumber(),
899 IDB_LOG_STRINGIFY(mTransaction->Database()),
900 IDB_LOG_STRINGIFY(*mTransaction), IDB_LOG_STRINGIFY(this),
901 IDB_LOG_STRINGIFY(keyRange), IDB_LOG_STRINGIFY(aLimit));
902 } else {
903 IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
904 "database(%s).transaction(%s).objectStore(%s)."
905 "getAll(%s, %s)",
906 "IDBObjectStore.getAll(%.0s%.0s%.0s%.0s%.0s)",
907 mTransaction->LoggingSerialNumber(), request->LoggingSerialNumber(),
908 IDB_LOG_STRINGIFY(mTransaction->Database()),
909 IDB_LOG_STRINGIFY(*mTransaction), IDB_LOG_STRINGIFY(this),
910 IDB_LOG_STRINGIFY(keyRange), IDB_LOG_STRINGIFY(aLimit));
913 // TODO: This is necessary to preserve request ordering only. Proper
914 // sequencing of requests should be done in a more sophisticated manner that
915 // doesn't require invalidating cursor caches (Bug 1580499).
916 mTransaction->InvalidateCursorCaches();
918 mTransaction->StartRequest(request, params);
920 return request;
923 RefPtr<IDBRequest> IDBObjectStore::Add(JSContext* aCx,
924 JS::Handle<JS::Value> aValue,
925 JS::Handle<JS::Value> aKey,
926 ErrorResult& aRv) {
927 AssertIsOnOwningThread();
929 ValueWrapper valueWrapper(aCx, aValue);
931 return AddOrPut(aCx, valueWrapper, aKey, false, /* aFromCursor */ false, aRv);
934 RefPtr<IDBRequest> IDBObjectStore::Put(JSContext* aCx,
935 JS::Handle<JS::Value> aValue,
936 JS::Handle<JS::Value> aKey,
937 ErrorResult& aRv) {
938 AssertIsOnOwningThread();
940 ValueWrapper valueWrapper(aCx, aValue);
942 return AddOrPut(aCx, valueWrapper, aKey, true, /* aFromCursor */ false, aRv);
945 RefPtr<IDBRequest> IDBObjectStore::Delete(JSContext* aCx,
946 JS::Handle<JS::Value> aKey,
947 ErrorResult& aRv) {
948 AssertIsOnOwningThread();
950 return DeleteInternal(aCx, aKey, /* aFromCursor */ false, aRv);
953 RefPtr<IDBRequest> IDBObjectStore::Get(JSContext* aCx,
954 JS::Handle<JS::Value> aKey,
955 ErrorResult& aRv) {
956 AssertIsOnOwningThread();
958 return GetInternal(/* aKeyOnly */ false, aCx, aKey, aRv);
961 RefPtr<IDBRequest> IDBObjectStore::GetKey(JSContext* aCx,
962 JS::Handle<JS::Value> aKey,
963 ErrorResult& aRv) {
964 AssertIsOnOwningThread();
966 return GetInternal(/* aKeyOnly */ true, aCx, aKey, aRv);
969 RefPtr<IDBRequest> IDBObjectStore::Clear(JSContext* aCx, ErrorResult& aRv) {
970 AssertIsOnOwningThread();
972 if (mDeletedSpec) {
973 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
974 return nullptr;
977 if (!mTransaction->IsActive()) {
978 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
979 return nullptr;
982 if (!mTransaction->IsWriteAllowed()) {
983 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_READ_ONLY_ERR);
984 return nullptr;
987 const ObjectStoreClearParams params = {Id()};
989 auto request = GenerateRequest(aCx, this).unwrap();
991 IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
992 "database(%s).transaction(%s).objectStore(%s).clear()",
993 "IDBObjectStore.clear(%.0s%.0s%.0s)", mTransaction->LoggingSerialNumber(),
994 request->LoggingSerialNumber(),
995 IDB_LOG_STRINGIFY(mTransaction->Database()),
996 IDB_LOG_STRINGIFY(*mTransaction), IDB_LOG_STRINGIFY(this));
998 mTransaction->InvalidateCursorCaches();
1000 mTransaction->StartRequest(request, params);
1002 return request;
1005 RefPtr<IDBRequest> IDBObjectStore::GetAll(JSContext* aCx,
1006 JS::Handle<JS::Value> aKey,
1007 const Optional<uint32_t>& aLimit,
1008 ErrorResult& aRv) {
1009 AssertIsOnOwningThread();
1011 return GetAllInternal(/* aKeysOnly */ false, aCx, aKey, aLimit, aRv);
1014 RefPtr<IDBRequest> IDBObjectStore::GetAllKeys(JSContext* aCx,
1015 JS::Handle<JS::Value> aKey,
1016 const Optional<uint32_t>& aLimit,
1017 ErrorResult& aRv) {
1018 AssertIsOnOwningThread();
1020 return GetAllInternal(/* aKeysOnly */ true, aCx, aKey, aLimit, aRv);
1023 RefPtr<IDBRequest> IDBObjectStore::OpenCursor(JSContext* aCx,
1024 JS::Handle<JS::Value> aRange,
1025 IDBCursorDirection aDirection,
1026 ErrorResult& aRv) {
1027 AssertIsOnOwningThread();
1029 return OpenCursorInternal(/* aKeysOnly */ false, aCx, aRange, aDirection,
1030 aRv);
1033 RefPtr<IDBRequest> IDBObjectStore::OpenCursor(JSContext* aCx,
1034 IDBCursorDirection aDirection,
1035 ErrorResult& aRv) {
1036 AssertIsOnOwningThread();
1038 return OpenCursorInternal(/* aKeysOnly */ false, aCx,
1039 JS::UndefinedHandleValue, aDirection, aRv);
1042 RefPtr<IDBRequest> IDBObjectStore::OpenKeyCursor(JSContext* aCx,
1043 JS::Handle<JS::Value> aRange,
1044 IDBCursorDirection aDirection,
1045 ErrorResult& aRv) {
1046 AssertIsOnOwningThread();
1048 return OpenCursorInternal(/* aKeysOnly */ true, aCx, aRange, aDirection, aRv);
1051 RefPtr<IDBIndex> IDBObjectStore::Index(const nsAString& aName,
1052 ErrorResult& aRv) {
1053 AssertIsOnOwningThread();
1055 if (mTransaction->IsCommittingOrFinished() || mDeletedSpec) {
1056 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1057 return nullptr;
1060 const nsTArray<IndexMetadata>& indexMetadatas = mSpec->indexes();
1062 const auto endIndexMetadatas = indexMetadatas.cend();
1063 const auto foundMetadata =
1064 std::find_if(indexMetadatas.cbegin(), endIndexMetadatas,
1065 [&aName](const auto& indexMetadata) {
1066 return indexMetadata.name() == aName;
1069 if (foundMetadata == endIndexMetadatas) {
1070 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_FOUND_ERR);
1071 return nullptr;
1074 const IndexMetadata& metadata = *foundMetadata;
1076 const auto endIndexes = mIndexes.cend();
1077 const auto foundIndex =
1078 std::find_if(mIndexes.cbegin(), endIndexes,
1079 [desiredId = metadata.id()](const auto& index) {
1080 return index->Id() == desiredId;
1083 RefPtr<IDBIndex> index;
1085 if (foundIndex == endIndexes) {
1086 index = IDBIndex::Create(this, metadata);
1087 MOZ_ASSERT(index);
1089 mIndexes.AppendElement(index);
1090 } else {
1091 index = *foundIndex;
1094 return index;
1097 NS_IMPL_CYCLE_COLLECTION_CLASS(IDBObjectStore)
1099 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(IDBObjectStore)
1100 NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
1101 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCachedKeyPath)
1102 NS_IMPL_CYCLE_COLLECTION_TRACE_END
1104 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(IDBObjectStore)
1105 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTransaction)
1106 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIndexes)
1107 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDeletedIndexes)
1108 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
1110 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(IDBObjectStore)
1111 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
1113 // Don't unlink mTransaction!
1115 NS_IMPL_CYCLE_COLLECTION_UNLINK(mIndexes)
1116 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDeletedIndexes)
1118 tmp->mCachedKeyPath.setUndefined();
1120 if (tmp->mRooted) {
1121 mozilla::DropJSObjects(tmp);
1122 tmp->mRooted = false;
1124 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
1126 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IDBObjectStore)
1127 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
1128 NS_INTERFACE_MAP_ENTRY(nsISupports)
1129 NS_INTERFACE_MAP_END
1131 NS_IMPL_CYCLE_COLLECTING_ADDREF(IDBObjectStore)
1132 NS_IMPL_CYCLE_COLLECTING_RELEASE(IDBObjectStore)
1134 JSObject* IDBObjectStore::WrapObject(JSContext* aCx,
1135 JS::Handle<JSObject*> aGivenProto) {
1136 return IDBObjectStore_Binding::Wrap(aCx, this, aGivenProto);
1139 nsIGlobalObject* IDBObjectStore::GetParentObject() const {
1140 return mTransaction->GetParentObject();
1143 void IDBObjectStore::GetKeyPath(JSContext* aCx,
1144 JS::MutableHandle<JS::Value> aResult,
1145 ErrorResult& aRv) {
1146 if (!mCachedKeyPath.isUndefined()) {
1147 aResult.set(mCachedKeyPath);
1148 return;
1151 aRv = GetKeyPath().ToJSVal(aCx, mCachedKeyPath);
1152 if (NS_WARN_IF(aRv.Failed())) {
1153 return;
1156 if (mCachedKeyPath.isGCThing()) {
1157 mozilla::HoldJSObjects(this);
1158 mRooted = true;
1161 aResult.set(mCachedKeyPath);
1164 RefPtr<DOMStringList> IDBObjectStore::IndexNames() {
1165 AssertIsOnOwningThread();
1167 return CreateSortedDOMStringList(
1168 mSpec->indexes(), [](const auto& index) { return index.name(); });
1171 RefPtr<IDBRequest> IDBObjectStore::GetInternal(bool aKeyOnly, JSContext* aCx,
1172 JS::Handle<JS::Value> aKey,
1173 ErrorResult& aRv) {
1174 AssertIsOnOwningThread();
1176 if (mDeletedSpec) {
1177 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
1178 return nullptr;
1181 if (!mTransaction->IsActive()) {
1182 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
1183 return nullptr;
1186 RefPtr<IDBKeyRange> keyRange;
1187 IDBKeyRange::FromJSVal(aCx, aKey, &keyRange, aRv);
1188 if (aRv.Failed()) {
1189 return nullptr;
1192 if (!keyRange) {
1193 // Must specify a key or keyRange for get().
1194 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_KEY_ERR);
1195 return nullptr;
1198 const int64_t id = Id();
1200 SerializedKeyRange serializedKeyRange;
1201 keyRange->ToSerialized(serializedKeyRange);
1203 const auto& params =
1204 aKeyOnly ? RequestParams{ObjectStoreGetKeyParams(id, serializedKeyRange)}
1205 : RequestParams{ObjectStoreGetParams(id, serializedKeyRange)};
1207 auto request = GenerateRequest(aCx, this).unwrap();
1209 IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
1210 "database(%s).transaction(%s).objectStore(%s).get(%s)",
1211 "IDBObjectStore.get(%.0s%.0s%.0s%.0s)",
1212 mTransaction->LoggingSerialNumber(), request->LoggingSerialNumber(),
1213 IDB_LOG_STRINGIFY(mTransaction->Database()),
1214 IDB_LOG_STRINGIFY(*mTransaction), IDB_LOG_STRINGIFY(this),
1215 IDB_LOG_STRINGIFY(keyRange));
1217 // TODO: This is necessary to preserve request ordering only. Proper
1218 // sequencing of requests should be done in a more sophisticated manner that
1219 // doesn't require invalidating cursor caches (Bug 1580499).
1220 mTransaction->InvalidateCursorCaches();
1222 mTransaction->StartRequest(request, params);
1224 return request;
1227 RefPtr<IDBRequest> IDBObjectStore::DeleteInternal(JSContext* aCx,
1228 JS::Handle<JS::Value> aKey,
1229 bool aFromCursor,
1230 ErrorResult& aRv) {
1231 AssertIsOnOwningThread();
1233 if (mDeletedSpec) {
1234 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
1235 return nullptr;
1238 if (!mTransaction->IsActive()) {
1239 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
1240 return nullptr;
1243 if (!mTransaction->IsWriteAllowed()) {
1244 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_READ_ONLY_ERR);
1245 return nullptr;
1248 RefPtr<IDBKeyRange> keyRange;
1249 IDBKeyRange::FromJSVal(aCx, aKey, &keyRange, aRv);
1250 if (NS_WARN_IF((aRv.Failed()))) {
1251 return nullptr;
1254 if (!keyRange) {
1255 // Must specify a key or keyRange for delete().
1256 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_KEY_ERR);
1257 return nullptr;
1260 ObjectStoreDeleteParams params;
1261 params.objectStoreId() = Id();
1262 keyRange->ToSerialized(params.keyRange());
1264 auto request = GenerateRequest(aCx, this).unwrap();
1266 if (!aFromCursor) {
1267 IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
1268 "database(%s).transaction(%s).objectStore(%s).delete(%s)",
1269 "IDBObjectStore.delete(%.0s%.0s%.0s%.0s)",
1270 mTransaction->LoggingSerialNumber(), request->LoggingSerialNumber(),
1271 IDB_LOG_STRINGIFY(mTransaction->Database()),
1272 IDB_LOG_STRINGIFY(*mTransaction), IDB_LOG_STRINGIFY(this),
1273 IDB_LOG_STRINGIFY(keyRange));
1276 mTransaction->StartRequest(request, params);
1278 mTransaction->InvalidateCursorCaches();
1280 return request;
1283 RefPtr<IDBIndex> IDBObjectStore::CreateIndex(
1284 const nsAString& aName, const StringOrStringSequence& aKeyPath,
1285 const IDBIndexParameters& aOptionalParameters, ErrorResult& aRv) {
1286 AssertIsOnOwningThread();
1288 if (mTransaction->GetMode() != IDBTransaction::Mode::VersionChange ||
1289 mDeletedSpec) {
1290 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
1291 return nullptr;
1294 const auto transaction = IDBTransaction::MaybeCurrent();
1295 if (!transaction || transaction != mTransaction || !transaction->IsActive()) {
1296 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
1297 return nullptr;
1300 const auto& indexes = mSpec->indexes();
1301 const auto end = indexes.cend();
1302 const auto foundIt = std::find_if(
1303 indexes.cbegin(), end,
1304 [&aName](const auto& index) { return aName == index.name(); });
1305 if (foundIt != end) {
1306 aRv.ThrowConstraintError(nsPrintfCString(
1307 "Index named '%s' already exists at index '%zu'",
1308 NS_ConvertUTF16toUTF8(aName).get(), foundIt.GetIndex()));
1309 return nullptr;
1312 const auto checkValid = [](const auto& keyPath) -> Result<KeyPath, nsresult> {
1313 if (!keyPath.IsValid()) {
1314 return Err(NS_ERROR_DOM_SYNTAX_ERR);
1317 return keyPath;
1320 QM_INFOONLY_TRY_UNWRAP(
1321 const auto maybeKeyPath,
1322 ([&aKeyPath, checkValid]() -> Result<KeyPath, nsresult> {
1323 if (aKeyPath.IsString()) {
1324 QM_TRY_RETURN(
1325 KeyPath::Parse(aKeyPath.GetAsString()).andThen(checkValid));
1328 MOZ_ASSERT(aKeyPath.IsStringSequence());
1329 if (aKeyPath.GetAsStringSequence().IsEmpty()) {
1330 return Err(NS_ERROR_DOM_SYNTAX_ERR);
1333 QM_TRY_RETURN(
1334 KeyPath::Parse(aKeyPath.GetAsStringSequence()).andThen(checkValid));
1335 })());
1336 if (!maybeKeyPath) {
1337 aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
1338 return nullptr;
1341 const auto& keyPath = maybeKeyPath.ref();
1343 if (aOptionalParameters.mMultiEntry && keyPath.IsArray()) {
1344 aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
1345 return nullptr;
1348 #ifdef DEBUG
1350 const auto duplicateIndexName = std::any_of(
1351 mIndexes.cbegin(), mIndexes.cend(),
1352 [&aName](const auto& index) { return index->Name() == aName; });
1353 MOZ_ASSERT(!duplicateIndexName);
1355 #endif
1357 const IndexMetadata* const oldMetadataElements =
1358 indexes.IsEmpty() ? nullptr : indexes.Elements();
1360 // With this setup we only validate the passed in locale name by the time we
1361 // get to encoding Keys. Maybe we should do it here right away and error out.
1363 // Valid locale names are always ASCII as per BCP-47.
1364 nsCString locale = NS_LossyConvertUTF16toASCII(aOptionalParameters.mLocale);
1365 bool autoLocale = locale.EqualsASCII("auto");
1366 if (autoLocale) {
1367 locale = IndexedDatabaseManager::GetLocale();
1370 if (!locale.IsEmpty()) {
1371 // Set use counter and log deprecation warning for locale in parent doc.
1372 nsIGlobalObject* global = GetParentObject();
1373 AutoJSAPI jsapi;
1374 // This isn't critical so don't error out if init fails.
1375 if (jsapi.Init(global)) {
1376 DeprecationWarning(
1377 jsapi.cx(), global->GetGlobalJSObject(),
1378 DeprecatedOperations::eIDBObjectStoreCreateIndexLocale);
1382 IndexMetadata* const metadata = mSpec->indexes().EmplaceBack(
1383 transaction->NextIndexId(), nsString(aName), keyPath, locale,
1384 aOptionalParameters.mUnique, aOptionalParameters.mMultiEntry, autoLocale);
1386 if (oldMetadataElements && oldMetadataElements != indexes.Elements()) {
1387 MOZ_ASSERT(indexes.Length() > 1);
1389 // Array got moved, update the spec pointers for all live indexes.
1390 RefreshSpec(/* aMayDelete */ false);
1393 transaction->CreateIndex(this, *metadata);
1395 auto index = IDBIndex::Create(this, *metadata);
1397 mIndexes.AppendElement(index);
1399 // Don't do this in the macro because we always need to increment the serial
1400 // number to keep in sync with the parent.
1401 const uint64_t requestSerialNumber = IDBRequest::NextSerialNumber();
1403 IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
1404 "database(%s).transaction(%s).objectStore(%s).createIndex(%s)",
1405 "IDBObjectStore.createIndex(%.0s%.0s%.0s%.0s)",
1406 mTransaction->LoggingSerialNumber(), requestSerialNumber,
1407 IDB_LOG_STRINGIFY(mTransaction->Database()),
1408 IDB_LOG_STRINGIFY(*mTransaction), IDB_LOG_STRINGIFY(this),
1409 IDB_LOG_STRINGIFY(index));
1411 return index;
1414 void IDBObjectStore::DeleteIndex(const nsAString& aName, ErrorResult& aRv) {
1415 AssertIsOnOwningThread();
1417 if (mTransaction->GetMode() != IDBTransaction::Mode::VersionChange ||
1418 mDeletedSpec) {
1419 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
1420 return;
1423 const auto transaction = IDBTransaction::MaybeCurrent();
1424 if (!transaction || transaction != mTransaction || !transaction->IsActive()) {
1425 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
1426 return;
1429 const auto& metadataArray = mSpec->indexes();
1431 const auto endMetadata = metadataArray.cend();
1432 const auto foundMetadataIt = std::find_if(
1433 metadataArray.cbegin(), endMetadata,
1434 [&aName](const auto& metadata) { return aName == metadata.name(); });
1436 if (foundMetadataIt == endMetadata) {
1437 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_FOUND_ERR);
1438 return;
1441 const auto foundId = foundMetadataIt->id();
1442 MOZ_ASSERT(foundId);
1444 // Must remove index from mIndexes before altering the metadata array!
1446 const auto end = mIndexes.end();
1447 const auto foundIt = std::find_if(
1448 mIndexes.begin(), end,
1449 [foundId](const auto& index) { return index->Id() == foundId; });
1450 // TODO: Or should we assert foundIt != end?
1451 if (foundIt != end) {
1452 auto& index = *foundIt;
1454 index->NoteDeletion();
1456 mDeletedIndexes.EmplaceBack(std::move(index));
1457 mIndexes.RemoveElementAt(foundIt.GetIndex());
1461 mSpec->indexes().RemoveElementAt(foundMetadataIt.GetIndex());
1463 RefreshSpec(/* aMayDelete */ false);
1465 // Don't do this in the macro because we always need to increment the serial
1466 // number to keep in sync with the parent.
1467 const uint64_t requestSerialNumber = IDBRequest::NextSerialNumber();
1469 IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
1470 "database(%s).transaction(%s).objectStore(%s)."
1471 "deleteIndex(\"%s\")",
1472 "IDBObjectStore.deleteIndex(%.0s%.0s%.0s%.0s)",
1473 mTransaction->LoggingSerialNumber(), requestSerialNumber,
1474 IDB_LOG_STRINGIFY(mTransaction->Database()),
1475 IDB_LOG_STRINGIFY(*mTransaction), IDB_LOG_STRINGIFY(this),
1476 NS_ConvertUTF16toUTF8(aName).get());
1478 transaction->DeleteIndex(this, foundId);
1481 RefPtr<IDBRequest> IDBObjectStore::Count(JSContext* aCx,
1482 JS::Handle<JS::Value> aKey,
1483 ErrorResult& aRv) {
1484 AssertIsOnOwningThread();
1486 if (mDeletedSpec) {
1487 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
1488 return nullptr;
1491 if (!mTransaction->IsActive()) {
1492 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
1493 return nullptr;
1496 RefPtr<IDBKeyRange> keyRange;
1497 IDBKeyRange::FromJSVal(aCx, aKey, &keyRange, aRv);
1498 if (aRv.Failed()) {
1499 return nullptr;
1502 ObjectStoreCountParams params;
1503 params.objectStoreId() = Id();
1505 if (keyRange) {
1506 SerializedKeyRange serializedKeyRange;
1507 keyRange->ToSerialized(serializedKeyRange);
1508 params.optionalKeyRange().emplace(serializedKeyRange);
1511 auto request = GenerateRequest(aCx, this).unwrap();
1513 IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
1514 "database(%s).transaction(%s).objectStore(%s).count(%s)",
1515 "IDBObjectStore.count(%.0s%.0s%.0s%.0s)",
1516 mTransaction->LoggingSerialNumber(), request->LoggingSerialNumber(),
1517 IDB_LOG_STRINGIFY(mTransaction->Database()),
1518 IDB_LOG_STRINGIFY(*mTransaction), IDB_LOG_STRINGIFY(this),
1519 IDB_LOG_STRINGIFY(keyRange));
1521 // TODO: This is necessary to preserve request ordering only. Proper
1522 // sequencing of requests should be done in a more sophisticated manner that
1523 // doesn't require invalidating cursor caches (Bug 1580499).
1524 mTransaction->InvalidateCursorCaches();
1526 mTransaction->StartRequest(request, params);
1528 return request;
1531 RefPtr<IDBRequest> IDBObjectStore::OpenCursorInternal(
1532 bool aKeysOnly, JSContext* aCx, JS::Handle<JS::Value> aRange,
1533 IDBCursorDirection aDirection, ErrorResult& aRv) {
1534 AssertIsOnOwningThread();
1535 MOZ_ASSERT(aCx);
1537 if (mDeletedSpec) {
1538 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
1539 return nullptr;
1542 if (!mTransaction->IsActive()) {
1543 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
1544 return nullptr;
1547 RefPtr<IDBKeyRange> keyRange;
1548 IDBKeyRange::FromJSVal(aCx, aRange, &keyRange, aRv);
1549 if (NS_WARN_IF(aRv.Failed())) {
1550 return nullptr;
1553 const int64_t objectStoreId = Id();
1555 Maybe<SerializedKeyRange> optionalKeyRange;
1557 if (keyRange) {
1558 SerializedKeyRange serializedKeyRange;
1559 keyRange->ToSerialized(serializedKeyRange);
1561 optionalKeyRange.emplace(std::move(serializedKeyRange));
1564 const CommonOpenCursorParams commonParams = {
1565 objectStoreId, std::move(optionalKeyRange), aDirection};
1567 // TODO: It would be great if the IPDL generator created a constructor
1568 // accepting a CommonOpenCursorParams by value or rvalue reference.
1569 const auto params =
1570 aKeysOnly ? OpenCursorParams{ObjectStoreOpenKeyCursorParams{commonParams}}
1571 : OpenCursorParams{ObjectStoreOpenCursorParams{commonParams}};
1573 auto request = GenerateRequest(aCx, this).unwrap();
1575 if (aKeysOnly) {
1576 IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
1577 "database(%s).transaction(%s).objectStore(%s)."
1578 "openKeyCursor(%s, %s)",
1579 "IDBObjectStore.openKeyCursor(%.0s%.0s%.0s%.0s%.0s)",
1580 mTransaction->LoggingSerialNumber(), request->LoggingSerialNumber(),
1581 IDB_LOG_STRINGIFY(mTransaction->Database()),
1582 IDB_LOG_STRINGIFY(*mTransaction), IDB_LOG_STRINGIFY(this),
1583 IDB_LOG_STRINGIFY(keyRange), IDB_LOG_STRINGIFY(aDirection));
1584 } else {
1585 IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
1586 "database(%s).transaction(%s).objectStore(%s)."
1587 "openCursor(%s, %s)",
1588 "IDBObjectStore.openCursor(%.0s%.0s%.0s%.0s%.0s)",
1589 mTransaction->LoggingSerialNumber(), request->LoggingSerialNumber(),
1590 IDB_LOG_STRINGIFY(mTransaction->Database()),
1591 IDB_LOG_STRINGIFY(*mTransaction), IDB_LOG_STRINGIFY(this),
1592 IDB_LOG_STRINGIFY(keyRange), IDB_LOG_STRINGIFY(aDirection));
1595 const auto actor =
1596 aKeysOnly
1597 ? static_cast<SafeRefPtr<BackgroundCursorChildBase>>(
1598 MakeSafeRefPtr<
1599 BackgroundCursorChild<IDBCursorType::ObjectStoreKey>>(
1600 request, this, aDirection))
1601 : MakeSafeRefPtr<BackgroundCursorChild<IDBCursorType::ObjectStore>>(
1602 request, this, aDirection);
1604 // TODO: This is necessary to preserve request ordering only. Proper
1605 // sequencing of requests should be done in a more sophisticated manner that
1606 // doesn't require invalidating cursor caches (Bug 1580499).
1607 mTransaction->InvalidateCursorCaches();
1609 mTransaction->OpenCursor(*actor, params);
1611 return request;
1614 void IDBObjectStore::RefreshSpec(bool aMayDelete) {
1615 AssertIsOnOwningThread();
1616 MOZ_ASSERT_IF(mDeletedSpec, mSpec == mDeletedSpec.get());
1618 auto* const foundObjectStoreSpec =
1619 mTransaction->Database()->LookupModifiableObjectStoreSpec(
1620 [id = Id()](const auto& objSpec) {
1621 return objSpec.metadata().id() == id;
1623 if (foundObjectStoreSpec) {
1624 mSpec = foundObjectStoreSpec;
1626 for (auto& index : mIndexes) {
1627 index->RefreshMetadata(aMayDelete);
1630 for (auto& index : mDeletedIndexes) {
1631 index->RefreshMetadata(false);
1635 MOZ_ASSERT_IF(!aMayDelete && !mDeletedSpec, foundObjectStoreSpec);
1637 if (foundObjectStoreSpec) {
1638 MOZ_ASSERT(mSpec != mDeletedSpec.get());
1639 mDeletedSpec = nullptr;
1640 } else {
1641 NoteDeletion();
1645 const ObjectStoreSpec& IDBObjectStore::Spec() const {
1646 AssertIsOnOwningThread();
1647 MOZ_ASSERT(mSpec);
1649 return *mSpec;
1652 void IDBObjectStore::NoteDeletion() {
1653 AssertIsOnOwningThread();
1654 MOZ_ASSERT(mSpec);
1655 MOZ_ASSERT(Id() == mSpec->metadata().id());
1657 if (mDeletedSpec) {
1658 MOZ_ASSERT(mDeletedSpec.get() == mSpec);
1659 return;
1662 // Copy the spec here.
1663 mDeletedSpec = MakeUnique<ObjectStoreSpec>(*mSpec);
1664 mDeletedSpec->indexes().Clear();
1666 mSpec = mDeletedSpec.get();
1668 for (const auto& index : mIndexes) {
1669 index->NoteDeletion();
1673 const nsString& IDBObjectStore::Name() const {
1674 AssertIsOnOwningThread();
1675 MOZ_ASSERT(mSpec);
1677 return mSpec->metadata().name();
1680 void IDBObjectStore::SetName(const nsAString& aName, ErrorResult& aRv) {
1681 AssertIsOnOwningThread();
1683 if (mTransaction->GetMode() != IDBTransaction::Mode::VersionChange ||
1684 mDeletedSpec) {
1685 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1686 return;
1689 const auto transaction = IDBTransaction::MaybeCurrent();
1690 if (!transaction || transaction != mTransaction || !transaction->IsActive()) {
1691 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
1692 return;
1695 if (aName == mSpec->metadata().name()) {
1696 return;
1699 // Cache logging string of this object store before renaming.
1700 const LoggingString loggingOldObjectStore(this);
1702 const nsresult rv =
1703 transaction->Database()->RenameObjectStore(mSpec->metadata().id(), aName);
1705 if (NS_FAILED(rv)) {
1706 aRv.Throw(rv);
1707 return;
1710 // Don't do this in the macro because we always need to increment the serial
1711 // number to keep in sync with the parent.
1712 const uint64_t requestSerialNumber = IDBRequest::NextSerialNumber();
1714 IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
1715 "database(%s).transaction(%s).objectStore(%s).rename(%s)",
1716 "IDBObjectStore.rename(%.0s%.0s%.0s%.0s)",
1717 mTransaction->LoggingSerialNumber(), requestSerialNumber,
1718 IDB_LOG_STRINGIFY(mTransaction->Database()),
1719 IDB_LOG_STRINGIFY(*mTransaction), loggingOldObjectStore.get(),
1720 IDB_LOG_STRINGIFY(this));
1722 transaction->RenameObjectStore(mSpec->metadata().id(), aName);
1725 bool IDBObjectStore::AutoIncrement() const {
1726 AssertIsOnOwningThread();
1727 MOZ_ASSERT(mSpec);
1729 return mSpec->metadata().autoIncrement();
1732 const indexedDB::KeyPath& IDBObjectStore::GetKeyPath() const {
1733 AssertIsOnOwningThread();
1734 MOZ_ASSERT(mSpec);
1736 return mSpec->metadata().keyPath();
1739 bool IDBObjectStore::HasValidKeyPath() const {
1740 AssertIsOnOwningThread();
1741 MOZ_ASSERT(mSpec);
1743 return GetKeyPath().IsValid();
1746 bool IDBObjectStore::ValueWrapper::Clone(JSContext* aCx) {
1747 if (mCloned) {
1748 return true;
1751 static const JSStructuredCloneCallbacks callbacks = {
1752 CopyingStructuredCloneReadCallback /* read */,
1753 CopyingStructuredCloneWriteCallback /* write */,
1754 nullptr /* reportError */,
1755 nullptr /* readTransfer */,
1756 nullptr /* writeTransfer */,
1757 nullptr /* freeTransfer */,
1758 nullptr /* canTransfer */,
1759 nullptr /* sabCloned */
1762 StructuredCloneInfo cloneInfo;
1764 JS::Rooted<JS::Value> clonedValue(aCx);
1765 if (!JS_StructuredClone(aCx, mValue, &clonedValue, &callbacks, &cloneInfo)) {
1766 return false;
1769 mValue = clonedValue;
1771 mCloned = true;
1773 return true;
1776 } // namespace mozilla::dom