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"
12 #include "IDBCursorType.h"
13 #include "IDBDatabase.h"
14 #include "IDBEvents.h"
15 #include "IDBFactory.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"
25 #include "ProfilerHelpers.h"
26 #include "ReportInternalError.h"
27 #include "js/Array.h" // JS::GetArrayLength, JS::IsArrayObject
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"
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
;
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
;
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
,
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
;
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
) {
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
);
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
))) {
174 const uint64_t nativeEndianSize
= blob
->GetSize(rv
);
175 MOZ_ASSERT(!rv
.Failed());
177 const uint64_t size
= NativeEndian::swapToLittleEndian(nativeEndianSize
);
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
)) {
188 "Fix the structured clone data to use a bigger type!");
192 const uint32_t index
= cloneWriteInfo
->mFiles
.Length();
194 if (!JS_WriteUint32Pair(aWriter
,
195 blob
->IsFile() ? SCTAG_DOM_FILE
: SCTAG_DOM_BLOB
,
197 !JS_WriteBytes(aWriter
, &size
, sizeof(size
)) ||
198 !JS_WriteBytes(aWriter
, &convTypeLength
, sizeof(convTypeLength
)) ||
199 !JS_WriteBytes(aWriter
, convType
.get(), convType
.Length())) {
203 const RefPtr
<File
> file
= blob
->ToFile();
206 const int64_t nativeEndianLastModifiedDate
= file
->GetLastModified(rv
);
207 MOZ_ALWAYS_TRUE(!rv
.Failed());
209 const int64_t lastModifiedDate
=
210 NativeEndian::swapToLittleEndian(nativeEndianLastModifiedDate
);
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())) {
227 cloneWriteInfo
->mFiles
.EmplaceBack(StructuredCloneFileBase::eBlob
, blob
);
233 return StructuredCloneHolder::WriteFullySerializableObjects(aCx
, aWriter
,
237 bool CopyingStructuredCloneWriteCallback(JSContext
* aCx
,
238 JSStructuredCloneWriter
* aWriter
,
239 JS::Handle
<JSObject
*> aObj
,
240 bool* aSameProcessRequired
,
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
)) {
257 "Fix the structured clone data to use a bigger type!");
261 const uint32_t index
= cloneInfo
->mFiles
.Length();
263 if (!JS_WriteUint32Pair(aWriter
,
264 blob
->IsFile() ? SCTAG_DOM_FILE
: SCTAG_DOM_BLOB
,
269 cloneInfo
->mFiles
.EmplaceBack(StructuredCloneFileBase::eBlob
, blob
);
275 return StructuredCloneHolder::WriteFullySerializableObjects(aCx
, aWriter
,
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 */
289 auto* const data
= static_cast<GetAddInfoClosure
*>(aClosure
);
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
;
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
,
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!");
328 StructuredCloneFileChild
& file
= cloneInfo
->mFiles
[aData
];
330 switch (static_cast<StructuredCloneTags
>(aTag
)) {
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
345 const RefPtr
<Blob
> blob
= file
.BlobPtr();
346 MOZ_ASSERT(blob
->IsFile());
348 const RefPtr
<File
> file
= blob
->ToFile();
351 if (!WrapAsJSObject(aCx
, file
, &result
)) {
359 case SCTAG_DOM_MUTABLEFILE
:
360 MOZ_ASSERT(file
.Type() == StructuredCloneFileBase::eMutableFile
);
365 // This cannot be reached due to the if condition before.
370 return StructuredCloneHolder::ReadFullySerializableObjects(aCx
, aReader
, aTag
,
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()),
385 mId(aSpec
->metadata().id()),
387 MOZ_ASSERT(mTransaction
);
388 mTransaction
->AssertIsOnOwningThread();
392 IDBObjectStore::~IDBObjectStore() {
393 AssertIsOnOwningThread();
396 mozilla::DropJSObjects(this);
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
);
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");
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();
438 QM_TRY_UNWRAP(auto item
, MakeIndexUpdateInfo(aIndexID
, key
, aLocale
),
440 [aRv
](const nsresult tryResult
) { aRv
->Throw(tryResult
); });
442 aUpdateInfoArray
->AppendElement(std::move(item
));
446 JS::Rooted
<JS::Value
> val(aCx
);
447 if (NS_FAILED(aKeyPath
.ExtractKeyAsJSVal(aCx
, aVal
, val
.address()))) {
452 if (NS_WARN_IF(!JS::IsArrayObject(aCx
, val
, &isArray
))) {
453 IDB_REPORT_INTERNAL_ERR();
454 aRv
->Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR
);
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
);
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
);
476 !JS_HasOwnPropertyById(aCx
, array
, indexId
, &hasOwnProperty
))) {
477 IDB_REPORT_INTERNAL_ERR();
478 aRv
->Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR
);
482 if (!hasOwnProperty
) {
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
);
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();
504 QM_TRY_UNWRAP(auto item
, MakeIndexUpdateInfo(aIndexID
, value
, aLocale
),
506 [aRv
](const nsresult tryResult
) { aRv
->Throw(tryResult
); });
508 aUpdateInfoArray
->AppendElement(std::move(item
));
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();
521 QM_TRY_UNWRAP(auto item
, MakeIndexUpdateInfo(aIndexID
, value
, aLocale
),
523 [aRv
](const nsresult tryResult
) { aRv
->Throw(tryResult
); });
525 aUpdateInfoArray
->AppendElement(std::move(item
));
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()) {
539 aReadInfo
.ReleaseFiles();
543 bool IDBObjectStore::DeserializeValue(
544 JSContext
* aCx
, StructuredCloneReadInfoChild
&& aCloneReadInfo
,
545 JS::MutableHandle
<JS::Value
> aValue
) {
548 if (!aCloneReadInfo
.Data().Size()) {
549 aValue
.setUndefined();
553 MOZ_ASSERT(!(aCloneReadInfo
.Data().Size() % sizeof(uint64_t)));
555 static const JSStructuredCloneCallbacks callbacks
= {
556 StructuredCloneReadCallback
<StructuredCloneReadInfoChild
>,
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
);
575 void IDBObjectStore::AssertIsOnOwningThread() const {
576 MOZ_ASSERT(mTransaction
);
577 mTransaction
->AssertIsOnOwningThread();
582 void IDBObjectStore::GetAddInfo(JSContext
* aCx
, ValueWrapper
& aValueWrapper
,
583 JS::Handle
<JS::Value
> aKeyVal
,
584 StructuredCloneWriteInfo
& aCloneWriteInfo
,
586 nsTArray
<IndexUpdateInfo
>& aUpdateInfoArray
,
588 // Return DATA_ERR if a key was passed in and this objectStore uses inline
590 if (!aKeyVal
.isUndefined() && HasValidKeyPath()) {
591 aRv
.Throw(NS_ERROR_DOM_INDEXEDDB_DATA_ERR
);
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
>);
605 } else if (!isAutoIncrement
) {
606 if (!aValueWrapper
.Clone(aCx
)) {
607 aRv
.Throw(NS_ERROR_DOM_DATA_CLONE_ERR
);
611 aRv
= GetKeyPath().ExtractKey(aCx
, aValueWrapper
.Value(), aKey
);
617 // Return DATA_ERR if no key was specified this isn't an autoIncrement
619 if (aKey
.IsUnset() && !isAutoIncrement
) {
620 aRv
.Throw(NS_ERROR_DOM_INDEXEDDB_DATA_ERR
);
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
);
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];
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())) {
665 if (isAutoIncrement
&& HasValidKeyPath()) {
666 if (!aValueWrapper
.Clone(aCx
)) {
667 aRv
.Throw(NS_ERROR_DOM_DATA_CLONE_ERR
);
671 GetAddInfoClosure
data(aCloneWriteInfo
, aValueWrapper
.Value());
673 MOZ_ASSERT(aKey
.IsUnset());
675 aRv
= GetKeyPath().ExtractOrCreateKey(aCx
, aValueWrapper
.Value(), aKey
,
676 &GetAddInfoCallback
, &data
);
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
,
689 AssertIsOnOwningThread();
691 MOZ_ASSERT_IF(aFromCursor
, aOverwrite
);
693 if (mTransaction
->GetMode() == IDBTransaction::Mode::Cleanup
||
695 aRv
.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR
);
699 if (!mTransaction
->IsActive()) {
700 aRv
.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR
);
704 if (!mTransaction
->IsWriteAllowed()) {
705 aRv
.Throw(NS_ERROR_DOM_INDEXEDDB_READ_ONLY_ERR
);
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();
718 const uint32_t previousPendingRequestCount
{
719 mTransaction
->GetPendingRequestCount()};
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
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
));
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.
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()};
813 MOZ_CRASH("Should never get here!");
817 nullptr, [&aRv
](const nsresult result
) { aRv
= result
; });
820 aOverwrite
? RequestParams
{ObjectStorePutParams(std::move(commonParams
))}
821 : RequestParams
{ObjectStoreAddParams(std::move(commonParams
))};
823 auto request
= GenerateRequest(aCx
, this).unwrap();
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
));
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();
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();
858 aRv
.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR
);
862 if (!mTransaction
->IsActive()) {
863 aRv
.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR
);
867 RefPtr
<IDBKeyRange
> keyRange
;
868 IDBKeyRange::FromJSVal(aCx
, aKey
, &keyRange
, aRv
);
869 if (NS_WARN_IF(aRv
.Failed())) {
873 const int64_t id
= Id();
875 Maybe
<SerializedKeyRange
> optionalKeyRange
;
877 SerializedKeyRange serializedKeyRange
;
878 keyRange
->ToSerialized(serializedKeyRange
);
879 optionalKeyRange
.emplace(serializedKeyRange
);
882 const uint32_t limit
= aLimit
.WasPassed() ? aLimit
.Value() : 0;
884 RequestParams params
;
886 params
= ObjectStoreGetAllKeysParams(id
, optionalKeyRange
, limit
);
888 params
= ObjectStoreGetAllParams(id
, optionalKeyRange
, limit
);
891 auto request
= GenerateRequest(aCx
, this).unwrap();
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
));
903 IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
904 "database(%s).transaction(%s).objectStore(%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
);
923 RefPtr
<IDBRequest
> IDBObjectStore::Add(JSContext
* aCx
,
924 JS::Handle
<JS::Value
> aValue
,
925 JS::Handle
<JS::Value
> aKey
,
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
,
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
,
948 AssertIsOnOwningThread();
950 return DeleteInternal(aCx
, aKey
, /* aFromCursor */ false, aRv
);
953 RefPtr
<IDBRequest
> IDBObjectStore::Get(JSContext
* aCx
,
954 JS::Handle
<JS::Value
> aKey
,
956 AssertIsOnOwningThread();
958 return GetInternal(/* aKeyOnly */ false, aCx
, aKey
, aRv
);
961 RefPtr
<IDBRequest
> IDBObjectStore::GetKey(JSContext
* aCx
,
962 JS::Handle
<JS::Value
> aKey
,
964 AssertIsOnOwningThread();
966 return GetInternal(/* aKeyOnly */ true, aCx
, aKey
, aRv
);
969 RefPtr
<IDBRequest
> IDBObjectStore::Clear(JSContext
* aCx
, ErrorResult
& aRv
) {
970 AssertIsOnOwningThread();
973 aRv
.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR
);
977 if (!mTransaction
->IsActive()) {
978 aRv
.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR
);
982 if (!mTransaction
->IsWriteAllowed()) {
983 aRv
.Throw(NS_ERROR_DOM_INDEXEDDB_READ_ONLY_ERR
);
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
);
1005 RefPtr
<IDBRequest
> IDBObjectStore::GetAll(JSContext
* aCx
,
1006 JS::Handle
<JS::Value
> aKey
,
1007 const Optional
<uint32_t>& aLimit
,
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
,
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
,
1027 AssertIsOnOwningThread();
1029 return OpenCursorInternal(/* aKeysOnly */ false, aCx
, aRange
, aDirection
,
1033 RefPtr
<IDBRequest
> IDBObjectStore::OpenCursor(JSContext
* aCx
,
1034 IDBCursorDirection aDirection
,
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
,
1046 AssertIsOnOwningThread();
1048 return OpenCursorInternal(/* aKeysOnly */ true, aCx
, aRange
, aDirection
, aRv
);
1051 RefPtr
<IDBIndex
> IDBObjectStore::Index(const nsAString
& aName
,
1053 AssertIsOnOwningThread();
1055 if (mTransaction
->IsCommittingOrFinished() || mDeletedSpec
) {
1056 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
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
);
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
);
1089 mIndexes
.AppendElement(index
);
1091 index
= *foundIndex
;
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();
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
,
1146 if (!mCachedKeyPath
.isUndefined()) {
1147 aResult
.set(mCachedKeyPath
);
1151 aRv
= GetKeyPath().ToJSVal(aCx
, mCachedKeyPath
);
1152 if (NS_WARN_IF(aRv
.Failed())) {
1156 if (mCachedKeyPath
.isGCThing()) {
1157 mozilla::HoldJSObjects(this);
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
,
1174 AssertIsOnOwningThread();
1177 aRv
.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR
);
1181 if (!mTransaction
->IsActive()) {
1182 aRv
.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR
);
1186 RefPtr
<IDBKeyRange
> keyRange
;
1187 IDBKeyRange::FromJSVal(aCx
, aKey
, &keyRange
, aRv
);
1193 // Must specify a key or keyRange for get().
1194 aRv
.Throw(NS_ERROR_DOM_INDEXEDDB_KEY_ERR
);
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
);
1227 RefPtr
<IDBRequest
> IDBObjectStore::DeleteInternal(JSContext
* aCx
,
1228 JS::Handle
<JS::Value
> aKey
,
1231 AssertIsOnOwningThread();
1234 aRv
.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR
);
1238 if (!mTransaction
->IsActive()) {
1239 aRv
.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR
);
1243 if (!mTransaction
->IsWriteAllowed()) {
1244 aRv
.Throw(NS_ERROR_DOM_INDEXEDDB_READ_ONLY_ERR
);
1248 RefPtr
<IDBKeyRange
> keyRange
;
1249 IDBKeyRange::FromJSVal(aCx
, aKey
, &keyRange
, aRv
);
1250 if (NS_WARN_IF((aRv
.Failed()))) {
1255 // Must specify a key or keyRange for delete().
1256 aRv
.Throw(NS_ERROR_DOM_INDEXEDDB_KEY_ERR
);
1260 ObjectStoreDeleteParams params
;
1261 params
.objectStoreId() = Id();
1262 keyRange
->ToSerialized(params
.keyRange());
1264 auto request
= GenerateRequest(aCx
, this).unwrap();
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();
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
||
1290 aRv
.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR
);
1294 const auto transaction
= IDBTransaction::MaybeCurrent();
1295 if (!transaction
|| transaction
!= mTransaction
|| !transaction
->IsActive()) {
1296 aRv
.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR
);
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()));
1312 const auto checkValid
= [](const auto& keyPath
) -> Result
<KeyPath
, nsresult
> {
1313 if (!keyPath
.IsValid()) {
1314 return Err(NS_ERROR_DOM_SYNTAX_ERR
);
1320 QM_INFOONLY_TRY_UNWRAP(
1321 const auto maybeKeyPath
,
1322 ([&aKeyPath
, checkValid
]() -> Result
<KeyPath
, nsresult
> {
1323 if (aKeyPath
.IsString()) {
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
);
1334 KeyPath::Parse(aKeyPath
.GetAsStringSequence()).andThen(checkValid
));
1336 if (!maybeKeyPath
) {
1337 aRv
.Throw(NS_ERROR_DOM_SYNTAX_ERR
);
1341 const auto& keyPath
= maybeKeyPath
.ref();
1343 if (aOptionalParameters
.mMultiEntry
&& keyPath
.IsArray()) {
1344 aRv
.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR
);
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
);
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");
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();
1374 // This isn't critical so don't error out if init fails.
1375 if (jsapi
.Init(global
)) {
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
));
1414 void IDBObjectStore::DeleteIndex(const nsAString
& aName
, ErrorResult
& aRv
) {
1415 AssertIsOnOwningThread();
1417 if (mTransaction
->GetMode() != IDBTransaction::Mode::VersionChange
||
1419 aRv
.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR
);
1423 const auto transaction
= IDBTransaction::MaybeCurrent();
1424 if (!transaction
|| transaction
!= mTransaction
|| !transaction
->IsActive()) {
1425 aRv
.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR
);
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
);
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
,
1484 AssertIsOnOwningThread();
1487 aRv
.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR
);
1491 if (!mTransaction
->IsActive()) {
1492 aRv
.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR
);
1496 RefPtr
<IDBKeyRange
> keyRange
;
1497 IDBKeyRange::FromJSVal(aCx
, aKey
, &keyRange
, aRv
);
1502 ObjectStoreCountParams params
;
1503 params
.objectStoreId() = Id();
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
);
1531 RefPtr
<IDBRequest
> IDBObjectStore::OpenCursorInternal(
1532 bool aKeysOnly
, JSContext
* aCx
, JS::Handle
<JS::Value
> aRange
,
1533 IDBCursorDirection aDirection
, ErrorResult
& aRv
) {
1534 AssertIsOnOwningThread();
1538 aRv
.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR
);
1542 if (!mTransaction
->IsActive()) {
1543 aRv
.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR
);
1547 RefPtr
<IDBKeyRange
> keyRange
;
1548 IDBKeyRange::FromJSVal(aCx
, aRange
, &keyRange
, aRv
);
1549 if (NS_WARN_IF(aRv
.Failed())) {
1553 const int64_t objectStoreId
= Id();
1555 Maybe
<SerializedKeyRange
> optionalKeyRange
;
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.
1570 aKeysOnly
? OpenCursorParams
{ObjectStoreOpenKeyCursorParams
{commonParams
}}
1571 : OpenCursorParams
{ObjectStoreOpenCursorParams
{commonParams
}};
1573 auto request
= GenerateRequest(aCx
, this).unwrap();
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
));
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
));
1597 ? static_cast<SafeRefPtr
<BackgroundCursorChildBase
>>(
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
);
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;
1645 const ObjectStoreSpec
& IDBObjectStore::Spec() const {
1646 AssertIsOnOwningThread();
1652 void IDBObjectStore::NoteDeletion() {
1653 AssertIsOnOwningThread();
1655 MOZ_ASSERT(Id() == mSpec
->metadata().id());
1658 MOZ_ASSERT(mDeletedSpec
.get() == mSpec
);
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();
1677 return mSpec
->metadata().name();
1680 void IDBObjectStore::SetName(const nsAString
& aName
, ErrorResult
& aRv
) {
1681 AssertIsOnOwningThread();
1683 if (mTransaction
->GetMode() != IDBTransaction::Mode::VersionChange
||
1685 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
1689 const auto transaction
= IDBTransaction::MaybeCurrent();
1690 if (!transaction
|| transaction
!= mTransaction
|| !transaction
->IsActive()) {
1691 aRv
.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR
);
1695 if (aName
== mSpec
->metadata().name()) {
1699 // Cache logging string of this object store before renaming.
1700 const LoggingString
loggingOldObjectStore(this);
1703 transaction
->Database()->RenameObjectStore(mSpec
->metadata().id(), aName
);
1705 if (NS_FAILED(rv
)) {
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();
1729 return mSpec
->metadata().autoIncrement();
1732 const indexedDB::KeyPath
& IDBObjectStore::GetKeyPath() const {
1733 AssertIsOnOwningThread();
1736 return mSpec
->metadata().keyPath();
1739 bool IDBObjectStore::HasValidKeyPath() const {
1740 AssertIsOnOwningThread();
1743 return GetKeyPath().IsValid();
1746 bool IDBObjectStore::ValueWrapper::Clone(JSContext
* aCx
) {
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
)) {
1769 mValue
= clonedValue
;
1776 } // namespace mozilla::dom