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