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/. */
6
7 #include "IDBObjectStore.h"
8
9 #include <numeric>
10 #include <utility>
11
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 "IDBMutableFile.h"
19 #include "IDBRequest.h"
20 #include "IDBTransaction.h"
21 #include "IndexedDatabase.h"
22 #include "IndexedDatabaseInlines.h"
23 #include "IndexedDatabaseManager.h"
24 #include "IndexedDBCommon.h"
25 #include "KeyPath.h"
26 #include "ProfilerHelpers.h"
27 #include "ReportInternalError.h"
28 #include "js/Array.h" // JS::GetArrayLength, JS::IsArrayObject
29 #include "js/Class.h"
30 #include "js/Date.h"
31 #include "js/Object.h" // JS::GetClass
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/File.h"
39 #include "mozilla/dom/IDBMutableFileBinding.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"
51
52 // Include this last to avoid path problems on Windows.
53 #include "ActorsChild.h"
54
55 namespace mozilla::dom {
56
57 using namespace mozilla::dom::indexedDB;
58 using namespace mozilla::dom::quota;
59 using namespace mozilla::ipc;
60
61 namespace {
62
MakeIndexUpdateInfo(const int64_t aIndexID,const Key & aKey,const nsCString & aLocale)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));
71 }
72 return indexUpdateInfo;
73 }
74
75 } // namespace
76
77 struct IDBObjectStore::StructuredCloneWriteInfo {
78 JSAutoStructuredCloneBuffer mCloneBuffer;
79 nsTArray<StructuredCloneFileChild> mFiles;
80 IDBDatabase* mDatabase;
81 uint64_t mOffsetToKeyProp;
82
StructuredCloneWriteInfomozilla::dom::IDBObjectStore::StructuredCloneWriteInfo83 explicit StructuredCloneWriteInfo(IDBDatabase* aDatabase)
84 : mCloneBuffer(JS::StructuredCloneScope::DifferentProcessForIndexedDB,
85 nullptr, nullptr),
86 mDatabase(aDatabase),
87 mOffsetToKeyProp(0) {
88 MOZ_ASSERT(aDatabase);
89
90 MOZ_COUNT_CTOR(StructuredCloneWriteInfo);
91 }
92
StructuredCloneWriteInfomozilla::dom::IDBObjectStore::StructuredCloneWriteInfo93 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);
99
100 MOZ_COUNT_CTOR(StructuredCloneWriteInfo);
101
102 aCloneWriteInfo.mOffsetToKeyProp = 0;
103 }
104
105 MOZ_COUNTED_DTOR(StructuredCloneWriteInfo)
106 };
107
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;
118 };
119
120 namespace {
121
122 struct MOZ_STACK_CLASS GetAddInfoClosure final {
123 IDBObjectStore::StructuredCloneWriteInfo& mCloneWriteInfo;
124 JS::Handle<JS::Value> mValue;
125
GetAddInfoClosuremozilla::dom::__anonf0fd2b3d0211::GetAddInfoClosure126 GetAddInfoClosure(IDBObjectStore::StructuredCloneWriteInfo& aCloneWriteInfo,
127 JS::Handle<JS::Value> aValue)
128 : mCloneWriteInfo(aCloneWriteInfo), mValue(aValue) {
129 MOZ_COUNT_CTOR(GetAddInfoClosure);
130 }
131
132 MOZ_COUNTED_DTOR(GetAddInfoClosure)
133 };
134
GenerateRequest(JSContext * aCx,IDBObjectStore * aObjectStore)135 MovingNotNull<RefPtr<IDBRequest>> GenerateRequest(
136 JSContext* aCx, IDBObjectStore* aObjectStore) {
137 MOZ_ASSERT(aObjectStore);
138 aObjectStore->AssertIsOnOwningThread();
139
140 auto transaction = aObjectStore->AcquireTransaction();
141 auto* const database = transaction->Database();
142
143 return IDBRequest::Create(aCx, aObjectStore, database,
144 std::move(transaction));
145 }
146
StructuredCloneWriteCallback(JSContext * aCx,JSStructuredCloneWriter * aWriter,JS::Handle<JSObject * > aObj,bool * aSameProcessRequired,void * aClosure)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);
154
155 auto* const cloneWriteInfo =
156 static_cast<IDBObjectStore::StructuredCloneWriteInfo*>(aClosure);
157
158 if (JS::GetClass(aObj) == IDBObjectStore::DummyPropClass()) {
159 MOZ_ASSERT(!cloneWriteInfo->mOffsetToKeyProp);
160 cloneWriteInfo->mOffsetToKeyProp = js::GetSCOffset(aWriter);
161
162 uint64_t value = 0;
163 // Omit endian swap
164 return JS_WriteBytes(aWriter, &value, sizeof(value));
165 }
166
167 // UNWRAP_OBJECT calls might mutate this.
168 JS::Rooted<JSObject*> obj(aCx, aObj);
169
170 IDBMutableFile* mutableFile;
171 if (NS_SUCCEEDED(UNWRAP_OBJECT(IDBMutableFile, &obj, mutableFile))) {
172 if (cloneWriteInfo->mDatabase->IsFileHandleDisabled()) {
173 return false;
174 }
175
176 IDBDatabase* const database = mutableFile->Database();
177 MOZ_ASSERT(database);
178
179 // Throw when trying to store IDBMutableFile objects that live in a
180 // different database.
181 if (database != cloneWriteInfo->mDatabase) {
182 MOZ_ASSERT(!SameCOMIdentity(database, cloneWriteInfo->mDatabase));
183
184 if (database->Name() != cloneWriteInfo->mDatabase->Name()) {
185 return false;
186 }
187
188 nsCString fileOrigin, databaseOrigin;
189 PersistenceType filePersistenceType, databasePersistenceType;
190
191 if (NS_WARN_IF(NS_FAILED(
192 database->GetQuotaInfo(fileOrigin, &filePersistenceType)))) {
193 return false;
194 }
195
196 if (NS_WARN_IF(NS_FAILED(cloneWriteInfo->mDatabase->GetQuotaInfo(
197 databaseOrigin, &databasePersistenceType)))) {
198 return false;
199 }
200
201 if (filePersistenceType != databasePersistenceType ||
202 fileOrigin != databaseOrigin) {
203 return false;
204 }
205 }
206
207 if (cloneWriteInfo->mFiles.Length() > size_t(UINT32_MAX)) {
208 MOZ_ASSERT(false, "Fix the structured clone data to use a bigger type!");
209 return false;
210 }
211
212 const uint32_t index = cloneWriteInfo->mFiles.Length();
213
214 const NS_ConvertUTF16toUTF8 convType(mutableFile->Type());
215 const uint32_t convTypeLength =
216 NativeEndian::swapToLittleEndian(convType.Length());
217
218 const NS_ConvertUTF16toUTF8 convName(mutableFile->Name());
219 const uint32_t convNameLength =
220 NativeEndian::swapToLittleEndian(convName.Length());
221
222 if (!JS_WriteUint32Pair(aWriter, SCTAG_DOM_MUTABLEFILE, uint32_t(index)) ||
223 !JS_WriteBytes(aWriter, &convTypeLength, sizeof(uint32_t)) ||
224 !JS_WriteBytes(aWriter, convType.get(), convType.Length()) ||
225 !JS_WriteBytes(aWriter, &convNameLength, sizeof(uint32_t)) ||
226 !JS_WriteBytes(aWriter, convName.get(), convName.Length())) {
227 return false;
228 }
229
230 cloneWriteInfo->mFiles.EmplaceBack(mutableFile);
231
232 return true;
233 }
234
235 {
236 Blob* blob = nullptr;
237 if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, &obj, blob))) {
238 ErrorResult rv;
239 const uint64_t nativeEndianSize = blob->GetSize(rv);
240 MOZ_ASSERT(!rv.Failed());
241
242 const uint64_t size = NativeEndian::swapToLittleEndian(nativeEndianSize);
243
244 nsString type;
245 blob->GetType(type);
246
247 const NS_ConvertUTF16toUTF8 convType(type);
248 const uint32_t convTypeLength =
249 NativeEndian::swapToLittleEndian(convType.Length());
250
251 if (cloneWriteInfo->mFiles.Length() > size_t(UINT32_MAX)) {
252 MOZ_ASSERT(false,
253 "Fix the structured clone data to use a bigger type!");
254 return false;
255 }
256
257 const uint32_t index = cloneWriteInfo->mFiles.Length();
258
259 if (!JS_WriteUint32Pair(aWriter,
260 blob->IsFile() ? SCTAG_DOM_FILE : SCTAG_DOM_BLOB,
261 index) ||
262 !JS_WriteBytes(aWriter, &size, sizeof(size)) ||
263 !JS_WriteBytes(aWriter, &convTypeLength, sizeof(convTypeLength)) ||
264 !JS_WriteBytes(aWriter, convType.get(), convType.Length())) {
265 return false;
266 }
267
268 const RefPtr<File> file = blob->ToFile();
269 if (file) {
270 ErrorResult rv;
271 const int64_t nativeEndianLastModifiedDate = file->GetLastModified(rv);
272 MOZ_ALWAYS_TRUE(!rv.Failed());
273
274 const int64_t lastModifiedDate =
275 NativeEndian::swapToLittleEndian(nativeEndianLastModifiedDate);
276
277 nsString name;
278 file->GetName(name);
279
280 const NS_ConvertUTF16toUTF8 convName(name);
281 const uint32_t convNameLength =
282 NativeEndian::swapToLittleEndian(convName.Length());
283
284 if (!JS_WriteBytes(aWriter, &lastModifiedDate,
285 sizeof(lastModifiedDate)) ||
286 !JS_WriteBytes(aWriter, &convNameLength, sizeof(convNameLength)) ||
287 !JS_WriteBytes(aWriter, convName.get(), convName.Length())) {
288 return false;
289 }
290 }
291
292 cloneWriteInfo->mFiles.EmplaceBack(StructuredCloneFileBase::eBlob, blob);
293
294 return true;
295 }
296 }
297
298 return StructuredCloneHolder::WriteFullySerializableObjects(aCx, aWriter,
299 aObj);
300 }
301
CopyingStructuredCloneWriteCallback(JSContext * aCx,JSStructuredCloneWriter * aWriter,JS::Handle<JSObject * > aObj,bool * aSameProcessRequired,void * aClosure)302 bool CopyingStructuredCloneWriteCallback(JSContext* aCx,
303 JSStructuredCloneWriter* aWriter,
304 JS::Handle<JSObject*> aObj,
305 bool* aSameProcessRequired,
306 void* aClosure) {
307 MOZ_ASSERT(aCx);
308 MOZ_ASSERT(aWriter);
309 MOZ_ASSERT(aClosure);
310
311 auto* const cloneInfo =
312 static_cast<IDBObjectStore::StructuredCloneInfo*>(aClosure);
313
314 // UNWRAP_OBJECT calls might mutate this.
315 JS::Rooted<JSObject*> obj(aCx, aObj);
316
317 {
318 Blob* blob = nullptr;
319 if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, &obj, blob))) {
320 if (cloneInfo->mFiles.Length() > size_t(UINT32_MAX)) {
321 MOZ_ASSERT(false,
322 "Fix the structured clone data to use a bigger type!");
323 return false;
324 }
325
326 const uint32_t index = cloneInfo->mFiles.Length();
327
328 if (!JS_WriteUint32Pair(aWriter,
329 blob->IsFile() ? SCTAG_DOM_FILE : SCTAG_DOM_BLOB,
330 index)) {
331 return false;
332 }
333
334 cloneInfo->mFiles.EmplaceBack(StructuredCloneFileBase::eBlob, blob);
335
336 return true;
337 }
338 }
339
340 {
341 IDBMutableFile* mutableFile;
342 if (NS_SUCCEEDED(UNWRAP_OBJECT(IDBMutableFile, &obj, mutableFile))) {
343 if (cloneInfo->mFiles.Length() > size_t(UINT32_MAX)) {
344 MOZ_ASSERT(false,
345 "Fix the structured clone data to use a bigger type!");
346 return false;
347 }
348
349 const uint32_t index = cloneInfo->mFiles.Length();
350
351 if (!JS_WriteUint32Pair(aWriter, SCTAG_DOM_MUTABLEFILE, index)) {
352 return false;
353 }
354
355 cloneInfo->mFiles.EmplaceBack(mutableFile);
356
357 return true;
358 }
359 }
360
361 return StructuredCloneHolder::WriteFullySerializableObjects(aCx, aWriter,
362 aObj);
363 }
364
GetAddInfoCallback(JSContext * aCx,void * aClosure)365 nsresult GetAddInfoCallback(JSContext* aCx, void* aClosure) {
366 static const JSStructuredCloneCallbacks kStructuredCloneCallbacks = {
367 nullptr /* read */, StructuredCloneWriteCallback /* write */,
368 nullptr /* reportError */, nullptr /* readTransfer */,
369 nullptr /* writeTransfer */, nullptr /* freeTransfer */,
370 nullptr /* canTransfer */, nullptr /* sabCloned */
371 };
372
373 MOZ_ASSERT(aCx);
374
375 auto* const data = static_cast<GetAddInfoClosure*>(aClosure);
376 MOZ_ASSERT(data);
377
378 data->mCloneWriteInfo.mOffsetToKeyProp = 0;
379
380 if (!data->mCloneWriteInfo.mCloneBuffer.write(aCx, data->mValue,
381 &kStructuredCloneCallbacks,
382 &data->mCloneWriteInfo)) {
383 return NS_ERROR_DOM_DATA_CLONE_ERR;
384 }
385
386 return NS_OK;
387 }
388
389 using indexedDB::WrapAsJSObject;
390
391 template <typename T>
WrapAsJSObject(JSContext * const aCx,T & aBaseObject)392 JSObject* WrapAsJSObject(JSContext* const aCx, T& aBaseObject) {
393 JS::Rooted<JSObject*> result(aCx);
394 const bool res = WrapAsJSObject(aCx, aBaseObject, &result);
395 return res ? static_cast<JSObject*>(result) : nullptr;
396 }
397
CopyingStructuredCloneReadCallback(JSContext * aCx,JSStructuredCloneReader * aReader,const JS::CloneDataPolicy & aCloneDataPolicy,uint32_t aTag,uint32_t aData,void * aClosure)398 JSObject* CopyingStructuredCloneReadCallback(
399 JSContext* aCx, JSStructuredCloneReader* aReader,
400 const JS::CloneDataPolicy& aCloneDataPolicy, uint32_t aTag, uint32_t aData,
401 void* aClosure) {
402 MOZ_ASSERT(aTag != SCTAG_DOM_FILE_WITHOUT_LASTMODIFIEDDATE);
403
404 if (aTag == SCTAG_DOM_BLOB || aTag == SCTAG_DOM_FILE ||
405 aTag == SCTAG_DOM_MUTABLEFILE) {
406 auto* const cloneInfo =
407 static_cast<IDBObjectStore::StructuredCloneInfo*>(aClosure);
408
409 if (aData >= cloneInfo->mFiles.Length()) {
410 MOZ_ASSERT(false, "Bad index value!");
411 return nullptr;
412 }
413
414 StructuredCloneFileChild& file = cloneInfo->mFiles[aData];
415
416 switch (static_cast<StructuredCloneTags>(aTag)) {
417 case SCTAG_DOM_BLOB:
418 MOZ_ASSERT(file.Type() == StructuredCloneFileBase::eBlob);
419 MOZ_ASSERT(!file.Blob().IsFile());
420
421 return WrapAsJSObject(aCx, file.MutableBlob());
422
423 case SCTAG_DOM_FILE: {
424 MOZ_ASSERT(file.Type() == StructuredCloneFileBase::eBlob);
425
426 JS::Rooted<JSObject*> result(aCx);
427
428 {
429 // Create a scope so ~RefPtr fires before returning an unwrapped
430 // JS::Value.
431 const RefPtr<Blob> blob = file.BlobPtr();
432 MOZ_ASSERT(blob->IsFile());
433
434 const RefPtr<File> file = blob->ToFile();
435 MOZ_ASSERT(file);
436
437 if (!WrapAsJSObject(aCx, file, &result)) {
438 return nullptr;
439 }
440 }
441
442 return result;
443 }
444
445 case SCTAG_DOM_MUTABLEFILE:
446 MOZ_ASSERT(file.Type() == StructuredCloneFileBase::eMutableFile);
447
448 return WrapAsJSObject(aCx, file.MutableMutableFile());
449
450 default:
451 // This cannot be reached due to the if condition before.
452 break;
453 }
454 }
455
456 return StructuredCloneHolder::ReadFullySerializableObjects(aCx, aReader,
457 aTag);
458 }
459
460 } // namespace
461
462 const JSClass IDBObjectStore::sDummyPropJSClass = {
463 "IDBObjectStore Dummy", 0 /* flags */
464 };
465
IDBObjectStore(SafeRefPtr<IDBTransaction> aTransaction,ObjectStoreSpec * aSpec)466 IDBObjectStore::IDBObjectStore(SafeRefPtr<IDBTransaction> aTransaction,
467 ObjectStoreSpec* aSpec)
468 : mTransaction(std::move(aTransaction)),
469 mCachedKeyPath(JS::UndefinedValue()),
470 mSpec(aSpec),
471 mId(aSpec->metadata().id()),
472 mRooted(false) {
473 MOZ_ASSERT(mTransaction);
474 mTransaction->AssertIsOnOwningThread();
475 MOZ_ASSERT(aSpec);
476 }
477
~IDBObjectStore()478 IDBObjectStore::~IDBObjectStore() {
479 AssertIsOnOwningThread();
480
481 if (mRooted) {
482 mozilla::DropJSObjects(this);
483 }
484 }
485
486 // static
Create(SafeRefPtr<IDBTransaction> aTransaction,ObjectStoreSpec & aSpec)487 RefPtr<IDBObjectStore> IDBObjectStore::Create(
488 SafeRefPtr<IDBTransaction> aTransaction, ObjectStoreSpec& aSpec) {
489 MOZ_ASSERT(aTransaction);
490 aTransaction->AssertIsOnOwningThread();
491
492 return new IDBObjectStore(std::move(aTransaction), &aSpec);
493 }
494
495 // static
AppendIndexUpdateInfo(const int64_t aIndexID,const KeyPath & aKeyPath,const bool aMultiEntry,const nsCString & aLocale,JSContext * const aCx,JS::Handle<JS::Value> aVal,nsTArray<IndexUpdateInfo> * const aUpdateInfoArray,ErrorResult * const aRv)496 void IDBObjectStore::AppendIndexUpdateInfo(
497 const int64_t aIndexID, const KeyPath& aKeyPath, const bool aMultiEntry,
498 const nsCString& aLocale, JSContext* const aCx, JS::Handle<JS::Value> aVal,
499 nsTArray<IndexUpdateInfo>* const aUpdateInfoArray, ErrorResult* const aRv) {
500 // This precondition holds when `aVal` is the result of a structured clone.
501 js::AutoAssertNoContentJS noContentJS(aCx);
502
503 if (!aMultiEntry) {
504 Key key;
505 *aRv = aKeyPath.ExtractKey(aCx, aVal, key);
506
507 // If an index's keyPath doesn't match an object, we ignore that object.
508 if (aRv->ErrorCodeIs(NS_ERROR_DOM_INDEXEDDB_DATA_ERR) || key.IsUnset()) {
509 aRv->SuppressException();
510 return;
511 }
512
513 if (aRv->Failed()) {
514 return;
515 }
516
517 QM_TRY_UNWRAP(auto item, MakeIndexUpdateInfo(aIndexID, key, aLocale),
518 QM_VOID,
519 [aRv](const nsresult tryResult) { aRv->Throw(tryResult); });
520
521 aUpdateInfoArray->AppendElement(std::move(item));
522 return;
523 }
524
525 JS::Rooted<JS::Value> val(aCx);
526 if (NS_FAILED(aKeyPath.ExtractKeyAsJSVal(aCx, aVal, val.address()))) {
527 return;
528 }
529
530 bool isArray;
531 if (NS_WARN_IF(!JS::IsArrayObject(aCx, val, &isArray))) {
532 IDB_REPORT_INTERNAL_ERR();
533 aRv->Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
534 return;
535 }
536 if (isArray) {
537 JS::Rooted<JSObject*> array(aCx, &val.toObject());
538 uint32_t arrayLength;
539 if (NS_WARN_IF(!JS::GetArrayLength(aCx, array, &arrayLength))) {
540 IDB_REPORT_INTERNAL_ERR();
541 aRv->Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
542 return;
543 }
544
545 for (uint32_t arrayIndex = 0; arrayIndex < arrayLength; arrayIndex++) {
546 JS::RootedId indexId(aCx);
547 if (NS_WARN_IF(!JS_IndexToId(aCx, arrayIndex, &indexId))) {
548 IDB_REPORT_INTERNAL_ERR();
549 aRv->Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
550 return;
551 }
552
553 bool hasOwnProperty;
554 if (NS_WARN_IF(
555 !JS_HasOwnPropertyById(aCx, array, indexId, &hasOwnProperty))) {
556 IDB_REPORT_INTERNAL_ERR();
557 aRv->Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
558 return;
559 }
560
561 if (!hasOwnProperty) {
562 continue;
563 }
564
565 JS::RootedValue arrayItem(aCx);
566 if (NS_WARN_IF(!JS_GetPropertyById(aCx, array, indexId, &arrayItem))) {
567 IDB_REPORT_INTERNAL_ERR();
568 aRv->Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
569 return;
570 }
571
572 Key value;
573 auto result = value.SetFromJSVal(aCx, arrayItem);
574 if (result.isErr() || value.IsUnset()) {
575 // Not a value we can do anything with, ignore it.
576 if (result.isErr() &&
577 result.inspectErr().Is(SpecialValues::Exception)) {
578 result.unwrapErr().AsException().SuppressException();
579 }
580 continue;
581 }
582
583 QM_TRY_UNWRAP(auto item, MakeIndexUpdateInfo(aIndexID, value, aLocale),
584 QM_VOID,
585 [aRv](const nsresult tryResult) { aRv->Throw(tryResult); });
586
587 aUpdateInfoArray->AppendElement(std::move(item));
588 }
589 } else {
590 Key value;
591 auto result = value.SetFromJSVal(aCx, val);
592 if (result.isErr() || value.IsUnset()) {
593 // Not a value we can do anything with, ignore it.
594 if (result.isErr() && result.inspectErr().Is(SpecialValues::Exception)) {
595 result.unwrapErr().AsException().SuppressException();
596 }
597 return;
598 }
599
600 QM_TRY_UNWRAP(auto item, MakeIndexUpdateInfo(aIndexID, value, aLocale),
601 QM_VOID,
602 [aRv](const nsresult tryResult) { aRv->Throw(tryResult); });
603
604 aUpdateInfoArray->AppendElement(std::move(item));
605 }
606 }
607
608 // static
ClearCloneReadInfo(StructuredCloneReadInfoChild & aReadInfo)609 void IDBObjectStore::ClearCloneReadInfo(
610 StructuredCloneReadInfoChild& aReadInfo) {
611 // This is kind of tricky, we only want to release stuff on the main thread,
612 // but we can end up being called on other threads if we have already been
613 // cleared on the main thread.
614 if (!aReadInfo.HasFiles()) {
615 return;
616 }
617
618 aReadInfo.ReleaseFiles();
619 }
620
621 // static
DeserializeValue(JSContext * aCx,StructuredCloneReadInfoChild && aCloneReadInfo,JS::MutableHandle<JS::Value> aValue)622 bool IDBObjectStore::DeserializeValue(
623 JSContext* aCx, StructuredCloneReadInfoChild&& aCloneReadInfo,
624 JS::MutableHandle<JS::Value> aValue) {
625 MOZ_ASSERT(aCx);
626
627 if (!aCloneReadInfo.Data().Size()) {
628 aValue.setUndefined();
629 return true;
630 }
631
632 MOZ_ASSERT(!(aCloneReadInfo.Data().Size() % sizeof(uint64_t)));
633
634 static const JSStructuredCloneCallbacks callbacks = {
635 StructuredCloneReadCallback<StructuredCloneReadInfoChild>,
636 nullptr,
637 nullptr,
638 nullptr,
639 nullptr,
640 nullptr,
641 nullptr,
642 nullptr};
643
644 // FIXME: Consider to use StructuredCloneHolder here and in other
645 // deserializing methods.
646 return JS_ReadStructuredClone(
647 aCx, aCloneReadInfo.Data(), JS_STRUCTURED_CLONE_VERSION,
648 JS::StructuredCloneScope::DifferentProcessForIndexedDB, aValue,
649 JS::CloneDataPolicy(), &callbacks, &aCloneReadInfo);
650 }
651
652 #ifdef DEBUG
653
AssertIsOnOwningThread() const654 void IDBObjectStore::AssertIsOnOwningThread() const {
655 MOZ_ASSERT(mTransaction);
656 mTransaction->AssertIsOnOwningThread();
657 }
658
659 #endif // DEBUG
660
GetAddInfo(JSContext * aCx,ValueWrapper & aValueWrapper,JS::Handle<JS::Value> aKeyVal,StructuredCloneWriteInfo & aCloneWriteInfo,Key & aKey,nsTArray<IndexUpdateInfo> & aUpdateInfoArray,ErrorResult & aRv)661 void IDBObjectStore::GetAddInfo(JSContext* aCx, ValueWrapper& aValueWrapper,
662 JS::Handle<JS::Value> aKeyVal,
663 StructuredCloneWriteInfo& aCloneWriteInfo,
664 Key& aKey,
665 nsTArray<IndexUpdateInfo>& aUpdateInfoArray,
666 ErrorResult& aRv) {
667 // Return DATA_ERR if a key was passed in and this objectStore uses inline
668 // keys.
669 if (!aKeyVal.isUndefined() && HasValidKeyPath()) {
670 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_DATA_ERR);
671 return;
672 }
673
674 const bool isAutoIncrement = AutoIncrement();
675
676 if (!HasValidKeyPath()) {
677 // Out-of-line keys must be passed in.
678 auto result = aKey.SetFromJSVal(aCx, aKeyVal);
679 if (result.isErr()) {
680 aRv = result.unwrapErr().ExtractErrorResult(
681 InvalidMapsTo<NS_ERROR_DOM_INDEXEDDB_DATA_ERR>);
682 return;
683 }
684 } else if (!isAutoIncrement) {
685 if (!aValueWrapper.Clone(aCx)) {
686 aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
687 return;
688 }
689
690 aRv = GetKeyPath().ExtractKey(aCx, aValueWrapper.Value(), aKey);
691 if (aRv.Failed()) {
692 return;
693 }
694 }
695
696 // Return DATA_ERR if no key was specified this isn't an autoIncrement
697 // objectStore.
698 if (aKey.IsUnset() && !isAutoIncrement) {
699 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_DATA_ERR);
700 return;
701 }
702
703 // Figure out indexes and the index values to update here.
704
705 if (mSpec->indexes().Length() && !aValueWrapper.Clone(aCx)) {
706 aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
707 return;
708 }
709
710 {
711 const nsTArray<IndexMetadata>& indexes = mSpec->indexes();
712 const uint32_t idxCount = indexes.Length();
713
714 aUpdateInfoArray.SetCapacity(idxCount); // Pretty good estimate
715
716 for (uint32_t idxIndex = 0; idxIndex < idxCount; idxIndex++) {
717 const IndexMetadata& metadata = indexes[idxIndex];
718
719 AppendIndexUpdateInfo(metadata.id(), metadata.keyPath(),
720 metadata.multiEntry(), metadata.locale(), aCx,
721 aValueWrapper.Value(), &aUpdateInfoArray, &aRv);
722 if (NS_WARN_IF(aRv.Failed())) {
723 return;
724 }
725 }
726 }
727
728 if (isAutoIncrement && HasValidKeyPath()) {
729 if (!aValueWrapper.Clone(aCx)) {
730 aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
731 return;
732 }
733
734 GetAddInfoClosure data(aCloneWriteInfo, aValueWrapper.Value());
735
736 MOZ_ASSERT(aKey.IsUnset());
737
738 aRv = GetKeyPath().ExtractOrCreateKey(aCx, aValueWrapper.Value(), aKey,
739 &GetAddInfoCallback, &data);
740 } else {
741 GetAddInfoClosure data(aCloneWriteInfo, aValueWrapper.Value());
742
743 aRv = GetAddInfoCallback(aCx, &data);
744 }
745 }
746
AddOrPut(JSContext * aCx,ValueWrapper & aValueWrapper,JS::Handle<JS::Value> aKey,bool aOverwrite,bool aFromCursor,ErrorResult & aRv)747 RefPtr<IDBRequest> IDBObjectStore::AddOrPut(JSContext* aCx,
748 ValueWrapper& aValueWrapper,
749 JS::Handle<JS::Value> aKey,
750 bool aOverwrite, bool aFromCursor,
751 ErrorResult& aRv) {
752 AssertIsOnOwningThread();
753 MOZ_ASSERT(aCx);
754 MOZ_ASSERT_IF(aFromCursor, aOverwrite);
755
756 if (mTransaction->GetMode() == IDBTransaction::Mode::Cleanup ||
757 mDeletedSpec) {
758 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
759 return nullptr;
760 }
761
762 if (!mTransaction->IsActive()) {
763 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
764 return nullptr;
765 }
766
767 if (!mTransaction->IsWriteAllowed()) {
768 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_READ_ONLY_ERR);
769 return nullptr;
770 }
771
772 Key key;
773 StructuredCloneWriteInfo cloneWriteInfo(mTransaction->Database());
774 nsTArray<IndexUpdateInfo> updateInfos;
775
776 {
777 const auto autoStateRestore =
778 mTransaction->TemporarilyTransitionToInactive();
779 GetAddInfo(aCx, aValueWrapper, aKey, cloneWriteInfo, key, updateInfos, aRv);
780 }
781
782 if (aRv.Failed()) {
783 return nullptr;
784 }
785
786 if (!mTransaction->IsActive()) {
787 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
788 return nullptr;
789 }
790
791 // Check the size limit of the serialized message which mainly consists of
792 // a StructuredCloneBuffer, an encoded object key, and the encoded index keys.
793 // kMaxIDBMsgOverhead covers the minor stuff not included in this calculation
794 // because the precise calculation would slow down this AddOrPut operation.
795 static const size_t kMaxIDBMsgOverhead = 1024 * 1024; // 1MB
796 const uint32_t maximalSizeFromPref =
797 IndexedDatabaseManager::MaxSerializedMsgSize();
798 MOZ_ASSERT(maximalSizeFromPref > kMaxIDBMsgOverhead);
799 const size_t kMaxMessageSize = maximalSizeFromPref - kMaxIDBMsgOverhead;
800
801 const size_t indexUpdateInfoSize =
802 std::accumulate(updateInfos.cbegin(), updateInfos.cend(), 0u,
803 [](size_t old, const IndexUpdateInfo& updateInfo) {
804 return old + updateInfo.value().GetBuffer().Length() +
805 updateInfo.localizedValue().GetBuffer().Length();
806 });
807
808 const size_t messageSize = cloneWriteInfo.mCloneBuffer.data().Size() +
809 key.GetBuffer().Length() + indexUpdateInfoSize;
810
811 if (messageSize > kMaxMessageSize) {
812 IDB_REPORT_INTERNAL_ERR();
813 aRv.ThrowUnknownError(
814 nsPrintfCString("The serialized value is too large"
815 " (size=%zu bytes, max=%zu bytes).",
816 messageSize, kMaxMessageSize));
817 return nullptr;
818 }
819
820 ObjectStoreAddPutParams commonParams;
821 commonParams.objectStoreId() = Id();
822 commonParams.cloneInfo().data().data =
823 std::move(cloneWriteInfo.mCloneBuffer.data());
824 commonParams.cloneInfo().offsetToKeyProp() = cloneWriteInfo.mOffsetToKeyProp;
825 commonParams.key() = key;
826 commonParams.indexUpdateInfos() = std::move(updateInfos);
827
828 // Convert any blobs or mutable files into FileAddInfo.
829 QM_TRY_UNWRAP(
830 commonParams.fileAddInfos(),
831 TransformIntoNewArrayAbortOnErr(
832 cloneWriteInfo.mFiles,
833 [&database = *mTransaction->Database()](
834 auto& file) -> Result<FileAddInfo, nsresult> {
835 switch (file.Type()) {
836 case StructuredCloneFileBase::eBlob: {
837 MOZ_ASSERT(file.HasBlob());
838 MOZ_ASSERT(!file.HasMutableFile());
839
840 PBackgroundIDBDatabaseFileChild* const fileActor =
841 database.GetOrCreateFileActorForBlob(file.MutableBlob());
842 if (NS_WARN_IF(!fileActor)) {
843 IDB_REPORT_INTERNAL_ERR();
844 return Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
845 }
846
847 return FileAddInfo{fileActor, StructuredCloneFileBase::eBlob};
848 }
849
850 case StructuredCloneFileBase::eMutableFile: {
851 MOZ_ASSERT(file.HasMutableFile());
852 MOZ_ASSERT(!file.HasBlob());
853
854 PBackgroundMutableFileChild* const mutableFileActor =
855 file.MutableFile().GetBackgroundActor();
856 if (NS_WARN_IF(!mutableFileActor)) {
857 IDB_REPORT_INTERNAL_ERR();
858 return Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
859 }
860
861 return FileAddInfo{mutableFileActor,
862 StructuredCloneFileBase::eMutableFile};
863 }
864
865 case StructuredCloneFileBase::eWasmBytecode:
866 case StructuredCloneFileBase::eWasmCompiled: {
867 MOZ_ASSERT(file.HasBlob());
868 MOZ_ASSERT(!file.HasMutableFile());
869
870 PBackgroundIDBDatabaseFileChild* const fileActor =
871 database.GetOrCreateFileActorForBlob(file.MutableBlob());
872 if (NS_WARN_IF(!fileActor)) {
873 IDB_REPORT_INTERNAL_ERR();
874 return Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
875 }
876
877 return FileAddInfo{fileActor, file.Type()};
878 }
879
880 default:
881 MOZ_CRASH("Should never get here!");
882 }
883 },
884 fallible),
885 nullptr, [&aRv](const nsresult result) { aRv = result; });
886
887 const auto& params =
888 aOverwrite ? RequestParams{ObjectStorePutParams(std::move(commonParams))}
889 : RequestParams{ObjectStoreAddParams(std::move(commonParams))};
890
891 auto request = GenerateRequest(aCx, this).unwrap();
892
893 if (!aFromCursor) {
894 if (aOverwrite) {
895 IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
896 "database(%s).transaction(%s).objectStore(%s).put(%s)",
897 "IDBObjectStore.put(%.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(key));
902 } else {
903 IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
904 "database(%s).transaction(%s).objectStore(%s).add(%s)",
905 "IDBObjectStore.add(%.0s%.0s%.0s%.0s)",
906 mTransaction->LoggingSerialNumber(), request->LoggingSerialNumber(),
907 IDB_LOG_STRINGIFY(mTransaction->Database()),
908 IDB_LOG_STRINGIFY(*mTransaction), IDB_LOG_STRINGIFY(this),
909 IDB_LOG_STRINGIFY(key));
910 }
911 }
912
913 mTransaction->StartRequest(request, params);
914
915 mTransaction->InvalidateCursorCaches();
916
917 return request;
918 }
919
GetAllInternal(bool aKeysOnly,JSContext * aCx,JS::Handle<JS::Value> aKey,const Optional<uint32_t> & aLimit,ErrorResult & aRv)920 RefPtr<IDBRequest> IDBObjectStore::GetAllInternal(
921 bool aKeysOnly, JSContext* aCx, JS::Handle<JS::Value> aKey,
922 const Optional<uint32_t>& aLimit, ErrorResult& aRv) {
923 AssertIsOnOwningThread();
924
925 if (mDeletedSpec) {
926 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
927 return nullptr;
928 }
929
930 if (!mTransaction->IsActive()) {
931 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
932 return nullptr;
933 }
934
935 RefPtr<IDBKeyRange> keyRange;
936 IDBKeyRange::FromJSVal(aCx, aKey, &keyRange, aRv);
937 if (NS_WARN_IF(aRv.Failed())) {
938 return nullptr;
939 }
940
941 const int64_t id = Id();
942
943 Maybe<SerializedKeyRange> optionalKeyRange;
944 if (keyRange) {
945 SerializedKeyRange serializedKeyRange;
946 keyRange->ToSerialized(serializedKeyRange);
947 optionalKeyRange.emplace(serializedKeyRange);
948 }
949
950 const uint32_t limit = aLimit.WasPassed() ? aLimit.Value() : 0;
951
952 RequestParams params;
953 if (aKeysOnly) {
954 params = ObjectStoreGetAllKeysParams(id, optionalKeyRange, limit);
955 } else {
956 params = ObjectStoreGetAllParams(id, optionalKeyRange, limit);
957 }
958
959 auto request = GenerateRequest(aCx, this).unwrap();
960
961 if (aKeysOnly) {
962 IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
963 "database(%s).transaction(%s).objectStore(%s)."
964 "getAllKeys(%s, %s)",
965 "IDBObjectStore.getAllKeys(%.0s%.0s%.0s%.0s%.0s)",
966 mTransaction->LoggingSerialNumber(), request->LoggingSerialNumber(),
967 IDB_LOG_STRINGIFY(mTransaction->Database()),
968 IDB_LOG_STRINGIFY(*mTransaction), IDB_LOG_STRINGIFY(this),
969 IDB_LOG_STRINGIFY(keyRange), IDB_LOG_STRINGIFY(aLimit));
970 } else {
971 IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
972 "database(%s).transaction(%s).objectStore(%s)."
973 "getAll(%s, %s)",
974 "IDBObjectStore.getAll(%.0s%.0s%.0s%.0s%.0s)",
975 mTransaction->LoggingSerialNumber(), request->LoggingSerialNumber(),
976 IDB_LOG_STRINGIFY(mTransaction->Database()),
977 IDB_LOG_STRINGIFY(*mTransaction), IDB_LOG_STRINGIFY(this),
978 IDB_LOG_STRINGIFY(keyRange), IDB_LOG_STRINGIFY(aLimit));
979 }
980
981 // TODO: This is necessary to preserve request ordering only. Proper
982 // sequencing of requests should be done in a more sophisticated manner that
983 // doesn't require invalidating cursor caches (Bug 1580499).
984 mTransaction->InvalidateCursorCaches();
985
986 mTransaction->StartRequest(request, params);
987
988 return request;
989 }
990
Add(JSContext * aCx,JS::Handle<JS::Value> aValue,JS::Handle<JS::Value> aKey,ErrorResult & aRv)991 RefPtr<IDBRequest> IDBObjectStore::Add(JSContext* aCx,
992 JS::Handle<JS::Value> aValue,
993 JS::Handle<JS::Value> aKey,
994 ErrorResult& aRv) {
995 AssertIsOnOwningThread();
996
997 ValueWrapper valueWrapper(aCx, aValue);
998
999 return AddOrPut(aCx, valueWrapper, aKey, false, /* aFromCursor */ false, aRv);
1000 }
1001
Put(JSContext * aCx,JS::Handle<JS::Value> aValue,JS::Handle<JS::Value> aKey,ErrorResult & aRv)1002 RefPtr<IDBRequest> IDBObjectStore::Put(JSContext* aCx,
1003 JS::Handle<JS::Value> aValue,
1004 JS::Handle<JS::Value> aKey,
1005 ErrorResult& aRv) {
1006 AssertIsOnOwningThread();
1007
1008 ValueWrapper valueWrapper(aCx, aValue);
1009
1010 return AddOrPut(aCx, valueWrapper, aKey, true, /* aFromCursor */ false, aRv);
1011 }
1012
Delete(JSContext * aCx,JS::Handle<JS::Value> aKey,ErrorResult & aRv)1013 RefPtr<IDBRequest> IDBObjectStore::Delete(JSContext* aCx,
1014 JS::Handle<JS::Value> aKey,
1015 ErrorResult& aRv) {
1016 AssertIsOnOwningThread();
1017
1018 return DeleteInternal(aCx, aKey, /* aFromCursor */ false, aRv);
1019 }
1020
Get(JSContext * aCx,JS::Handle<JS::Value> aKey,ErrorResult & aRv)1021 RefPtr<IDBRequest> IDBObjectStore::Get(JSContext* aCx,
1022 JS::Handle<JS::Value> aKey,
1023 ErrorResult& aRv) {
1024 AssertIsOnOwningThread();
1025
1026 return GetInternal(/* aKeyOnly */ false, aCx, aKey, aRv);
1027 }
1028
GetKey(JSContext * aCx,JS::Handle<JS::Value> aKey,ErrorResult & aRv)1029 RefPtr<IDBRequest> IDBObjectStore::GetKey(JSContext* aCx,
1030 JS::Handle<JS::Value> aKey,
1031 ErrorResult& aRv) {
1032 AssertIsOnOwningThread();
1033
1034 return GetInternal(/* aKeyOnly */ true, aCx, aKey, aRv);
1035 }
1036
Clear(JSContext * aCx,ErrorResult & aRv)1037 RefPtr<IDBRequest> IDBObjectStore::Clear(JSContext* aCx, ErrorResult& aRv) {
1038 AssertIsOnOwningThread();
1039
1040 if (mDeletedSpec) {
1041 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
1042 return nullptr;
1043 }
1044
1045 if (!mTransaction->IsActive()) {
1046 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
1047 return nullptr;
1048 }
1049
1050 if (!mTransaction->IsWriteAllowed()) {
1051 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_READ_ONLY_ERR);
1052 return nullptr;
1053 }
1054
1055 const ObjectStoreClearParams params = {Id()};
1056
1057 auto request = GenerateRequest(aCx, this).unwrap();
1058
1059 IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
1060 "database(%s).transaction(%s).objectStore(%s).clear()",
1061 "IDBObjectStore.clear(%.0s%.0s%.0s)", mTransaction->LoggingSerialNumber(),
1062 request->LoggingSerialNumber(),
1063 IDB_LOG_STRINGIFY(mTransaction->Database()),
1064 IDB_LOG_STRINGIFY(*mTransaction), IDB_LOG_STRINGIFY(this));
1065
1066 mTransaction->InvalidateCursorCaches();
1067
1068 mTransaction->StartRequest(request, params);
1069
1070 return request;
1071 }
1072
GetAll(JSContext * aCx,JS::Handle<JS::Value> aKey,const Optional<uint32_t> & aLimit,ErrorResult & aRv)1073 RefPtr<IDBRequest> IDBObjectStore::GetAll(JSContext* aCx,
1074 JS::Handle<JS::Value> aKey,
1075 const Optional<uint32_t>& aLimit,
1076 ErrorResult& aRv) {
1077 AssertIsOnOwningThread();
1078
1079 return GetAllInternal(/* aKeysOnly */ false, aCx, aKey, aLimit, aRv);
1080 }
1081
GetAllKeys(JSContext * aCx,JS::Handle<JS::Value> aKey,const Optional<uint32_t> & aLimit,ErrorResult & aRv)1082 RefPtr<IDBRequest> IDBObjectStore::GetAllKeys(JSContext* aCx,
1083 JS::Handle<JS::Value> aKey,
1084 const Optional<uint32_t>& aLimit,
1085 ErrorResult& aRv) {
1086 AssertIsOnOwningThread();
1087
1088 return GetAllInternal(/* aKeysOnly */ true, aCx, aKey, aLimit, aRv);
1089 }
1090
OpenCursor(JSContext * aCx,JS::Handle<JS::Value> aRange,IDBCursorDirection aDirection,ErrorResult & aRv)1091 RefPtr<IDBRequest> IDBObjectStore::OpenCursor(JSContext* aCx,
1092 JS::Handle<JS::Value> aRange,
1093 IDBCursorDirection aDirection,
1094 ErrorResult& aRv) {
1095 AssertIsOnOwningThread();
1096
1097 return OpenCursorInternal(/* aKeysOnly */ false, aCx, aRange, aDirection,
1098 aRv);
1099 }
1100
OpenCursor(JSContext * aCx,IDBCursorDirection aDirection,ErrorResult & aRv)1101 RefPtr<IDBRequest> IDBObjectStore::OpenCursor(JSContext* aCx,
1102 IDBCursorDirection aDirection,
1103 ErrorResult& aRv) {
1104 AssertIsOnOwningThread();
1105
1106 return OpenCursorInternal(/* aKeysOnly */ false, aCx,
1107 JS::UndefinedHandleValue, aDirection, aRv);
1108 }
1109
OpenKeyCursor(JSContext * aCx,JS::Handle<JS::Value> aRange,IDBCursorDirection aDirection,ErrorResult & aRv)1110 RefPtr<IDBRequest> IDBObjectStore::OpenKeyCursor(JSContext* aCx,
1111 JS::Handle<JS::Value> aRange,
1112 IDBCursorDirection aDirection,
1113 ErrorResult& aRv) {
1114 AssertIsOnOwningThread();
1115
1116 return OpenCursorInternal(/* aKeysOnly */ true, aCx, aRange, aDirection, aRv);
1117 }
1118
Index(const nsAString & aName,ErrorResult & aRv)1119 RefPtr<IDBIndex> IDBObjectStore::Index(const nsAString& aName,
1120 ErrorResult& aRv) {
1121 AssertIsOnOwningThread();
1122
1123 if (mTransaction->IsCommittingOrFinished() || mDeletedSpec) {
1124 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1125 return nullptr;
1126 }
1127
1128 const nsTArray<IndexMetadata>& indexMetadatas = mSpec->indexes();
1129
1130 const auto endIndexMetadatas = indexMetadatas.cend();
1131 const auto foundMetadata =
1132 std::find_if(indexMetadatas.cbegin(), endIndexMetadatas,
1133 [&aName](const auto& indexMetadata) {
1134 return indexMetadata.name() == aName;
1135 });
1136
1137 if (foundMetadata == endIndexMetadatas) {
1138 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_FOUND_ERR);
1139 return nullptr;
1140 }
1141
1142 const IndexMetadata& metadata = *foundMetadata;
1143
1144 const auto endIndexes = mIndexes.cend();
1145 const auto foundIndex =
1146 std::find_if(mIndexes.cbegin(), endIndexes,
1147 [desiredId = metadata.id()](const auto& index) {
1148 return index->Id() == desiredId;
1149 });
1150
1151 RefPtr<IDBIndex> index;
1152
1153 if (foundIndex == endIndexes) {
1154 index = IDBIndex::Create(this, metadata);
1155 MOZ_ASSERT(index);
1156
1157 mIndexes.AppendElement(index);
1158 } else {
1159 index = *foundIndex;
1160 }
1161
1162 return index;
1163 }
1164
1165 NS_IMPL_CYCLE_COLLECTION_MULTI_ZONE_JSHOLDER_CLASS(IDBObjectStore)
1166
1167 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(IDBObjectStore)
1168 NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
1169 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCachedKeyPath)
1170 NS_IMPL_CYCLE_COLLECTION_TRACE_END
1171
1172 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(IDBObjectStore)
1173 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTransaction)
1174 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIndexes)
1175 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDeletedIndexes)
1176 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
1177
1178 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(IDBObjectStore)
1179 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
1180
1181 // Don't unlink mTransaction!
1182
1183 NS_IMPL_CYCLE_COLLECTION_UNLINK(mIndexes)
1184 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDeletedIndexes)
1185
1186 tmp->mCachedKeyPath.setUndefined();
1187
1188 if (tmp->mRooted) {
1189 mozilla::DropJSObjects(tmp);
1190 tmp->mRooted = false;
1191 }
1192 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
1193
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IDBObjectStore)1194 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IDBObjectStore)
1195 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
1196 NS_INTERFACE_MAP_ENTRY(nsISupports)
1197 NS_INTERFACE_MAP_END
1198
1199 NS_IMPL_CYCLE_COLLECTING_ADDREF(IDBObjectStore)
1200 NS_IMPL_CYCLE_COLLECTING_RELEASE(IDBObjectStore)
1201
1202 JSObject* IDBObjectStore::WrapObject(JSContext* aCx,
1203 JS::Handle<JSObject*> aGivenProto) {
1204 return IDBObjectStore_Binding::Wrap(aCx, this, aGivenProto);
1205 }
1206
GetParentObject() const1207 nsIGlobalObject* IDBObjectStore::GetParentObject() const {
1208 return mTransaction->GetParentObject();
1209 }
1210
GetKeyPath(JSContext * aCx,JS::MutableHandle<JS::Value> aResult,ErrorResult & aRv)1211 void IDBObjectStore::GetKeyPath(JSContext* aCx,
1212 JS::MutableHandle<JS::Value> aResult,
1213 ErrorResult& aRv) {
1214 if (!mCachedKeyPath.isUndefined()) {
1215 aResult.set(mCachedKeyPath);
1216 return;
1217 }
1218
1219 aRv = GetKeyPath().ToJSVal(aCx, mCachedKeyPath);
1220 if (NS_WARN_IF(aRv.Failed())) {
1221 return;
1222 }
1223
1224 if (mCachedKeyPath.isGCThing()) {
1225 mozilla::HoldJSObjects(this);
1226 mRooted = true;
1227 }
1228
1229 aResult.set(mCachedKeyPath);
1230 }
1231
IndexNames()1232 RefPtr<DOMStringList> IDBObjectStore::IndexNames() {
1233 AssertIsOnOwningThread();
1234
1235 return CreateSortedDOMStringList(
1236 mSpec->indexes(), [](const auto& index) { return index.name(); });
1237 }
1238
GetInternal(bool aKeyOnly,JSContext * aCx,JS::Handle<JS::Value> aKey,ErrorResult & aRv)1239 RefPtr<IDBRequest> IDBObjectStore::GetInternal(bool aKeyOnly, JSContext* aCx,
1240 JS::Handle<JS::Value> aKey,
1241 ErrorResult& aRv) {
1242 AssertIsOnOwningThread();
1243
1244 if (mDeletedSpec) {
1245 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
1246 return nullptr;
1247 }
1248
1249 if (!mTransaction->IsActive()) {
1250 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
1251 return nullptr;
1252 }
1253
1254 RefPtr<IDBKeyRange> keyRange;
1255 IDBKeyRange::FromJSVal(aCx, aKey, &keyRange, aRv);
1256 if (aRv.Failed()) {
1257 return nullptr;
1258 }
1259
1260 if (!keyRange) {
1261 // Must specify a key or keyRange for get().
1262 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_KEY_ERR);
1263 return nullptr;
1264 }
1265
1266 const int64_t id = Id();
1267
1268 SerializedKeyRange serializedKeyRange;
1269 keyRange->ToSerialized(serializedKeyRange);
1270
1271 const auto& params =
1272 aKeyOnly ? RequestParams{ObjectStoreGetKeyParams(id, serializedKeyRange)}
1273 : RequestParams{ObjectStoreGetParams(id, serializedKeyRange)};
1274
1275 auto request = GenerateRequest(aCx, this).unwrap();
1276
1277 IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
1278 "database(%s).transaction(%s).objectStore(%s).get(%s)",
1279 "IDBObjectStore.get(%.0s%.0s%.0s%.0s)",
1280 mTransaction->LoggingSerialNumber(), request->LoggingSerialNumber(),
1281 IDB_LOG_STRINGIFY(mTransaction->Database()),
1282 IDB_LOG_STRINGIFY(*mTransaction), IDB_LOG_STRINGIFY(this),
1283 IDB_LOG_STRINGIFY(keyRange));
1284
1285 // TODO: This is necessary to preserve request ordering only. Proper
1286 // sequencing of requests should be done in a more sophisticated manner that
1287 // doesn't require invalidating cursor caches (Bug 1580499).
1288 mTransaction->InvalidateCursorCaches();
1289
1290 mTransaction->StartRequest(request, params);
1291
1292 return request;
1293 }
1294
DeleteInternal(JSContext * aCx,JS::Handle<JS::Value> aKey,bool aFromCursor,ErrorResult & aRv)1295 RefPtr<IDBRequest> IDBObjectStore::DeleteInternal(JSContext* aCx,
1296 JS::Handle<JS::Value> aKey,
1297 bool aFromCursor,
1298 ErrorResult& aRv) {
1299 AssertIsOnOwningThread();
1300
1301 if (mDeletedSpec) {
1302 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
1303 return nullptr;
1304 }
1305
1306 if (!mTransaction->IsActive()) {
1307 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
1308 return nullptr;
1309 }
1310
1311 if (!mTransaction->IsWriteAllowed()) {
1312 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_READ_ONLY_ERR);
1313 return nullptr;
1314 }
1315
1316 RefPtr<IDBKeyRange> keyRange;
1317 IDBKeyRange::FromJSVal(aCx, aKey, &keyRange, aRv);
1318 if (NS_WARN_IF((aRv.Failed()))) {
1319 return nullptr;
1320 }
1321
1322 if (!keyRange) {
1323 // Must specify a key or keyRange for delete().
1324 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_KEY_ERR);
1325 return nullptr;
1326 }
1327
1328 ObjectStoreDeleteParams params;
1329 params.objectStoreId() = Id();
1330 keyRange->ToSerialized(params.keyRange());
1331
1332 auto request = GenerateRequest(aCx, this).unwrap();
1333
1334 if (!aFromCursor) {
1335 IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
1336 "database(%s).transaction(%s).objectStore(%s).delete(%s)",
1337 "IDBObjectStore.delete(%.0s%.0s%.0s%.0s)",
1338 mTransaction->LoggingSerialNumber(), request->LoggingSerialNumber(),
1339 IDB_LOG_STRINGIFY(mTransaction->Database()),
1340 IDB_LOG_STRINGIFY(*mTransaction), IDB_LOG_STRINGIFY(this),
1341 IDB_LOG_STRINGIFY(keyRange));
1342 }
1343
1344 mTransaction->StartRequest(request, params);
1345
1346 mTransaction->InvalidateCursorCaches();
1347
1348 return request;
1349 }
1350
CreateIndex(const nsAString & aName,const StringOrStringSequence & aKeyPath,const IDBIndexParameters & aOptionalParameters,ErrorResult & aRv)1351 RefPtr<IDBIndex> IDBObjectStore::CreateIndex(
1352 const nsAString& aName, const StringOrStringSequence& aKeyPath,
1353 const IDBIndexParameters& aOptionalParameters, ErrorResult& aRv) {
1354 AssertIsOnOwningThread();
1355
1356 if (mTransaction->GetMode() != IDBTransaction::Mode::VersionChange ||
1357 mDeletedSpec) {
1358 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
1359 return nullptr;
1360 }
1361
1362 const auto transaction = IDBTransaction::MaybeCurrent();
1363 if (!transaction || transaction != mTransaction || !transaction->IsActive()) {
1364 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
1365 return nullptr;
1366 }
1367
1368 const auto& indexes = mSpec->indexes();
1369 const auto end = indexes.cend();
1370 const auto foundIt = std::find_if(
1371 indexes.cbegin(), end,
1372 [&aName](const auto& index) { return aName == index.name(); });
1373 if (foundIt != end) {
1374 aRv.ThrowConstraintError(nsPrintfCString(
1375 "Index named '%s' already exists at index '%zu'",
1376 NS_ConvertUTF16toUTF8(aName).get(), foundIt.GetIndex()));
1377 return nullptr;
1378 }
1379
1380 const auto checkValid = [](const auto& keyPath) -> Result<KeyPath, nsresult> {
1381 if (!keyPath.IsValid()) {
1382 return Err(NS_ERROR_DOM_SYNTAX_ERR);
1383 }
1384
1385 return keyPath;
1386 };
1387
1388 QM_NOTEONLY_TRY_UNWRAP(
1389 const auto maybeKeyPath,
1390 ([&aKeyPath, checkValid]() -> Result<KeyPath, nsresult> {
1391 if (aKeyPath.IsString()) {
1392 QM_TRY_RETURN(
1393 KeyPath::Parse(aKeyPath.GetAsString()).andThen(checkValid));
1394 }
1395
1396 MOZ_ASSERT(aKeyPath.IsStringSequence());
1397 if (aKeyPath.GetAsStringSequence().IsEmpty()) {
1398 return Err(NS_ERROR_DOM_SYNTAX_ERR);
1399 }
1400
1401 QM_TRY_RETURN(
1402 KeyPath::Parse(aKeyPath.GetAsStringSequence()).andThen(checkValid));
1403 })());
1404 if (!maybeKeyPath) {
1405 aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
1406 return nullptr;
1407 }
1408
1409 const auto& keyPath = maybeKeyPath.ref();
1410
1411 if (aOptionalParameters.mMultiEntry && keyPath.IsArray()) {
1412 aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
1413 return nullptr;
1414 }
1415
1416 #ifdef DEBUG
1417 {
1418 const auto duplicateIndexName = std::any_of(
1419 mIndexes.cbegin(), mIndexes.cend(),
1420 [&aName](const auto& index) { return index->Name() == aName; });
1421 MOZ_ASSERT(!duplicateIndexName);
1422 }
1423 #endif
1424
1425 const IndexMetadata* const oldMetadataElements =
1426 indexes.IsEmpty() ? nullptr : indexes.Elements();
1427
1428 // With this setup we only validate the passed in locale name by the time we
1429 // get to encoding Keys. Maybe we should do it here right away and error out.
1430
1431 // Valid locale names are always ASCII as per BCP-47.
1432 nsCString locale = NS_LossyConvertUTF16toASCII(aOptionalParameters.mLocale);
1433 bool autoLocale = locale.EqualsASCII("auto");
1434 if (autoLocale) {
1435 locale = IndexedDatabaseManager::GetLocale();
1436 }
1437
1438 IndexMetadata* const metadata = mSpec->indexes().EmplaceBack(
1439 transaction->NextIndexId(), nsString(aName), keyPath, locale,
1440 aOptionalParameters.mUnique, aOptionalParameters.mMultiEntry, autoLocale);
1441
1442 if (oldMetadataElements && oldMetadataElements != indexes.Elements()) {
1443 MOZ_ASSERT(indexes.Length() > 1);
1444
1445 // Array got moved, update the spec pointers for all live indexes.
1446 RefreshSpec(/* aMayDelete */ false);
1447 }
1448
1449 transaction->CreateIndex(this, *metadata);
1450
1451 auto index = IDBIndex::Create(this, *metadata);
1452
1453 mIndexes.AppendElement(index);
1454
1455 // Don't do this in the macro because we always need to increment the serial
1456 // number to keep in sync with the parent.
1457 const uint64_t requestSerialNumber = IDBRequest::NextSerialNumber();
1458
1459 IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
1460 "database(%s).transaction(%s).objectStore(%s).createIndex(%s)",
1461 "IDBObjectStore.createIndex(%.0s%.0s%.0s%.0s)",
1462 mTransaction->LoggingSerialNumber(), requestSerialNumber,
1463 IDB_LOG_STRINGIFY(mTransaction->Database()),
1464 IDB_LOG_STRINGIFY(*mTransaction), IDB_LOG_STRINGIFY(this),
1465 IDB_LOG_STRINGIFY(index));
1466
1467 return index;
1468 }
1469
DeleteIndex(const nsAString & aName,ErrorResult & aRv)1470 void IDBObjectStore::DeleteIndex(const nsAString& aName, ErrorResult& aRv) {
1471 AssertIsOnOwningThread();
1472
1473 if (mTransaction->GetMode() != IDBTransaction::Mode::VersionChange ||
1474 mDeletedSpec) {
1475 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
1476 return;
1477 }
1478
1479 const auto transaction = IDBTransaction::MaybeCurrent();
1480 if (!transaction || transaction != mTransaction || !transaction->IsActive()) {
1481 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
1482 return;
1483 }
1484
1485 const auto& metadataArray = mSpec->indexes();
1486
1487 const auto endMetadata = metadataArray.cend();
1488 const auto foundMetadataIt = std::find_if(
1489 metadataArray.cbegin(), endMetadata,
1490 [&aName](const auto& metadata) { return aName == metadata.name(); });
1491
1492 if (foundMetadataIt == endMetadata) {
1493 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_FOUND_ERR);
1494 return;
1495 }
1496
1497 const auto foundId = foundMetadataIt->id();
1498 MOZ_ASSERT(foundId);
1499
1500 // Must remove index from mIndexes before altering the metadata array!
1501 {
1502 const auto end = mIndexes.end();
1503 const auto foundIt = std::find_if(
1504 mIndexes.begin(), end,
1505 [foundId](const auto& index) { return index->Id() == foundId; });
1506 // TODO: Or should we assert foundIt != end?
1507 if (foundIt != end) {
1508 auto& index = *foundIt;
1509
1510 index->NoteDeletion();
1511
1512 mDeletedIndexes.EmplaceBack(std::move(index));
1513 mIndexes.RemoveElementAt(foundIt.GetIndex());
1514 }
1515 }
1516
1517 mSpec->indexes().RemoveElementAt(foundMetadataIt.GetIndex());
1518
1519 RefreshSpec(/* aMayDelete */ false);
1520
1521 // Don't do this in the macro because we always need to increment the serial
1522 // number to keep in sync with the parent.
1523 const uint64_t requestSerialNumber = IDBRequest::NextSerialNumber();
1524
1525 IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
1526 "database(%s).transaction(%s).objectStore(%s)."
1527 "deleteIndex(\"%s\")",
1528 "IDBObjectStore.deleteIndex(%.0s%.0s%.0s%.0s)",
1529 mTransaction->LoggingSerialNumber(), requestSerialNumber,
1530 IDB_LOG_STRINGIFY(mTransaction->Database()),
1531 IDB_LOG_STRINGIFY(*mTransaction), IDB_LOG_STRINGIFY(this),
1532 NS_ConvertUTF16toUTF8(aName).get());
1533
1534 transaction->DeleteIndex(this, foundId);
1535 }
1536
Count(JSContext * aCx,JS::Handle<JS::Value> aKey,ErrorResult & aRv)1537 RefPtr<IDBRequest> IDBObjectStore::Count(JSContext* aCx,
1538 JS::Handle<JS::Value> aKey,
1539 ErrorResult& aRv) {
1540 AssertIsOnOwningThread();
1541
1542 if (mDeletedSpec) {
1543 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
1544 return nullptr;
1545 }
1546
1547 if (!mTransaction->IsActive()) {
1548 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
1549 return nullptr;
1550 }
1551
1552 RefPtr<IDBKeyRange> keyRange;
1553 IDBKeyRange::FromJSVal(aCx, aKey, &keyRange, aRv);
1554 if (aRv.Failed()) {
1555 return nullptr;
1556 }
1557
1558 ObjectStoreCountParams params;
1559 params.objectStoreId() = Id();
1560
1561 if (keyRange) {
1562 SerializedKeyRange serializedKeyRange;
1563 keyRange->ToSerialized(serializedKeyRange);
1564 params.optionalKeyRange().emplace(serializedKeyRange);
1565 }
1566
1567 auto request = GenerateRequest(aCx, this).unwrap();
1568
1569 IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
1570 "database(%s).transaction(%s).objectStore(%s).count(%s)",
1571 "IDBObjectStore.count(%.0s%.0s%.0s%.0s)",
1572 mTransaction->LoggingSerialNumber(), request->LoggingSerialNumber(),
1573 IDB_LOG_STRINGIFY(mTransaction->Database()),
1574 IDB_LOG_STRINGIFY(*mTransaction), IDB_LOG_STRINGIFY(this),
1575 IDB_LOG_STRINGIFY(keyRange));
1576
1577 // TODO: This is necessary to preserve request ordering only. Proper
1578 // sequencing of requests should be done in a more sophisticated manner that
1579 // doesn't require invalidating cursor caches (Bug 1580499).
1580 mTransaction->InvalidateCursorCaches();
1581
1582 mTransaction->StartRequest(request, params);
1583
1584 return request;
1585 }
1586
OpenCursorInternal(bool aKeysOnly,JSContext * aCx,JS::Handle<JS::Value> aRange,IDBCursorDirection aDirection,ErrorResult & aRv)1587 RefPtr<IDBRequest> IDBObjectStore::OpenCursorInternal(
1588 bool aKeysOnly, JSContext* aCx, JS::Handle<JS::Value> aRange,
1589 IDBCursorDirection aDirection, ErrorResult& aRv) {
1590 AssertIsOnOwningThread();
1591 MOZ_ASSERT(aCx);
1592
1593 if (mDeletedSpec) {
1594 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
1595 return nullptr;
1596 }
1597
1598 if (!mTransaction->IsActive()) {
1599 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
1600 return nullptr;
1601 }
1602
1603 RefPtr<IDBKeyRange> keyRange;
1604 IDBKeyRange::FromJSVal(aCx, aRange, &keyRange, aRv);
1605 if (NS_WARN_IF(aRv.Failed())) {
1606 return nullptr;
1607 }
1608
1609 const int64_t objectStoreId = Id();
1610
1611 Maybe<SerializedKeyRange> optionalKeyRange;
1612
1613 if (keyRange) {
1614 SerializedKeyRange serializedKeyRange;
1615 keyRange->ToSerialized(serializedKeyRange);
1616
1617 optionalKeyRange.emplace(std::move(serializedKeyRange));
1618 }
1619
1620 const CommonOpenCursorParams commonParams = {
1621 objectStoreId, std::move(optionalKeyRange), aDirection};
1622
1623 // TODO: It would be great if the IPDL generator created a constructor
1624 // accepting a CommonOpenCursorParams by value or rvalue reference.
1625 const auto params =
1626 aKeysOnly ? OpenCursorParams{ObjectStoreOpenKeyCursorParams{commonParams}}
1627 : OpenCursorParams{ObjectStoreOpenCursorParams{commonParams}};
1628
1629 auto request = GenerateRequest(aCx, this).unwrap();
1630
1631 if (aKeysOnly) {
1632 IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
1633 "database(%s).transaction(%s).objectStore(%s)."
1634 "openKeyCursor(%s, %s)",
1635 "IDBObjectStore.openKeyCursor(%.0s%.0s%.0s%.0s%.0s)",
1636 mTransaction->LoggingSerialNumber(), request->LoggingSerialNumber(),
1637 IDB_LOG_STRINGIFY(mTransaction->Database()),
1638 IDB_LOG_STRINGIFY(*mTransaction), IDB_LOG_STRINGIFY(this),
1639 IDB_LOG_STRINGIFY(keyRange), IDB_LOG_STRINGIFY(aDirection));
1640 } else {
1641 IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
1642 "database(%s).transaction(%s).objectStore(%s)."
1643 "openCursor(%s, %s)",
1644 "IDBObjectStore.openCursor(%.0s%.0s%.0s%.0s%.0s)",
1645 mTransaction->LoggingSerialNumber(), request->LoggingSerialNumber(),
1646 IDB_LOG_STRINGIFY(mTransaction->Database()),
1647 IDB_LOG_STRINGIFY(*mTransaction), IDB_LOG_STRINGIFY(this),
1648 IDB_LOG_STRINGIFY(keyRange), IDB_LOG_STRINGIFY(aDirection));
1649 }
1650
1651 const auto actor =
1652 aKeysOnly
1653 ? static_cast<SafeRefPtr<BackgroundCursorChildBase>>(
1654 MakeSafeRefPtr<
1655 BackgroundCursorChild<IDBCursorType::ObjectStoreKey>>(
1656 request, this, aDirection))
1657 : MakeSafeRefPtr<BackgroundCursorChild<IDBCursorType::ObjectStore>>(
1658 request, this, aDirection);
1659
1660 // TODO: This is necessary to preserve request ordering only. Proper
1661 // sequencing of requests should be done in a more sophisticated manner that
1662 // doesn't require invalidating cursor caches (Bug 1580499).
1663 mTransaction->InvalidateCursorCaches();
1664
1665 mTransaction->OpenCursor(*actor, params);
1666
1667 return request;
1668 }
1669
RefreshSpec(bool aMayDelete)1670 void IDBObjectStore::RefreshSpec(bool aMayDelete) {
1671 AssertIsOnOwningThread();
1672 MOZ_ASSERT_IF(mDeletedSpec, mSpec == mDeletedSpec.get());
1673
1674 auto* const foundObjectStoreSpec =
1675 mTransaction->Database()->LookupModifiableObjectStoreSpec(
1676 [id = Id()](const auto& objSpec) {
1677 return objSpec.metadata().id() == id;
1678 });
1679 if (foundObjectStoreSpec) {
1680 mSpec = foundObjectStoreSpec;
1681
1682 for (auto& index : mIndexes) {
1683 index->RefreshMetadata(aMayDelete);
1684 }
1685
1686 for (auto& index : mDeletedIndexes) {
1687 index->RefreshMetadata(false);
1688 }
1689 }
1690
1691 MOZ_ASSERT_IF(!aMayDelete && !mDeletedSpec, foundObjectStoreSpec);
1692
1693 if (foundObjectStoreSpec) {
1694 MOZ_ASSERT(mSpec != mDeletedSpec.get());
1695 mDeletedSpec = nullptr;
1696 } else {
1697 NoteDeletion();
1698 }
1699 }
1700
Spec() const1701 const ObjectStoreSpec& IDBObjectStore::Spec() const {
1702 AssertIsOnOwningThread();
1703 MOZ_ASSERT(mSpec);
1704
1705 return *mSpec;
1706 }
1707
NoteDeletion()1708 void IDBObjectStore::NoteDeletion() {
1709 AssertIsOnOwningThread();
1710 MOZ_ASSERT(mSpec);
1711 MOZ_ASSERT(Id() == mSpec->metadata().id());
1712
1713 if (mDeletedSpec) {
1714 MOZ_ASSERT(mDeletedSpec.get() == mSpec);
1715 return;
1716 }
1717
1718 // Copy the spec here.
1719 mDeletedSpec = MakeUnique<ObjectStoreSpec>(*mSpec);
1720 mDeletedSpec->indexes().Clear();
1721
1722 mSpec = mDeletedSpec.get();
1723
1724 for (const auto& index : mIndexes) {
1725 index->NoteDeletion();
1726 }
1727 }
1728
Name() const1729 const nsString& IDBObjectStore::Name() const {
1730 AssertIsOnOwningThread();
1731 MOZ_ASSERT(mSpec);
1732
1733 return mSpec->metadata().name();
1734 }
1735
SetName(const nsAString & aName,ErrorResult & aRv)1736 void IDBObjectStore::SetName(const nsAString& aName, ErrorResult& aRv) {
1737 AssertIsOnOwningThread();
1738
1739 if (mTransaction->GetMode() != IDBTransaction::Mode::VersionChange ||
1740 mDeletedSpec) {
1741 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1742 return;
1743 }
1744
1745 const auto transaction = IDBTransaction::MaybeCurrent();
1746 if (!transaction || transaction != mTransaction || !transaction->IsActive()) {
1747 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
1748 return;
1749 }
1750
1751 if (aName == mSpec->metadata().name()) {
1752 return;
1753 }
1754
1755 // Cache logging string of this object store before renaming.
1756 const LoggingString loggingOldObjectStore(this);
1757
1758 const nsresult rv =
1759 transaction->Database()->RenameObjectStore(mSpec->metadata().id(), aName);
1760
1761 if (NS_FAILED(rv)) {
1762 aRv.Throw(rv);
1763 return;
1764 }
1765
1766 // Don't do this in the macro because we always need to increment the serial
1767 // number to keep in sync with the parent.
1768 const uint64_t requestSerialNumber = IDBRequest::NextSerialNumber();
1769
1770 IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
1771 "database(%s).transaction(%s).objectStore(%s).rename(%s)",
1772 "IDBObjectStore.rename(%.0s%.0s%.0s%.0s)",
1773 mTransaction->LoggingSerialNumber(), requestSerialNumber,
1774 IDB_LOG_STRINGIFY(mTransaction->Database()),
1775 IDB_LOG_STRINGIFY(*mTransaction), loggingOldObjectStore.get(),
1776 IDB_LOG_STRINGIFY(this));
1777
1778 transaction->RenameObjectStore(mSpec->metadata().id(), aName);
1779 }
1780
AutoIncrement() const1781 bool IDBObjectStore::AutoIncrement() const {
1782 AssertIsOnOwningThread();
1783 MOZ_ASSERT(mSpec);
1784
1785 return mSpec->metadata().autoIncrement();
1786 }
1787
GetKeyPath() const1788 const indexedDB::KeyPath& IDBObjectStore::GetKeyPath() const {
1789 AssertIsOnOwningThread();
1790 MOZ_ASSERT(mSpec);
1791
1792 return mSpec->metadata().keyPath();
1793 }
1794
HasValidKeyPath() const1795 bool IDBObjectStore::HasValidKeyPath() const {
1796 AssertIsOnOwningThread();
1797 MOZ_ASSERT(mSpec);
1798
1799 return GetKeyPath().IsValid();
1800 }
1801
Clone(JSContext * aCx)1802 bool IDBObjectStore::ValueWrapper::Clone(JSContext* aCx) {
1803 if (mCloned) {
1804 return true;
1805 }
1806
1807 static const JSStructuredCloneCallbacks callbacks = {
1808 CopyingStructuredCloneReadCallback /* read */,
1809 CopyingStructuredCloneWriteCallback /* write */,
1810 nullptr /* reportError */,
1811 nullptr /* readTransfer */,
1812 nullptr /* writeTransfer */,
1813 nullptr /* freeTransfer */,
1814 nullptr /* canTransfer */,
1815 nullptr /* sabCloned */
1816 };
1817
1818 StructuredCloneInfo cloneInfo;
1819
1820 JS::Rooted<JS::Value> clonedValue(aCx);
1821 if (!JS_StructuredClone(aCx, mValue, &clonedValue, &callbacks, &cloneInfo)) {
1822 return false;
1823 }
1824
1825 mValue = clonedValue;
1826
1827 mCloned = true;
1828
1829 return true;
1830 }
1831
1832 } // namespace mozilla::dom
1833