IDBDatabase.cpp (32012B)
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 "IDBDatabase.h" 8 9 #include "IDBEvents.h" 10 #include "IDBFactory.h" 11 #include "IDBIndex.h" 12 #include "IDBObjectStore.h" 13 #include "IDBRequest.h" 14 #include "IDBTransaction.h" 15 #include "IndexedDBCommon.h" 16 #include "IndexedDatabaseInlines.h" 17 #include "IndexedDatabaseManager.h" 18 #include "MainThreadUtils.h" 19 #include "ProfilerHelpers.h" 20 #include "ReportInternalError.h" 21 #include "ScriptErrorHelper.h" 22 #include "mozilla/ErrorResult.h" 23 #include "mozilla/EventDispatcher.h" 24 #include "mozilla/Services.h" 25 #include "mozilla/dom/BindingDeclarations.h" 26 #include "mozilla/dom/DOMStringListBinding.h" 27 #include "mozilla/dom/Document.h" 28 #include "mozilla/dom/Exceptions.h" 29 #include "mozilla/dom/File.h" 30 #include "mozilla/dom/IDBDatabaseBinding.h" 31 #include "mozilla/dom/IDBObjectStoreBinding.h" 32 #include "mozilla/dom/IDBTransactionBinding.h" 33 #include "mozilla/dom/IPCBlobUtils.h" 34 #include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseFileChild.h" 35 #include "mozilla/dom/indexedDB/PBackgroundIDBSharedTypes.h" 36 #include "mozilla/dom/quota/QuotaManager.h" 37 #include "mozilla/dom/quota/ResultExtensions.h" 38 #include "mozilla/ipc/BackgroundChild.h" 39 #include "mozilla/ipc/BackgroundUtils.h" 40 #include "mozilla/ipc/FileDescriptor.h" 41 #include "mozilla/ipc/InputStreamParams.h" 42 #include "mozilla/ipc/InputStreamUtils.h" 43 #include "mozilla/storage.h" 44 #include "nsCOMPtr.h" 45 #include "nsGlobalWindowInner.h" 46 #include "nsIObserver.h" 47 #include "nsIObserverService.h" 48 #include "nsIScriptError.h" 49 #include "nsISupportsPrimitives.h" 50 #include "nsIWeakReferenceUtils.h" 51 #include "nsQueryObject.h" 52 #include "nsThreadUtils.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 using namespace mozilla::services; 63 64 namespace { 65 66 const char kCycleCollectionObserverTopic[] = "cycle-collector-end"; 67 const char kMemoryPressureObserverTopic[] = "memory-pressure"; 68 const char kWindowObserverTopic[] = "inner-window-destroyed"; 69 70 class CancelableRunnableWrapper final : public CancelableRunnable { 71 nsCOMPtr<nsIRunnable> mRunnable; 72 73 public: 74 explicit CancelableRunnableWrapper(nsCOMPtr<nsIRunnable> aRunnable) 75 : CancelableRunnable("dom::CancelableRunnableWrapper"), 76 mRunnable(std::move(aRunnable)) { 77 MOZ_ASSERT(mRunnable); 78 } 79 80 private: 81 ~CancelableRunnableWrapper() = default; 82 83 NS_DECL_NSIRUNNABLE 84 nsresult Cancel() override; 85 }; 86 87 class DatabaseFile final : public PBackgroundIDBDatabaseFileChild { 88 IDBDatabase* mDatabase; 89 90 public: 91 explicit DatabaseFile(IDBDatabase* aDatabase) : mDatabase(aDatabase) { 92 MOZ_ASSERT(aDatabase); 93 aDatabase->AssertIsOnOwningThread(); 94 95 MOZ_COUNT_CTOR(DatabaseFile); 96 } 97 98 private: 99 ~DatabaseFile() { 100 MOZ_ASSERT(!mDatabase); 101 102 MOZ_COUNT_DTOR(DatabaseFile); 103 } 104 105 virtual void ActorDestroy(ActorDestroyReason aWhy) override { 106 MOZ_ASSERT(mDatabase); 107 mDatabase->AssertIsOnOwningThread(); 108 109 if (aWhy != Deletion) { 110 RefPtr<IDBDatabase> database = mDatabase; 111 database->NoteFinishedFileActor(this); 112 } 113 114 #ifdef DEBUG 115 mDatabase = nullptr; 116 #endif 117 } 118 }; 119 120 } // namespace 121 122 class IDBDatabase::Observer final : public nsIObserver { 123 IDBDatabase* mWeakDatabase; 124 const uint64_t mWindowId; 125 126 public: 127 Observer(IDBDatabase* aDatabase, uint64_t aWindowId) 128 : mWeakDatabase(aDatabase), mWindowId(aWindowId) { 129 MOZ_ASSERT(NS_IsMainThread()); 130 MOZ_ASSERT(aDatabase); 131 } 132 133 void Revoke() { 134 MOZ_ASSERT(NS_IsMainThread()); 135 136 mWeakDatabase = nullptr; 137 } 138 139 NS_DECL_ISUPPORTS 140 141 private: 142 ~Observer() { 143 MOZ_ASSERT(NS_IsMainThread()); 144 MOZ_ASSERT(!mWeakDatabase); 145 } 146 147 NS_DECL_NSIOBSERVER 148 }; 149 150 IDBDatabase::IDBDatabase(IDBOpenDBRequest* aRequest, 151 SafeRefPtr<IDBFactory> aFactory, 152 BackgroundDatabaseChild* aActor, 153 UniquePtr<DatabaseSpec> aSpec) 154 : DOMEventTargetHelper(aRequest), 155 mFactory(std::move(aFactory)), 156 mSpec(std::move(aSpec)), 157 mBackgroundActor(aActor), 158 mClosed(false), 159 mInvalidated(false), 160 mQuotaExceeded(false), 161 mIncreasedActiveDatabaseCount(false) { 162 MOZ_ASSERT(aRequest); 163 MOZ_ASSERT(mFactory); 164 mFactory->AssertIsOnOwningThread(); 165 MOZ_ASSERT(aActor); 166 MOZ_ASSERT(mSpec); 167 } 168 169 IDBDatabase::~IDBDatabase() { 170 AssertIsOnOwningThread(); 171 MOZ_ASSERT(!mBackgroundActor); 172 MOZ_ASSERT(!mIncreasedActiveDatabaseCount); 173 } 174 175 // static 176 RefPtr<IDBDatabase> IDBDatabase::Create(IDBOpenDBRequest* aRequest, 177 SafeRefPtr<IDBFactory> aFactory, 178 BackgroundDatabaseChild* aActor, 179 UniquePtr<DatabaseSpec> aSpec) { 180 MOZ_ASSERT(aRequest); 181 MOZ_ASSERT(aFactory); 182 aFactory->AssertIsOnOwningThread(); 183 MOZ_ASSERT(aActor); 184 MOZ_ASSERT(aSpec); 185 186 RefPtr<IDBDatabase> db = 187 new IDBDatabase(aRequest, aFactory.clonePtr(), aActor, std::move(aSpec)); 188 189 if (nsCOMPtr<nsPIDOMWindowInner> window = aFactory->GetOwnerWindow()) { 190 MOZ_ASSERT(NS_IsMainThread()); 191 uint64_t windowId = window->WindowID(); 192 193 RefPtr<Observer> observer = new Observer(db, windowId); 194 195 nsCOMPtr<nsIObserverService> obsSvc = GetObserverService(); 196 MOZ_ASSERT(obsSvc); 197 198 // This topic must be successfully registered. 199 MOZ_ALWAYS_SUCCEEDS( 200 obsSvc->AddObserver(observer, kWindowObserverTopic, false)); 201 202 // These topics are not crucial. 203 QM_WARNONLY_TRY(QM_TO_RESULT( 204 obsSvc->AddObserver(observer, kCycleCollectionObserverTopic, false))); 205 QM_WARNONLY_TRY(QM_TO_RESULT( 206 obsSvc->AddObserver(observer, kMemoryPressureObserverTopic, false))); 207 208 db->mObserver = std::move(observer); 209 } 210 211 db->IncreaseActiveDatabaseCount(); 212 213 return db; 214 } 215 216 #ifdef DEBUG 217 218 void IDBDatabase::AssertIsOnOwningThread() const { 219 MOZ_ASSERT(mFactory); 220 mFactory->AssertIsOnOwningThread(); 221 } 222 223 #endif // DEBUG 224 225 nsIEventTarget* IDBDatabase::EventTarget() const { 226 AssertIsOnOwningThread(); 227 return mFactory->EventTarget(); 228 } 229 230 void IDBDatabase::CloseInternal() { 231 AssertIsOnOwningThread(); 232 233 if (!mClosed) { 234 mClosed = true; 235 236 ExpireFileActors(/* aExpireAll */ true); 237 238 if (mObserver) { 239 mObserver->Revoke(); 240 241 nsCOMPtr<nsIObserverService> obsSvc = GetObserverService(); 242 if (obsSvc) { 243 // These might not have been registered. 244 obsSvc->RemoveObserver(mObserver, kCycleCollectionObserverTopic); 245 obsSvc->RemoveObserver(mObserver, kMemoryPressureObserverTopic); 246 247 MOZ_ALWAYS_SUCCEEDS( 248 obsSvc->RemoveObserver(mObserver, kWindowObserverTopic)); 249 } 250 251 mObserver = nullptr; 252 } 253 254 if (mBackgroundActor && !mInvalidated) { 255 mBackgroundActor->SendClose(); 256 } 257 258 // Decrease the number of active databases right after the database is 259 // closed. 260 MaybeDecreaseActiveDatabaseCount(); 261 } 262 } 263 264 void IDBDatabase::InvalidateInternal() { 265 AssertIsOnOwningThread(); 266 267 AbortTransactions(/* aShouldWarn */ true); 268 269 CloseInternal(); 270 } 271 272 void IDBDatabase::EnterSetVersionTransaction(uint64_t aNewVersion) { 273 AssertIsOnOwningThread(); 274 MOZ_ASSERT(aNewVersion); 275 MOZ_ASSERT(!RunningVersionChangeTransaction()); 276 MOZ_ASSERT(mSpec); 277 MOZ_ASSERT(!mPreviousSpec); 278 279 mPreviousSpec = MakeUnique<DatabaseSpec>(*mSpec); 280 281 mSpec->metadata().version() = aNewVersion; 282 } 283 284 void IDBDatabase::ExitSetVersionTransaction() { 285 AssertIsOnOwningThread(); 286 287 if (mPreviousSpec) { 288 mPreviousSpec = nullptr; 289 } 290 } 291 292 void IDBDatabase::RevertToPreviousState() { 293 AssertIsOnOwningThread(); 294 MOZ_ASSERT(RunningVersionChangeTransaction()); 295 MOZ_ASSERT(mPreviousSpec); 296 297 // Hold the current spec alive until RefreshTransactionsSpecEnumerator has 298 // finished! 299 auto currentSpec = std::move(mSpec); 300 301 mSpec = std::move(mPreviousSpec); 302 303 RefreshSpec(/* aMayDelete */ true); 304 } 305 306 void IDBDatabase::RefreshSpec(bool aMayDelete) { 307 AssertIsOnOwningThread(); 308 309 for (auto* weakTransaction : mTransactions) { 310 const auto transaction = 311 SafeRefPtr{weakTransaction, AcquireStrongRefFromRawPtr{}}; 312 MOZ_ASSERT(transaction); 313 transaction->AssertIsOnOwningThread(); 314 transaction->RefreshSpec(aMayDelete); 315 } 316 } 317 318 const nsString& IDBDatabase::Name() const { 319 AssertIsOnOwningThread(); 320 MOZ_ASSERT(mSpec); 321 322 return mSpec->metadata().name(); 323 } 324 325 uint64_t IDBDatabase::Version() const { 326 AssertIsOnOwningThread(); 327 MOZ_ASSERT(mSpec); 328 329 return mSpec->metadata().version(); 330 } 331 332 RefPtr<DOMStringList> IDBDatabase::ObjectStoreNames() const { 333 AssertIsOnOwningThread(); 334 MOZ_ASSERT(mSpec); 335 336 return CreateSortedDOMStringList( 337 mSpec->objectStores(), 338 [](const auto& objectStore) { return objectStore.metadata().name(); }); 339 } 340 341 RefPtr<Document> IDBDatabase::GetOwnerDocument() const { 342 if (nsPIDOMWindowInner* window = GetOwnerWindow()) { 343 return window->GetExtantDoc(); 344 } 345 return nullptr; 346 } 347 348 RefPtr<IDBObjectStore> IDBDatabase::CreateObjectStore( 349 const nsAString& aName, const IDBObjectStoreParameters& aOptionalParameters, 350 ErrorResult& aRv) { 351 AssertIsOnOwningThread(); 352 353 const auto transaction = IDBTransaction::MaybeCurrent(); 354 if (!transaction || transaction->Database() != this || 355 transaction->GetMode() != IDBTransaction::Mode::VersionChange) { 356 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR); 357 return nullptr; 358 } 359 360 if (!transaction->IsActive()) { 361 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR); 362 return nullptr; 363 } 364 365 QM_INFOONLY_TRY_UNWRAP(const auto maybeKeyPath, 366 KeyPath::Parse(aOptionalParameters.mKeyPath)); 367 if (!maybeKeyPath) { 368 aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); 369 return nullptr; 370 } 371 372 const auto& keyPath = maybeKeyPath.ref(); 373 374 auto& objectStores = mSpec->objectStores(); 375 const auto end = objectStores.cend(); 376 const auto foundIt = std::find_if( 377 objectStores.cbegin(), end, [&aName](const auto& objectStore) { 378 return aName == objectStore.metadata().name(); 379 }); 380 if (foundIt != end) { 381 aRv.ThrowConstraintError(nsPrintfCString( 382 "Object store named '%s' already exists at index '%zu'", 383 NS_ConvertUTF16toUTF8(aName).get(), foundIt.GetIndex())); 384 return nullptr; 385 } 386 387 if (!keyPath.IsAllowedForObjectStore(aOptionalParameters.mAutoIncrement)) { 388 aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR); 389 return nullptr; 390 } 391 392 const ObjectStoreSpec* oldSpecElements = 393 objectStores.IsEmpty() ? nullptr : objectStores.Elements(); 394 395 ObjectStoreSpec* newSpec = objectStores.AppendElement(); 396 newSpec->metadata() = 397 ObjectStoreMetadata(transaction->NextObjectStoreId(), nsString(aName), 398 keyPath, aOptionalParameters.mAutoIncrement); 399 400 if (oldSpecElements && oldSpecElements != objectStores.Elements()) { 401 MOZ_ASSERT(objectStores.Length() > 1); 402 403 // Array got moved, update the spec pointers for all live objectStores and 404 // indexes. 405 RefreshSpec(/* aMayDelete */ false); 406 } 407 408 auto objectStore = transaction->CreateObjectStore(*newSpec); 409 MOZ_ASSERT(objectStore); 410 411 // Don't do this in the macro because we always need to increment the serial 412 // number to keep in sync with the parent. 413 const uint64_t requestSerialNumber = IDBRequest::NextSerialNumber(); 414 415 IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST( 416 "database(%s).transaction(%s).createObjectStore(%s)", 417 "IDBDatabase.createObjectStore(%.0s%.0s%.0s)", 418 transaction->LoggingSerialNumber(), requestSerialNumber, 419 IDB_LOG_STRINGIFY(this), IDB_LOG_STRINGIFY(*transaction), 420 IDB_LOG_STRINGIFY(objectStore)); 421 422 return objectStore; 423 } 424 425 void IDBDatabase::DeleteObjectStore(const nsAString& aName, ErrorResult& aRv) { 426 AssertIsOnOwningThread(); 427 428 const auto transaction = IDBTransaction::MaybeCurrent(); 429 if (!transaction || transaction->Database() != this || 430 transaction->GetMode() != IDBTransaction::Mode::VersionChange) { 431 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR); 432 return; 433 } 434 435 if (!transaction->IsActive()) { 436 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR); 437 return; 438 } 439 440 auto& specArray = mSpec->objectStores(); 441 const auto end = specArray.end(); 442 const auto foundIt = 443 std::find_if(specArray.begin(), end, [&aName](const auto& objectStore) { 444 const ObjectStoreMetadata& metadata = objectStore.metadata(); 445 MOZ_ASSERT(metadata.id()); 446 447 return aName == metadata.name(); 448 }); 449 450 if (foundIt == end) { 451 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_FOUND_ERR); 452 return; 453 } 454 455 // Must do this before altering the metadata array! 456 transaction->DeleteObjectStore(foundIt->metadata().id()); 457 458 specArray.RemoveElementAt(foundIt); 459 RefreshSpec(/* aMayDelete */ false); 460 461 // Don't do this in the macro because we always need to increment the serial 462 // number to keep in sync with the parent. 463 const uint64_t requestSerialNumber = IDBRequest::NextSerialNumber(); 464 465 IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST( 466 "database(%s).transaction(%s).deleteObjectStore(\"%s\")", 467 "IDBDatabase.deleteObjectStore(%.0s%.0s%.0s)", 468 transaction->LoggingSerialNumber(), requestSerialNumber, 469 IDB_LOG_STRINGIFY(this), IDB_LOG_STRINGIFY(*transaction), 470 NS_ConvertUTF16toUTF8(aName).get()); 471 } 472 473 RefPtr<IDBTransaction> IDBDatabase::Transaction( 474 JSContext* aCx, const StringOrStringSequence& aStoreNames, 475 IDBTransactionMode aMode, const IDBTransactionOptions& aOptions, 476 ErrorResult& aRv) { 477 AssertIsOnOwningThread(); 478 479 if ((aMode == IDBTransactionMode::Readwriteflush || 480 aMode == IDBTransactionMode::Cleanup) && 481 !StaticPrefs::dom_indexedDB_experimental()) { 482 // Pretend that this mode doesn't exist. We don't have a way to annotate 483 // certain enum values as depending on preferences so we just duplicate the 484 // normal exception generation here. 485 aRv.ThrowTypeError<MSG_INVALID_ENUM_VALUE>("argument 2", "readwriteflush", 486 "IDBTransactionMode"); 487 return nullptr; 488 } 489 490 if (QuotaManager::IsShuttingDown()) { 491 IDB_REPORT_INTERNAL_ERR(); 492 aRv.ThrowUnknownError("Can't start IndexedDB transaction during shutdown"); 493 return nullptr; 494 } 495 496 // https://w3c.github.io/IndexedDB/#dom-idbdatabase-transaction 497 // Step 1. 498 if (RunningVersionChangeTransaction()) { 499 aRv.ThrowInvalidStateError( 500 "Can't start a transaction while running an upgrade transaction"); 501 return nullptr; 502 } 503 504 // Step 2. 505 if (mClosed) { 506 aRv.ThrowInvalidStateError( 507 "Can't start a transaction on a closed database"); 508 return nullptr; 509 } 510 511 // Step 3. 512 AutoTArray<nsString, 1> stackSequence; 513 514 if (aStoreNames.IsString()) { 515 stackSequence.AppendElement(aStoreNames.GetAsString()); 516 } else { 517 MOZ_ASSERT(aStoreNames.IsStringSequence()); 518 // Step 5, but it can be done before step 4 because those steps 519 // can't both throw. 520 if (aStoreNames.GetAsStringSequence().IsEmpty()) { 521 aRv.ThrowInvalidAccessError("Empty scope passed in"); 522 return nullptr; 523 } 524 } 525 526 // Step 4. 527 const nsTArray<nsString>& storeNames = 528 aStoreNames.IsString() ? stackSequence 529 : static_cast<const nsTArray<nsString>&>( 530 aStoreNames.GetAsStringSequence()); 531 MOZ_ASSERT(!storeNames.IsEmpty()); 532 533 const nsTArray<ObjectStoreSpec>& objectStores = mSpec->objectStores(); 534 const uint32_t nameCount = storeNames.Length(); 535 536 nsTArray<nsString> sortedStoreNames; 537 sortedStoreNames.SetCapacity(nameCount); 538 539 // While collecting object store names, check if the corresponding object 540 // stores actually exist. 541 const auto begin = objectStores.cbegin(); 542 const auto end = objectStores.cend(); 543 for (const auto& name : storeNames) { 544 const auto foundIt = 545 std::find_if(begin, end, [&name](const auto& objectStore) { 546 return objectStore.metadata().name() == name; 547 }); 548 if (foundIt == end) { 549 // Not using nsPrintfCString in case "name" has embedded nulls. 550 aRv.ThrowNotFoundError("'"_ns + NS_ConvertUTF16toUTF8(name) + 551 "' is not a known object store name"_ns); 552 return nullptr; 553 } 554 555 sortedStoreNames.EmplaceBack(name); 556 } 557 sortedStoreNames.Sort(); 558 559 // Remove any duplicates. 560 sortedStoreNames.SetLength( 561 std::unique(sortedStoreNames.begin(), sortedStoreNames.end()).GetIndex()); 562 563 IDBTransaction::Mode mode; 564 switch (aMode) { 565 case IDBTransactionMode::Readonly: 566 mode = IDBTransaction::Mode::ReadOnly; 567 break; 568 case IDBTransactionMode::Readwrite: 569 if (mQuotaExceeded) { 570 mode = IDBTransaction::Mode::Cleanup; 571 mQuotaExceeded = false; 572 } else { 573 mode = IDBTransaction::Mode::ReadWrite; 574 } 575 break; 576 case IDBTransactionMode::Readwriteflush: 577 mode = IDBTransaction::Mode::ReadWriteFlush; 578 break; 579 case IDBTransactionMode::Cleanup: 580 mode = IDBTransaction::Mode::Cleanup; 581 mQuotaExceeded = false; 582 break; 583 case IDBTransactionMode::Versionchange: 584 // Step 6. 585 aRv.ThrowTypeError("Invalid transaction mode"); 586 return nullptr; 587 588 default: 589 MOZ_CRASH("Unknown mode!"); 590 } 591 592 auto durability = IDBTransaction::Durability::Default; 593 if (aOptions.IsAnyMemberPresent()) { 594 switch (aOptions.mDurability) { 595 case mozilla::dom::IDBTransactionDurability::Default: 596 durability = IDBTransaction::Durability::Default; 597 break; 598 case mozilla::dom::IDBTransactionDurability::Strict: 599 durability = IDBTransaction::Durability::Strict; 600 break; 601 case mozilla::dom::IDBTransactionDurability::Relaxed: 602 durability = IDBTransaction::Durability::Relaxed; 603 break; 604 605 default: 606 MOZ_CRASH("Unknown durability hint!"); 607 } 608 } 609 610 SafeRefPtr<IDBTransaction> transaction = 611 IDBTransaction::Create(aCx, this, sortedStoreNames, mode, durability); 612 if (NS_WARN_IF(!transaction)) { 613 IDB_REPORT_INTERNAL_ERR(); 614 MOZ_ASSERT(!NS_IsMainThread(), 615 "Transaction creation can only fail on workers"); 616 aRv.ThrowUnknownError("Failed to create IndexedDB transaction on worker"); 617 return nullptr; 618 } 619 620 RefPtr<BackgroundTransactionChild> actor = 621 new BackgroundTransactionChild(transaction.clonePtr()); 622 623 IDB_LOG_MARK_CHILD_TRANSACTION( 624 "database(%s).transaction(%s)", "IDBDatabase.transaction(%.0s%.0s)", 625 transaction->LoggingSerialNumber(), IDB_LOG_STRINGIFY(this), 626 IDB_LOG_STRINGIFY(*transaction)); 627 628 if (!mBackgroundActor->SendPBackgroundIDBTransactionConstructor( 629 actor, sortedStoreNames, mode, durability)) { 630 IDB_REPORT_INTERNAL_ERR(); 631 aRv.ThrowUnknownError("Failed to create IndexedDB transaction"); 632 return nullptr; 633 } 634 635 transaction->SetBackgroundActor(actor); 636 637 if (mode == IDBTransaction::Mode::Cleanup) { 638 ExpireFileActors(/* aExpireAll */ true); 639 } 640 641 return AsRefPtr(std::move(transaction)); 642 } 643 644 void IDBDatabase::RegisterTransaction(IDBTransaction& aTransaction) { 645 AssertIsOnOwningThread(); 646 aTransaction.AssertIsOnOwningThread(); 647 MOZ_ASSERT(!mTransactions.Contains(&aTransaction)); 648 649 mTransactions.Insert(&aTransaction); 650 } 651 652 void IDBDatabase::UnregisterTransaction(IDBTransaction& aTransaction) { 653 AssertIsOnOwningThread(); 654 aTransaction.AssertIsOnOwningThread(); 655 MOZ_ASSERT(mTransactions.Contains(&aTransaction)); 656 657 mTransactions.Remove(&aTransaction); 658 } 659 660 void IDBDatabase::AbortTransactions(bool aShouldWarn) { 661 AssertIsOnOwningThread(); 662 663 constexpr size_t StackExceptionLimit = 20; 664 using StrongTransactionArray = 665 AutoTArray<SafeRefPtr<IDBTransaction>, StackExceptionLimit>; 666 using WeakTransactionArray = AutoTArray<IDBTransaction*, StackExceptionLimit>; 667 668 if (!mTransactions.Count()) { 669 // Return early as an optimization, the remainder is a no-op in this 670 // case. 671 return; 672 } 673 674 // XXX TransformIfIntoNewArray might be generalized to allow specifying the 675 // type of nsTArray to create, so that it can create an AutoTArray as well; an 676 // TransformIf (without AbortOnErr) might be added, which could be used here. 677 StrongTransactionArray transactionsToAbort; 678 transactionsToAbort.SetCapacity(mTransactions.Count()); 679 680 for (IDBTransaction* const transaction : mTransactions) { 681 MOZ_ASSERT(transaction); 682 683 transaction->AssertIsOnOwningThread(); 684 685 // Transactions that are already done can simply be ignored. Otherwise 686 // there is a race here and it's possible that the transaction has not 687 // been successfully committed yet so we will warn the user. 688 if (!transaction->IsFinished()) { 689 transactionsToAbort.EmplaceBack(transaction, 690 AcquireStrongRefFromRawPtr{}); 691 } 692 } 693 MOZ_ASSERT(transactionsToAbort.Length() <= mTransactions.Count()); 694 695 if (transactionsToAbort.IsEmpty()) { 696 // Return early as an optimization, the remainder is a no-op in this 697 // case. 698 return; 699 } 700 701 // We want to abort transactions as soon as possible so we iterate the 702 // transactions once and abort them all first, collecting the transactions 703 // that need to have a warning issued along the way. Those that need a 704 // warning will be a subset of those that are aborted, so we don't need 705 // additional strong references here. 706 WeakTransactionArray transactionsThatNeedWarning; 707 708 for (const auto& transaction : transactionsToAbort) { 709 MOZ_ASSERT(transaction); 710 MOZ_ASSERT(!transaction->IsFinished()); 711 712 // We warn for any transactions that could have written data, but 713 // ignore read-only transactions. 714 if (aShouldWarn && transaction->IsWriteAllowed()) { 715 transactionsThatNeedWarning.AppendElement(transaction.unsafeGetRawPtr()); 716 } 717 718 transaction->Abort(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR); 719 } 720 721 static const char kWarningMessage[] = "IndexedDBTransactionAbortNavigation"; 722 723 for (IDBTransaction* transaction : transactionsThatNeedWarning) { 724 MOZ_ASSERT(transaction); 725 LogWarning(kWarningMessage, transaction->GetCallerLocation()); 726 } 727 } 728 729 PBackgroundIDBDatabaseFileChild* IDBDatabase::GetOrCreateFileActorForBlob( 730 Blob& aBlob) { 731 AssertIsOnOwningThread(); 732 MOZ_ASSERT(mBackgroundActor); 733 734 // We use the File's nsIWeakReference as the key to the table because 735 // a) it is unique per blob, b) it is reference-counted so that we can 736 // guarantee that it stays alive, and c) it doesn't hold the actual File 737 // alive. 738 nsWeakPtr weakRef = do_GetWeakReference(&aBlob); 739 MOZ_ASSERT(weakRef); 740 741 PBackgroundIDBDatabaseFileChild* actor = nullptr; 742 743 if (!mFileActors.Get(weakRef, &actor)) { 744 BlobImpl* blobImpl = aBlob.Impl(); 745 MOZ_ASSERT(blobImpl); 746 747 IPCBlob ipcBlob; 748 nsresult rv = IPCBlobUtils::Serialize(blobImpl, ipcBlob); 749 if (NS_WARN_IF(NS_FAILED(rv))) { 750 return nullptr; 751 } 752 753 auto* dbFile = new DatabaseFile(this); 754 755 actor = mBackgroundActor->SendPBackgroundIDBDatabaseFileConstructor( 756 dbFile, ipcBlob); 757 if (NS_WARN_IF(!actor)) { 758 return nullptr; 759 } 760 761 mFileActors.InsertOrUpdate(weakRef, actor); 762 } 763 764 MOZ_ASSERT(actor); 765 766 return actor; 767 } 768 769 void IDBDatabase::NoteFinishedFileActor( 770 PBackgroundIDBDatabaseFileChild* aFileActor) { 771 AssertIsOnOwningThread(); 772 MOZ_ASSERT(aFileActor); 773 774 mFileActors.RemoveIf([aFileActor](const auto& iter) { 775 MOZ_ASSERT(iter.Key()); 776 PBackgroundIDBDatabaseFileChild* actor = iter.Data(); 777 MOZ_ASSERT(actor); 778 779 return actor == aFileActor; 780 }); 781 } 782 783 void IDBDatabase::NoteActiveTransaction() { 784 AssertIsOnOwningThread(); 785 MOZ_ASSERT(mFactory); 786 787 // Increase the number of active transactions. 788 mFactory->UpdateActiveTransactionCount(1); 789 } 790 791 void IDBDatabase::NoteInactiveTransaction() { 792 AssertIsOnOwningThread(); 793 794 if (!mBackgroundActor || !mFileActors.Count()) { 795 MOZ_ASSERT(mFactory); 796 mFactory->UpdateActiveTransactionCount(-1); 797 return; 798 } 799 800 RefPtr<Runnable> runnable = 801 NewRunnableMethod("IDBDatabase::NoteInactiveTransactionDelayed", this, 802 &IDBDatabase::NoteInactiveTransactionDelayed); 803 MOZ_ASSERT(runnable); 804 805 if (!NS_IsMainThread()) { 806 // Wrap as a nsICancelableRunnable to make workers happy. 807 runnable = MakeRefPtr<CancelableRunnableWrapper>(runnable.forget()); 808 } 809 810 MOZ_ALWAYS_SUCCEEDS( 811 EventTarget()->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL)); 812 } 813 814 void IDBDatabase::ExpireFileActors(bool aExpireAll) { 815 AssertIsOnOwningThread(); 816 817 if (mBackgroundActor && mFileActors.Count()) { 818 for (auto iter = mFileActors.Iter(); !iter.Done(); iter.Next()) { 819 nsISupports* key = iter.Key(); 820 PBackgroundIDBDatabaseFileChild* actor = iter.Data(); 821 MOZ_ASSERT(key); 822 MOZ_ASSERT(actor); 823 824 bool shouldExpire = aExpireAll; 825 if (!shouldExpire) { 826 nsWeakPtr weakRef = do_QueryInterface(key); 827 MOZ_ASSERT(weakRef); 828 829 nsCOMPtr<nsISupports> referent = do_QueryReferent(weakRef); 830 shouldExpire = !referent; 831 } 832 833 if (shouldExpire) { 834 PBackgroundIDBDatabaseFileChild::Send__delete__(actor); 835 836 if (!aExpireAll) { 837 iter.Remove(); 838 } 839 } 840 } 841 if (aExpireAll) { 842 mFileActors.Clear(); 843 } 844 } else { 845 MOZ_ASSERT(!mFileActors.Count()); 846 } 847 } 848 849 void IDBDatabase::Invalidate() { 850 AssertIsOnOwningThread(); 851 852 if (!mInvalidated) { 853 mInvalidated = true; 854 855 InvalidateInternal(); 856 } 857 } 858 859 void IDBDatabase::NoteInactiveTransactionDelayed() { 860 ExpireFileActors(/* aExpireAll */ false); 861 862 MOZ_ASSERT(mFactory); 863 mFactory->UpdateActiveTransactionCount(-1); 864 } 865 866 void IDBDatabase::LogWarning(const char* aMessageName, 867 const JSCallingLocation& aLoc) { 868 AssertIsOnOwningThread(); 869 MOZ_ASSERT(aMessageName); 870 871 ScriptErrorHelper::DumpLocalizedMessage( 872 nsDependentCString(aMessageName), aLoc, nsIScriptError::warningFlag, 873 mFactory->IsChrome(), mFactory->InnerWindowID()); 874 } 875 876 NS_IMPL_ADDREF_INHERITED(IDBDatabase, DOMEventTargetHelper) 877 NS_IMPL_RELEASE_INHERITED(IDBDatabase, DOMEventTargetHelper) 878 879 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IDBDatabase) 880 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) 881 882 NS_IMPL_CYCLE_COLLECTION_CLASS(IDBDatabase) 883 884 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(IDBDatabase, 885 DOMEventTargetHelper) 886 tmp->AssertIsOnOwningThread(); 887 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFactory) 888 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 889 890 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(IDBDatabase, 891 DOMEventTargetHelper) 892 tmp->AssertIsOnOwningThread(); 893 894 // Don't unlink mFactory! 895 896 // We've been unlinked, at the very least we should be able to prevent further 897 // transactions from starting and unblock any other SetVersion callers. 898 tmp->CloseInternal(); 899 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 900 901 void IDBDatabase::DisconnectFromOwner() { 902 InvalidateInternal(); 903 DOMEventTargetHelper::DisconnectFromOwner(); 904 } 905 906 void IDBDatabase::LastRelease() { 907 AssertIsOnOwningThread(); 908 909 CloseInternal(); 910 911 ExpireFileActors(/* aExpireAll */ true); 912 913 if (mBackgroundActor) { 914 mBackgroundActor->SendDeleteMeInternal(); 915 MOZ_ASSERT(!mBackgroundActor, "SendDeleteMeInternal should have cleared!"); 916 } 917 } 918 919 JSObject* IDBDatabase::WrapObject(JSContext* aCx, 920 JS::Handle<JSObject*> aGivenProto) { 921 return IDBDatabase_Binding::Wrap(aCx, this, aGivenProto); 922 } 923 924 NS_IMETHODIMP 925 CancelableRunnableWrapper::Run() { 926 const nsCOMPtr<nsIRunnable> runnable = std::move(mRunnable); 927 928 if (runnable) { 929 return runnable->Run(); 930 } 931 932 return NS_OK; 933 } 934 935 nsresult CancelableRunnableWrapper::Cancel() { 936 if (mRunnable) { 937 mRunnable = nullptr; 938 return NS_OK; 939 } 940 941 return NS_ERROR_UNEXPECTED; 942 } 943 944 NS_IMPL_ISUPPORTS(IDBDatabase::Observer, nsIObserver) 945 946 NS_IMETHODIMP 947 IDBDatabase::Observer::Observe(nsISupports* aSubject, const char* aTopic, 948 const char16_t* aData) { 949 MOZ_ASSERT(NS_IsMainThread()); 950 MOZ_ASSERT(aTopic); 951 952 if (!strcmp(aTopic, kWindowObserverTopic)) { 953 if (mWeakDatabase) { 954 nsCOMPtr<nsISupportsPRUint64> supportsInt = do_QueryInterface(aSubject); 955 MOZ_ASSERT(supportsInt); 956 957 uint64_t windowId; 958 MOZ_ALWAYS_SUCCEEDS(supportsInt->GetData(&windowId)); 959 960 if (windowId == mWindowId) { 961 RefPtr<IDBDatabase> database = mWeakDatabase; 962 mWeakDatabase = nullptr; 963 964 database->InvalidateInternal(); 965 } 966 } 967 968 return NS_OK; 969 } 970 971 if (!strcmp(aTopic, kCycleCollectionObserverTopic) || 972 !strcmp(aTopic, kMemoryPressureObserverTopic)) { 973 if (mWeakDatabase) { 974 RefPtr<IDBDatabase> database = mWeakDatabase; 975 976 database->ExpireFileActors(/* aExpireAll */ false); 977 } 978 979 return NS_OK; 980 } 981 982 NS_WARNING("Unknown observer topic!"); 983 return NS_OK; 984 } 985 986 nsresult IDBDatabase::RenameObjectStore(int64_t aObjectStoreId, 987 const nsAString& aName) { 988 MOZ_ASSERT(mSpec); 989 990 nsTArray<ObjectStoreSpec>& objectStores = mSpec->objectStores(); 991 ObjectStoreSpec* foundObjectStoreSpec = nullptr; 992 993 // Find the matched object store spec and check if 'aName' is already used by 994 // another object store. 995 996 for (auto& objSpec : objectStores) { 997 const bool idIsCurrent = objSpec.metadata().id() == aObjectStoreId; 998 999 if (idIsCurrent) { 1000 MOZ_ASSERT(!foundObjectStoreSpec); 1001 foundObjectStoreSpec = &objSpec; 1002 } 1003 1004 if (objSpec.metadata().name() == aName) { 1005 if (idIsCurrent) { 1006 return NS_OK; 1007 } 1008 return NS_ERROR_DOM_INDEXEDDB_RENAME_OBJECT_STORE_ERR; 1009 } 1010 } 1011 1012 MOZ_ASSERT(foundObjectStoreSpec); 1013 1014 // Update the name of the matched object store. 1015 foundObjectStoreSpec->metadata().name().Assign(aName); 1016 1017 return NS_OK; 1018 } 1019 1020 nsresult IDBDatabase::RenameIndex(int64_t aObjectStoreId, int64_t aIndexId, 1021 const nsAString& aName) { 1022 MOZ_ASSERT(mSpec); 1023 1024 nsTArray<ObjectStoreSpec>& objectStores = mSpec->objectStores(); 1025 1026 ObjectStoreSpec* foundObjectStoreSpec = nullptr; 1027 // Find the matched index metadata and check if 'aName' is already used by 1028 // another index. 1029 for (uint32_t objCount = objectStores.Length(), objIndex = 0; 1030 objIndex < objCount; objIndex++) { 1031 const ObjectStoreSpec& objSpec = objectStores[objIndex]; 1032 if (objSpec.metadata().id() == aObjectStoreId) { 1033 foundObjectStoreSpec = &objectStores[objIndex]; 1034 break; 1035 } 1036 } 1037 1038 MOZ_ASSERT(foundObjectStoreSpec); 1039 1040 nsTArray<IndexMetadata>& indexes = foundObjectStoreSpec->indexes(); 1041 IndexMetadata* foundIndexMetadata = nullptr; 1042 for (uint32_t idxCount = indexes.Length(), idxIndex = 0; idxIndex < idxCount; 1043 idxIndex++) { 1044 const IndexMetadata& metadata = indexes[idxIndex]; 1045 if (metadata.id() == aIndexId) { 1046 MOZ_ASSERT(!foundIndexMetadata); 1047 foundIndexMetadata = &indexes[idxIndex]; 1048 continue; 1049 } 1050 if (aName == metadata.name()) { 1051 return NS_ERROR_DOM_INDEXEDDB_RENAME_INDEX_ERR; 1052 } 1053 } 1054 1055 MOZ_ASSERT(foundIndexMetadata); 1056 1057 // Update the name of the matched object store. 1058 foundIndexMetadata->name() = nsString(aName); 1059 1060 return NS_OK; 1061 } 1062 1063 void IDBDatabase::IncreaseActiveDatabaseCount() { 1064 AssertIsOnOwningThread(); 1065 MOZ_ASSERT(mFactory); 1066 MOZ_ASSERT(!mIncreasedActiveDatabaseCount); 1067 1068 mFactory->UpdateActiveDatabaseCount(1); 1069 mIncreasedActiveDatabaseCount = true; 1070 } 1071 1072 void IDBDatabase::MaybeDecreaseActiveDatabaseCount() { 1073 AssertIsOnOwningThread(); 1074 1075 if (mIncreasedActiveDatabaseCount) { 1076 // Decrease the number of active databases. 1077 MOZ_ASSERT(mFactory); 1078 mFactory->UpdateActiveDatabaseCount(-1); 1079 mIncreasedActiveDatabaseCount = false; 1080 } 1081 } 1082 1083 } // namespace mozilla::dom